sharp-chat/SharpChat.DataProvider.Misuzu/Users/MisuzuUserClient.cs

180 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); }
);
}
}
}