181 lines
7.5 KiB
C#
181 lines
7.5 KiB
C#
|
using Hamakaze;
|
|||
|
using SharpChat.Users;
|
|||
|
using SharpChat.Users.Remote;
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.IO;
|
|||
|
using System.Linq;
|
|||
|
using System.Text.Json;
|
|||
|
|
|||
|
namespace SharpChat.DataProvider.Misuzu.Users {
|
|||
|
public class MisuzuUserClient : IRemoteUserClient {
|
|||
|
private MisuzuDataProvider DataProvider { get; }
|
|||
|
private HttpClient HttpClient { get; }
|
|||
|
|
|||
|
private const string AUTH_URL = @"/verify";
|
|||
|
private const string BUMP_URL = @"/bump";
|
|||
|
private const string RESOLVE_URL = @"/resolve?m={0}&p={1}";
|
|||
|
|
|||
|
private Dictionary<long, (DateTimeOffset age, MisuzuUser mui)> UserIdCache { get; } = new();
|
|||
|
private readonly object UserIdCacheSync = new();
|
|||
|
private static readonly TimeSpan CacheMaxAge = TimeSpan.FromMinutes(1);
|
|||
|
|
|||
|
public MisuzuUserClient(MisuzuDataProvider dataProvider, HttpClient httpClient) {
|
|||
|
DataProvider = dataProvider ?? throw new ArgumentNullException(nameof(dataProvider));
|
|||
|
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
|||
|
}
|
|||
|
|
|||
|
public void AuthenticateUser(UserAuthRequest request, Action<IUserAuthResponse> onSuccess, Action<Exception> onFailure) {
|
|||
|
if(request == null)
|
|||
|
throw new ArgumentNullException(nameof(request));
|
|||
|
if(onSuccess == null)
|
|||
|
throw new ArgumentNullException(nameof(onSuccess));
|
|||
|
if(onFailure == null)
|
|||
|
throw new ArgumentNullException(nameof(onFailure));
|
|||
|
|
|||
|
#if DEBUG
|
|||
|
if(request.UserId >= 10000) {
|
|||
|
onSuccess.Invoke(new MisuzuUserAuthResponse {
|
|||
|
Success = true,
|
|||
|
UserId = request.UserId,
|
|||
|
UserName = @"Misaka-" + (request.UserId - 10000),
|
|||
|
ColourRaw = (RNG.Next(0, 255) << 16) | (RNG.Next(0, 255) << 8) | RNG.Next(0, 255),
|
|||
|
Rank = 0,
|
|||
|
SilencedUntil = DateTimeOffset.MinValue,
|
|||
|
Permissions = UserPermissions.SendMessage | UserPermissions.EditOwnMessage | UserPermissions.DeleteOwnMessage,
|
|||
|
});
|
|||
|
return;
|
|||
|
}
|
|||
|
#endif
|
|||
|
MisuzuUserAuthRequest mar = new(request);
|
|||
|
|
|||
|
HttpRequestMessage req = new(HttpRequestMessage.POST, DataProvider.GetURL(AUTH_URL));
|
|||
|
req.SetHeader(@"X-SharpChat-Signature", DataProvider.GetSignedHash(mar));
|
|||
|
req.SetBody(JsonSerializer.SerializeToUtf8Bytes(mar));
|
|||
|
|
|||
|
HttpClient.SendRequest(
|
|||
|
req,
|
|||
|
onComplete: (t, r) => {
|
|||
|
using MemoryStream ms = new();
|
|||
|
r.Body.CopyTo(ms);
|
|||
|
MisuzuUserAuthResponse res = JsonSerializer.Deserialize<MisuzuUserAuthResponse>(ms.ToArray());
|
|||
|
if(res.Success)
|
|||
|
onSuccess.Invoke(res);
|
|||
|
else
|
|||
|
onFailure.Invoke(new UserAuthFailedException(res.Reason));
|
|||
|
},
|
|||
|
onError: (t, e) => {
|
|||
|
Logger.Write(@"An error occurred during authentication.");
|
|||
|
Logger.Debug(e);
|
|||
|
onFailure.Invoke(e);
|
|||
|
}
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
public void BumpUsers(IEnumerable<UserBumpInfo> users, Action onSuccess, Action<Exception> onFailure) {
|
|||
|
if(users == null)
|
|||
|
throw new ArgumentNullException(nameof(users));
|
|||
|
if(onSuccess == null)
|
|||
|
throw new ArgumentNullException(nameof(onSuccess));
|
|||
|
if(onFailure == null)
|
|||
|
throw new ArgumentNullException(nameof(onFailure));
|
|||
|
if(!users.Any())
|
|||
|
return;
|
|||
|
|
|||
|
byte[] data = JsonSerializer.SerializeToUtf8Bytes(users.Select(ubi => new MisuzuUserBumpInfo(ubi)));
|
|||
|
|
|||
|
HttpRequestMessage request = new(HttpRequestMessage.POST, DataProvider.GetURL(BUMP_URL));
|
|||
|
request.SetHeader(@"X-SharpChat-Signature", DataProvider.GetSignedHash(data));
|
|||
|
request.SetBody(data);
|
|||
|
|
|||
|
HttpClient.SendRequest(
|
|||
|
request,
|
|||
|
disposeRequest: false,
|
|||
|
onComplete: (t, r) => { request.Dispose(); onSuccess?.Invoke(); },
|
|||
|
onError: (t, e) => {
|
|||
|
Logger.Write(@"User bump request failed. Retrying once...");
|
|||
|
Logger.Debug(e);
|
|||
|
|
|||
|
HttpClient.SendRequest(
|
|||
|
request,
|
|||
|
onComplete: (t, r) => {
|
|||
|
Logger.Write(@"Second user bump attempt succeeded!");
|
|||
|
onSuccess?.Invoke();
|
|||
|
},
|
|||
|
onError: (t, e) => {
|
|||
|
Logger.Write(@"User bump request failed again.");
|
|||
|
Logger.Debug(e);
|
|||
|
onFailure?.Invoke(e);
|
|||
|
}
|
|||
|
);
|
|||
|
}
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
private const string RESOLVE_ID = @"id";
|
|||
|
private const string RESOLVE_NAME = @"name";
|
|||
|
|
|||
|
public void ResolveUser(long userId, Action<IRemoteUser> onSuccess, Action<Exception> onFailure) {
|
|||
|
ResolveUser(RESOLVE_ID, userId, onSuccess, onFailure);
|
|||
|
}
|
|||
|
|
|||
|
public void ResolveUser(string userName, Action<IRemoteUser> onSuccess, Action<Exception> onFailure) {
|
|||
|
ResolveUser(RESOLVE_NAME, userName, onSuccess, onFailure);
|
|||
|
}
|
|||
|
|
|||
|
public void ResolveUser(IUser localUser, Action<IRemoteUser> onSuccess, Action<Exception> onFailure) {
|
|||
|
if(localUser == null)
|
|||
|
onSuccess(null);
|
|||
|
else
|
|||
|
ResolveUser(RESOLVE_ID, localUser.UserId, onSuccess, onFailure);
|
|||
|
}
|
|||
|
|
|||
|
private void ResolveUser(string method, object param, Action<IRemoteUser> onSuccess, Action<Exception> onFailure) {
|
|||
|
if(method == null)
|
|||
|
throw new ArgumentNullException(nameof(method));
|
|||
|
if(param == null)
|
|||
|
throw new ArgumentNullException(nameof(param));
|
|||
|
if(onSuccess == null)
|
|||
|
throw new ArgumentNullException(nameof(onSuccess));
|
|||
|
if(onFailure == null)
|
|||
|
throw new ArgumentNullException(nameof(onFailure));
|
|||
|
|
|||
|
if(method == RESOLVE_ID) {
|
|||
|
MisuzuUser mui = null;
|
|||
|
lock(UserIdCacheSync)
|
|||
|
if(UserIdCache.TryGetValue((long)param, out (DateTimeOffset age, MisuzuUser mui) cache)
|
|||
|
&& (DateTimeOffset.Now - cache.age) < CacheMaxAge)
|
|||
|
mui = cache.mui;
|
|||
|
|
|||
|
if(mui != null) {
|
|||
|
onSuccess(mui);
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
HttpRequestMessage req = new(HttpRequestMessage.GET, DataProvider.GetURL(
|
|||
|
string.Format(RESOLVE_URL, method, param)
|
|||
|
));
|
|||
|
req.SetHeader(@"X-SharpChat-Signature", DataProvider.GetSignedHash(string.Format(
|
|||
|
@"resolve#{0}#{1}", method, param
|
|||
|
)));
|
|||
|
|
|||
|
HttpClient.SendRequest(
|
|||
|
req,
|
|||
|
(t, r) => {
|
|||
|
try {
|
|||
|
MisuzuUser mui = JsonSerializer.Deserialize<MisuzuUser>(r.GetBodyBytes());
|
|||
|
lock(UserIdCacheSync)
|
|||
|
UserIdCache[mui.UserId] = (DateTimeOffset.Now, mui);
|
|||
|
onSuccess.Invoke(mui);
|
|||
|
} catch(Exception ex) {
|
|||
|
Logger.Debug(ex);
|
|||
|
onFailure.Invoke(ex);
|
|||
|
}
|
|||
|
},
|
|||
|
(t, e) => { Logger.Debug(e); onFailure.Invoke(e); }
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|