246 lines
9.3 KiB
C#
246 lines
9.3 KiB
C#
using SharpChat.Config;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace SharpChat.Misuzu {
|
|
public class MisuzuClient {
|
|
private const string DEFAULT_BASE_URL = "https://flashii.net/_sockchat";
|
|
private const string DEFAULT_SECRET_KEY = "woomy";
|
|
|
|
private const string BUMP_ONLINE_URL = "{0}/bump";
|
|
private const string AUTH_VERIFY_URL = "{0}/verify";
|
|
|
|
private const string BANS_CHECK_URL = "{0}/bans/check?u={1}&a={2}&x={3}&n={4}";
|
|
private const string BANS_CREATE_URL = "{0}/bans/create";
|
|
private const string BANS_REVOKE_URL = "{0}/bans/revoke?t={1}&s={2}&x={3}";
|
|
private const string BANS_LIST_URL = "{0}/bans/list?x={1}";
|
|
|
|
private const string VERIFY_SIG = "verify#{0}#{1}#{2}";
|
|
private const string BANS_CHECK_SIG = "check#{0}#{1}#{2}#{3}";
|
|
private const string BANS_REVOKE_SIG = "revoke#{0}#{1}#{2}";
|
|
private const string BANS_CREATE_SIG = "create#{0}#{1}#{2}#{3}#{4}#{5}#{6}#{7}";
|
|
private const string BANS_LIST_SIG = "list#{0}";
|
|
|
|
private readonly HttpClient HttpClient;
|
|
|
|
private CachedValue<string> BaseURL { get; }
|
|
private CachedValue<string> SecretKey { get; }
|
|
|
|
public MisuzuClient(HttpClient httpClient, IConfig config) {
|
|
HttpClient = httpClient;
|
|
|
|
BaseURL = config.ReadCached("url", DEFAULT_BASE_URL);
|
|
SecretKey = config.ReadCached("secret", DEFAULT_SECRET_KEY);
|
|
}
|
|
|
|
public string CreateStringSignature(string str) {
|
|
return CreateBufferSignature(Encoding.UTF8.GetBytes(str));
|
|
}
|
|
|
|
public string CreateBufferSignature(byte[] bytes) {
|
|
using HMACSHA256 algo = new(Encoding.UTF8.GetBytes(SecretKey.Value ?? string.Empty));
|
|
return string.Concat(algo.ComputeHash(bytes).Select(c => c.ToString("x2")));
|
|
}
|
|
|
|
public async Task<MisuzuAuthInfo?> AuthVerifyAsync(string method, string token, string ipAddr) {
|
|
method ??= string.Empty;
|
|
token ??= string.Empty;
|
|
ipAddr ??= string.Empty;
|
|
|
|
string sig = string.Format(VERIFY_SIG, method, token, ipAddr);
|
|
|
|
HttpRequestMessage req = new(HttpMethod.Post, string.Format(AUTH_VERIFY_URL, BaseURL)) {
|
|
Content = new FormUrlEncodedContent(new Dictionary<string, string> {
|
|
{ "method", method },
|
|
{ "token", token },
|
|
{ "ipaddr", ipAddr },
|
|
}),
|
|
Headers = {
|
|
{ "X-SharpChat-Signature", CreateStringSignature(sig) },
|
|
},
|
|
};
|
|
|
|
using HttpResponseMessage res = await HttpClient.SendAsync(req);
|
|
|
|
return JsonSerializer.Deserialize<MisuzuAuthInfo>(
|
|
await res.Content.ReadAsByteArrayAsync()
|
|
);
|
|
}
|
|
|
|
public async Task BumpUsersOnlineAsync(IEnumerable<(string userId, string ipAddr)> list) {
|
|
if(!list.Any())
|
|
return;
|
|
|
|
string now = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
|
|
StringBuilder sb = new();
|
|
sb.AppendFormat("bump#{0}", now);
|
|
|
|
Dictionary<string, string> formData = new() {
|
|
{ "t", now }
|
|
};
|
|
|
|
foreach(var (userId, ipAddr) in list) {
|
|
sb.AppendFormat("#{0}:{1}", userId, ipAddr);
|
|
formData.Add(string.Format("u[{0}]", userId), ipAddr);
|
|
}
|
|
|
|
HttpRequestMessage req = new(HttpMethod.Post, string.Format(BUMP_ONLINE_URL, BaseURL)) {
|
|
Headers = {
|
|
{ "X-SharpChat-Signature", CreateStringSignature(sb.ToString()) }
|
|
},
|
|
Content = new FormUrlEncodedContent(formData),
|
|
};
|
|
|
|
using HttpResponseMessage res = await HttpClient.SendAsync(req);
|
|
|
|
try {
|
|
res.EnsureSuccessStatusCode();
|
|
} catch(HttpRequestException) {
|
|
Logger.Debug(await res.Content.ReadAsStringAsync());
|
|
#if DEBUG
|
|
throw;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public async Task<MisuzuBanInfo?> CheckBanAsync(
|
|
string? userId = null,
|
|
string? ipAddr = null,
|
|
bool userIdIsName = false
|
|
) {
|
|
userId ??= string.Empty;
|
|
ipAddr ??= string.Empty;
|
|
|
|
string userIdIsNameStr = userIdIsName ? "1" : "0";
|
|
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
|
string url = string.Format(BANS_CHECK_URL, BaseURL, Uri.EscapeDataString(userId), Uri.EscapeDataString(ipAddr), Uri.EscapeDataString(now), Uri.EscapeDataString(userIdIsNameStr));
|
|
string sig = string.Format(BANS_CHECK_SIG, now, userId, ipAddr, userIdIsNameStr);
|
|
|
|
HttpRequestMessage req = new(HttpMethod.Get, url) {
|
|
Headers = {
|
|
{ "X-SharpChat-Signature", CreateStringSignature(sig) },
|
|
},
|
|
};
|
|
|
|
using HttpResponseMessage res = await HttpClient.SendAsync(req);
|
|
|
|
return JsonSerializer.Deserialize<MisuzuBanInfo>(
|
|
await res.Content.ReadAsByteArrayAsync()
|
|
);
|
|
}
|
|
|
|
public async Task<MisuzuBanInfo[]?> GetBanListAsync() {
|
|
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
|
string url = string.Format(BANS_LIST_URL, BaseURL, Uri.EscapeDataString(now));
|
|
string sig = string.Format(BANS_LIST_SIG, now);
|
|
|
|
HttpRequestMessage req = new(HttpMethod.Get, url) {
|
|
Headers = {
|
|
{ "X-SharpChat-Signature", CreateStringSignature(sig) },
|
|
},
|
|
};
|
|
|
|
using HttpResponseMessage res = await HttpClient.SendAsync(req);
|
|
|
|
return JsonSerializer.Deserialize<MisuzuBanInfo[]>(
|
|
await res.Content.ReadAsByteArrayAsync()
|
|
);
|
|
}
|
|
|
|
public enum BanRevokeKind {
|
|
UserId,
|
|
RemoteAddress,
|
|
}
|
|
|
|
public async Task<bool> RevokeBanAsync(MisuzuBanInfo banInfo, BanRevokeKind kind) {
|
|
string type = kind switch {
|
|
BanRevokeKind.UserId => "user",
|
|
BanRevokeKind.RemoteAddress => "addr",
|
|
_ => throw new ArgumentException("Invalid kind specified.", nameof(kind)),
|
|
};
|
|
|
|
string target = kind switch {
|
|
BanRevokeKind.UserId => banInfo?.UserId ?? string.Empty,
|
|
BanRevokeKind.RemoteAddress => banInfo?.RemoteAddress ?? string.Empty,
|
|
_ => string.Empty,
|
|
};
|
|
|
|
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
|
string url = string.Format(BANS_REVOKE_URL, BaseURL, Uri.EscapeDataString(type), Uri.EscapeDataString(target), Uri.EscapeDataString(now));
|
|
string sig = string.Format(BANS_REVOKE_SIG, now, type, target);
|
|
|
|
HttpRequestMessage req = new(HttpMethod.Delete, url) {
|
|
Headers = {
|
|
{ "X-SharpChat-Signature", CreateStringSignature(sig) },
|
|
},
|
|
};
|
|
|
|
using HttpResponseMessage res = await HttpClient.SendAsync(req);
|
|
|
|
if(res.StatusCode == HttpStatusCode.NotFound)
|
|
return false;
|
|
|
|
res.EnsureSuccessStatusCode();
|
|
|
|
return res.StatusCode == HttpStatusCode.NoContent;
|
|
}
|
|
|
|
public async Task CreateBanAsync(
|
|
string targetId,
|
|
string targetAddr,
|
|
string modId,
|
|
string modAddr,
|
|
TimeSpan duration,
|
|
string reason
|
|
) {
|
|
if(string.IsNullOrWhiteSpace(targetAddr))
|
|
throw new ArgumentException("targetAddr may not be empty", nameof(targetAddr));
|
|
if(string.IsNullOrWhiteSpace(modAddr))
|
|
throw new ArgumentException("modAddr may not be empty", nameof(modAddr));
|
|
if(duration <= TimeSpan.Zero)
|
|
return;
|
|
|
|
modId ??= string.Empty;
|
|
targetId ??= string.Empty;
|
|
reason ??= string.Empty;
|
|
|
|
string isPerma = duration == TimeSpan.MaxValue ? "1" : "0";
|
|
string durationStr = duration == TimeSpan.MaxValue ? "-1" : duration.TotalSeconds.ToString();
|
|
|
|
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
|
string sig = string.Format(
|
|
BANS_CREATE_SIG,
|
|
now, targetId, targetAddr,
|
|
modId, modAddr,
|
|
durationStr, isPerma, reason
|
|
);
|
|
|
|
HttpRequestMessage req = new(HttpMethod.Post, string.Format(BANS_CREATE_URL, BaseURL)) {
|
|
Headers = {
|
|
{ "X-SharpChat-Signature", CreateStringSignature(sig) },
|
|
},
|
|
Content = new FormUrlEncodedContent(new Dictionary<string, string> {
|
|
{ "t", now },
|
|
{ "ui", targetId },
|
|
{ "ua", targetAddr },
|
|
{ "mi", modId },
|
|
{ "ma", modAddr },
|
|
{ "d", durationStr },
|
|
{ "p", isPerma },
|
|
{ "r", reason },
|
|
}),
|
|
};
|
|
|
|
using HttpResponseMessage res = await HttpClient.SendAsync(req);
|
|
|
|
res.EnsureSuccessStatusCode();
|
|
}
|
|
}
|
|
}
|