Ported the config system from old master.
This commit is contained in:
parent
56a818254e
commit
e1e3def62c
23 changed files with 814 additions and 438 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,6 +7,8 @@ login_key.txt
|
||||||
http-motd.txt
|
http-motd.txt
|
||||||
_webdb.txt
|
_webdb.txt
|
||||||
msz_url.txt
|
msz_url.txt
|
||||||
|
sharpchat.cfg
|
||||||
|
SharpChat/version.txt
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.suo
|
*.suo
|
||||||
|
|
|
@ -31,13 +31,13 @@ namespace SharpChat {
|
||||||
UserLeave(user.Channel, user, reason);
|
UserLeave(user.Channel, user, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleJoin(ChatUser user, ChatChannel chan, ChatUserSession sess) {
|
public void HandleJoin(ChatUser user, ChatChannel chan, ChatUserSession sess, int maxMsgLength) {
|
||||||
if(!chan.HasUser(user)) {
|
if(!chan.HasUser(user)) {
|
||||||
chan.Send(new UserConnectPacket(DateTimeOffset.Now, user));
|
chan.Send(new UserConnectPacket(DateTimeOffset.Now, user));
|
||||||
Events.Add(new UserConnectEvent(DateTimeOffset.Now, user, chan));
|
Events.Add(new UserConnectEvent(DateTimeOffset.Now, user, chan));
|
||||||
}
|
}
|
||||||
|
|
||||||
sess.Send(new AuthSuccessPacket(user, chan, sess));
|
sess.Send(new AuthSuccessPacket(user, chan, sess, maxMsgLength));
|
||||||
sess.Send(new ContextUsersPacket(chan.GetUsers(new[] { user })));
|
sess.Send(new ContextUsersPacket(chan.GetUsers(new[] { user })));
|
||||||
|
|
||||||
IEnumerable<IChatEvent> msgs = Events.GetTargetLog(chan);
|
IEnumerable<IChatEvent> msgs = Events.GetTargetLog(chan);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using SharpChat.Flashii;
|
using SharpChat.Misuzu;
|
||||||
using SharpChat.Packet;
|
using SharpChat.Packet;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -120,12 +120,12 @@ namespace SharpChat {
|
||||||
public ChatUser() {
|
public ChatUser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChatUser(FlashiiAuthInfo auth) {
|
public ChatUser(MisuzuAuthInfo auth) {
|
||||||
UserId = auth.UserId;
|
UserId = auth.UserId;
|
||||||
ApplyAuth(auth, true);
|
ApplyAuth(auth, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyAuth(FlashiiAuthInfo auth, bool invalidateRestrictions = false) {
|
public void ApplyAuth(MisuzuAuthInfo auth, bool invalidateRestrictions = false) {
|
||||||
Username = auth.Username;
|
Username = auth.Username;
|
||||||
|
|
||||||
if(Status == ChatUserStatus.Offline)
|
if(Status == ChatUserStatus.Offline)
|
||||||
|
|
49
SharpChat/Config/CachedValue.cs
Normal file
49
SharpChat/Config/CachedValue.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharpChat.Config {
|
||||||
|
public class CachedValue<T> {
|
||||||
|
private IConfig Config { get; }
|
||||||
|
private string Name { get; }
|
||||||
|
private TimeSpan Lifetime { get; }
|
||||||
|
private T Fallback { get; }
|
||||||
|
private object Sync { get; } = new();
|
||||||
|
|
||||||
|
private object CurrentValue { get; set; }
|
||||||
|
private DateTimeOffset LastRead { get; set; }
|
||||||
|
|
||||||
|
public T Value {
|
||||||
|
get {
|
||||||
|
lock(Sync) {
|
||||||
|
DateTimeOffset now = DateTimeOffset.Now;
|
||||||
|
if((now - LastRead) >= Lifetime) {
|
||||||
|
LastRead = now;
|
||||||
|
CurrentValue = Config.ReadValue(Name, Fallback);
|
||||||
|
Logger.Debug($"Read {Name} ({CurrentValue})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)CurrentValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator T(CachedValue<T> val) => val.Value;
|
||||||
|
|
||||||
|
public CachedValue(IConfig config, string name, TimeSpan lifetime, T fallback) {
|
||||||
|
Config = config ?? throw new ArgumentNullException(nameof(config));
|
||||||
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
Lifetime = lifetime;
|
||||||
|
Fallback = fallback;
|
||||||
|
if(string.IsNullOrWhiteSpace(name))
|
||||||
|
throw new ArgumentException("Name cannot be empty.", nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh() {
|
||||||
|
lock(Sync) {
|
||||||
|
LastRead = DateTimeOffset.MinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return Value.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
SharpChat/Config/ConfigExceptions.cs
Normal file
16
SharpChat/Config/ConfigExceptions.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharpChat.Config {
|
||||||
|
public abstract class ConfigException : Exception {
|
||||||
|
public ConfigException(string message) : base(message) { }
|
||||||
|
public ConfigException(string message, Exception ex) : base(message, ex) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConfigLockException : ConfigException {
|
||||||
|
public ConfigLockException() : base("Unable to acquire lock for reading configuration.") { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConfigTypeException : ConfigException {
|
||||||
|
public ConfigTypeException(Exception ex) : base("Given type does not match the value in the configuration.", ex) { }
|
||||||
|
}
|
||||||
|
}
|
35
SharpChat/Config/IConfig.cs
Normal file
35
SharpChat/Config/IConfig.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharpChat.Config {
|
||||||
|
public interface IConfig : IDisposable {
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a proxy object that forces all names to start with the given prefix.
|
||||||
|
/// </summary>
|
||||||
|
IConfig ScopeTo(string prefix);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a raw (string) value from the config.
|
||||||
|
/// </summary>
|
||||||
|
string ReadValue(string name, string fallback = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads and casts value from the config.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ConfigTypeException">Type conversion failed.</exception>
|
||||||
|
T ReadValue<T>(string name, T fallback = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads and casts a value from the config. Returns fallback when type conversion fails.
|
||||||
|
/// </summary>
|
||||||
|
T SafeReadValue<T>(string name, T fallback);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an object that caches the read value for a certain amount of time, avoiding disk reads for frequently used non-static values.
|
||||||
|
/// </summary>
|
||||||
|
CachedValue<T> ReadCached<T>(string name, T fallback = default, TimeSpan? lifetime = null);
|
||||||
|
}
|
||||||
|
}
|
45
SharpChat/Config/ScopedConfig.cs
Normal file
45
SharpChat/Config/ScopedConfig.cs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharpChat.Config {
|
||||||
|
public class ScopedConfig : IConfig {
|
||||||
|
private IConfig Config { get; }
|
||||||
|
private string Prefix { get; }
|
||||||
|
|
||||||
|
public ScopedConfig(IConfig config, string prefix) {
|
||||||
|
Config = config ?? throw new ArgumentNullException(nameof(config));
|
||||||
|
Prefix = prefix ?? throw new ArgumentNullException(nameof(prefix));
|
||||||
|
if(string.IsNullOrWhiteSpace(prefix))
|
||||||
|
throw new ArgumentException("Prefix must exist.", nameof(prefix));
|
||||||
|
if(Prefix[^1] != ':')
|
||||||
|
Prefix += ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetName(string name) {
|
||||||
|
return Prefix + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadValue(string name, string fallback = null) {
|
||||||
|
return Config.ReadValue(GetName(name), fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T ReadValue<T>(string name, T fallback = default) {
|
||||||
|
return Config.ReadValue(GetName(name), fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T SafeReadValue<T>(string name, T fallback) {
|
||||||
|
return Config.SafeReadValue(GetName(name), fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfig ScopeTo(string prefix) {
|
||||||
|
return Config.ScopeTo(GetName(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CachedValue<T> ReadCached<T>(string name, T fallback = default, TimeSpan? lifetime = null) {
|
||||||
|
return Config.ReadCached(GetName(name), fallback, lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
112
SharpChat/Config/StreamConfig.cs
Normal file
112
SharpChat/Config/StreamConfig.cs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace SharpChat.Config {
|
||||||
|
public class StreamConfig : IConfig {
|
||||||
|
private Stream Stream { get; }
|
||||||
|
private StreamReader StreamReader { get; }
|
||||||
|
private Mutex Lock { get; }
|
||||||
|
|
||||||
|
private const int LOCK_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
private static readonly TimeSpan CACHE_LIFETIME = TimeSpan.FromMinutes(15);
|
||||||
|
|
||||||
|
public StreamConfig(string fileName)
|
||||||
|
: this(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite)) { }
|
||||||
|
|
||||||
|
public StreamConfig(Stream stream) {
|
||||||
|
Stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||||
|
if(!Stream.CanRead)
|
||||||
|
throw new ArgumentException("Provided stream must be readable.", nameof(stream));
|
||||||
|
if(!Stream.CanSeek)
|
||||||
|
throw new ArgumentException("Provided stream must be seekable.", nameof(stream));
|
||||||
|
StreamReader = new StreamReader(stream, new UTF8Encoding(false), false);
|
||||||
|
Lock = new Mutex();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadValue(string name, string fallback = null) {
|
||||||
|
if(!Lock.WaitOne(LOCK_TIMEOUT)) // don't catch this, if this happens something is Very Wrong
|
||||||
|
throw new ConfigLockException();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
string line;
|
||||||
|
while((line = StreamReader.ReadLine()) != null) {
|
||||||
|
if(string.IsNullOrWhiteSpace(line))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
line = line.TrimStart();
|
||||||
|
if(line.StartsWith(";") || line.StartsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string[] parts = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if(parts.Length < 2 || !string.Equals(parts[0], name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return parts[1];
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Lock.ReleaseMutex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T ReadValue<T>(string name, T fallback = default) {
|
||||||
|
object value = ReadValue(name);
|
||||||
|
if(value == null)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
Type type = typeof(T);
|
||||||
|
if(value is string strVal) {
|
||||||
|
if(type == typeof(bool))
|
||||||
|
value = !string.Equals(strVal, "0", StringComparison.InvariantCultureIgnoreCase)
|
||||||
|
&& !string.Equals(strVal, "false", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
else if(type == typeof(string[]))
|
||||||
|
value = strVal.Split(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (T)Convert.ChangeType(value, type);
|
||||||
|
} catch(InvalidCastException ex) {
|
||||||
|
throw new ConfigTypeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T SafeReadValue<T>(string name, T fallback) {
|
||||||
|
try {
|
||||||
|
return ReadValue(name, fallback);
|
||||||
|
} catch(ConfigTypeException) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfig ScopeTo(string prefix) {
|
||||||
|
return new ScopedConfig(this, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CachedValue<T> ReadCached<T>(string name, T fallback = default, TimeSpan? lifetime = null) {
|
||||||
|
return new CachedValue<T>(this, name, lifetime ?? CACHE_LIFETIME, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDisposed;
|
||||||
|
~StreamConfig()
|
||||||
|
=> DoDispose();
|
||||||
|
public void Dispose() {
|
||||||
|
DoDispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
private void DoDispose() {
|
||||||
|
if(IsDisposed)
|
||||||
|
return;
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
StreamReader.Dispose();
|
||||||
|
Stream.Dispose();
|
||||||
|
Lock.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
using MySqlConnector;
|
using MySqlConnector;
|
||||||
|
using SharpChat.Config;
|
||||||
using SharpChat.Events;
|
using SharpChat.Events;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
|
@ -13,13 +13,13 @@ namespace SharpChat {
|
||||||
public static bool HasDatabase
|
public static bool HasDatabase
|
||||||
=> !string.IsNullOrWhiteSpace(ConnectionString);
|
=> !string.IsNullOrWhiteSpace(ConnectionString);
|
||||||
|
|
||||||
public static void ReadConfig() {
|
public static void Init(IConfig config) {
|
||||||
if(!File.Exists("mariadb.txt"))
|
Init(
|
||||||
return;
|
config.ReadValue("host", "localhost"),
|
||||||
string[] config = File.ReadAllLines("mariadb.txt");
|
config.ReadValue("user", string.Empty),
|
||||||
if(config.Length < 4)
|
config.ReadValue("pass", string.Empty),
|
||||||
return;
|
config.ReadValue("db", "sharpchat")
|
||||||
Init(config[0], config[1], config[2], config[3]);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Init(string host, string username, string password, string database) {
|
public static void Init(string host, string username, string password, string database) {
|
||||||
|
|
|
@ -1,28 +1,7 @@
|
||||||
using System.IO;
|
using System.Text;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
public static class Extensions {
|
public static class Extensions {
|
||||||
public static string GetSignedHash(this string str, string key = null) {
|
|
||||||
return Encoding.UTF8.GetBytes(str).GetSignedHash(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetSignedHash(this byte[] bytes, string key = null) {
|
|
||||||
key ??= File.Exists("login_key.txt") ? File.ReadAllText("login_key.txt") : "woomy";
|
|
||||||
|
|
||||||
StringBuilder sb = new();
|
|
||||||
|
|
||||||
using(HMACSHA256 algo = new(Encoding.UTF8.GetBytes(key))) {
|
|
||||||
byte[] hash = algo.ComputeHash(bytes);
|
|
||||||
|
|
||||||
foreach(byte b in hash)
|
|
||||||
sb.AppendFormat("{0:x2}", b);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetIdString(this byte[] buffer) {
|
public static string GetIdString(this byte[] buffer) {
|
||||||
const string id_chars = "abcdefghijklmnopqrstuvwxyz0123456789-_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
const string id_chars = "abcdefghijklmnopqrstuvwxyz0123456789-_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharpChat.Flashii {
|
|
||||||
public class FlashiiAuthInfo {
|
|
||||||
[JsonPropertyName("success")]
|
|
||||||
public bool Success { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("reason")]
|
|
||||||
public string Reason { get; set; } = "none";
|
|
||||||
|
|
||||||
[JsonPropertyName("user_id")]
|
|
||||||
public long UserId { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("username")]
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("colour_raw")]
|
|
||||||
public int ColourRaw { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("hierarchy")]
|
|
||||||
public int Rank { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("is_silenced")]
|
|
||||||
public DateTimeOffset SilencedUntil { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("perms")]
|
|
||||||
public ChatUserPermissions Permissions { get; set; }
|
|
||||||
|
|
||||||
private const string SIG_FMT = "verify#{0}#{1}#{2}";
|
|
||||||
|
|
||||||
public static async Task<FlashiiAuthInfo> VerifyAsync(HttpClient client, string method, string token, string ipAddr) {
|
|
||||||
if(client == null)
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
|
|
||||||
method ??= string.Empty;
|
|
||||||
token ??= string.Empty;
|
|
||||||
ipAddr ??= string.Empty;
|
|
||||||
|
|
||||||
string sig = string.Format(SIG_FMT, method, token, ipAddr);
|
|
||||||
|
|
||||||
HttpRequestMessage req = new(HttpMethod.Post, FlashiiUrls.VerifyURL) {
|
|
||||||
Content = new FormUrlEncodedContent(new Dictionary<string, string> {
|
|
||||||
{ "method", method },
|
|
||||||
{ "token", token },
|
|
||||||
{ "ipaddr", ipAddr },
|
|
||||||
}),
|
|
||||||
Headers = {
|
|
||||||
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponseMessage res = await client.SendAsync(req);
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<FlashiiAuthInfo>(
|
|
||||||
await res.Content.ReadAsByteArrayAsync()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,183 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharpChat.Flashii {
|
|
||||||
public class FlashiiBanInfo {
|
|
||||||
[JsonPropertyName("is_ban")]
|
|
||||||
public bool IsBanned { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("user_id")]
|
|
||||||
public string UserId { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("ip_addr")]
|
|
||||||
public string RemoteAddress { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("is_perma")]
|
|
||||||
public bool IsPermanent { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("expires")]
|
|
||||||
public DateTimeOffset ExpiresAt { get; set; }
|
|
||||||
|
|
||||||
// only populated in list request
|
|
||||||
[JsonPropertyName("user_name")]
|
|
||||||
public string UserName { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("user_colour")]
|
|
||||||
public int UserColourRaw { get; set; }
|
|
||||||
|
|
||||||
public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt;
|
|
||||||
|
|
||||||
public ChatColour UserColour => ChatColour.FromMisuzu(UserColourRaw);
|
|
||||||
|
|
||||||
private const string CHECK_SIG_FMT = "check#{0}#{1}#{2}#{3}";
|
|
||||||
private const string REVOKE_SIG_FMT = "revoke#{0}#{1}#{2}";
|
|
||||||
private const string CREATE_SIG_FMT = "create#{0}#{1}#{2}#{3}#{4}#{5}#{6}#{7}";
|
|
||||||
private const string LIST_SIG_FMT = "list#{0}";
|
|
||||||
|
|
||||||
public static async Task<FlashiiBanInfo> CheckAsync(HttpClient client, string userId = null, string ipAddr = null, bool userIdIsName = false) {
|
|
||||||
if(client == null)
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
|
|
||||||
userId ??= string.Empty;
|
|
||||||
ipAddr ??= string.Empty;
|
|
||||||
|
|
||||||
string userIdIsNameStr = userIdIsName ? "1" : "0";
|
|
||||||
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
|
||||||
string url = string.Format(FlashiiUrls.BansCheckURL, Uri.EscapeDataString(userId), Uri.EscapeDataString(ipAddr), Uri.EscapeDataString(now), Uri.EscapeDataString(userIdIsNameStr));
|
|
||||||
string sig = string.Format(CHECK_SIG_FMT, now, userId, ipAddr, userIdIsNameStr);
|
|
||||||
|
|
||||||
HttpRequestMessage req = new(HttpMethod.Get, url) {
|
|
||||||
Headers = {
|
|
||||||
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponseMessage res = await client.SendAsync(req);
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<FlashiiBanInfo>(
|
|
||||||
await res.Content.ReadAsByteArrayAsync()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<FlashiiBanInfo[]> GetListAsync(HttpClient client) {
|
|
||||||
if(client == null)
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
|
|
||||||
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
|
||||||
string url = string.Format(FlashiiUrls.BansListURL, Uri.EscapeDataString(now));
|
|
||||||
string sig = string.Format(LIST_SIG_FMT, now);
|
|
||||||
|
|
||||||
HttpRequestMessage req = new(HttpMethod.Get, url) {
|
|
||||||
Headers = {
|
|
||||||
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponseMessage res = await client.SendAsync(req);
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<FlashiiBanInfo[]>(
|
|
||||||
await res.Content.ReadAsByteArrayAsync()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RevokeKind {
|
|
||||||
UserId,
|
|
||||||
RemoteAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> RevokeAsync(HttpClient client, RevokeKind kind) {
|
|
||||||
if(client == null)
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
|
|
||||||
string type = kind switch {
|
|
||||||
RevokeKind.UserId => "user",
|
|
||||||
RevokeKind.RemoteAddress => "addr",
|
|
||||||
_ => throw new ArgumentException("Invalid kind specified.", nameof(kind)),
|
|
||||||
};
|
|
||||||
|
|
||||||
string target = kind switch {
|
|
||||||
RevokeKind.UserId => UserId,
|
|
||||||
RevokeKind.RemoteAddress => RemoteAddress,
|
|
||||||
_ => string.Empty,
|
|
||||||
};
|
|
||||||
|
|
||||||
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
|
||||||
string url = string.Format(FlashiiUrls.BansRevokeURL, Uri.EscapeDataString(type), Uri.EscapeDataString(target), Uri.EscapeDataString(now));
|
|
||||||
string sig = string.Format(REVOKE_SIG_FMT, now, type, target);
|
|
||||||
|
|
||||||
HttpRequestMessage req = new(HttpMethod.Delete, url) {
|
|
||||||
Headers = {
|
|
||||||
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponseMessage res = await client.SendAsync(req);
|
|
||||||
|
|
||||||
if(res.StatusCode == HttpStatusCode.NotFound)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
res.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
return res.StatusCode == HttpStatusCode.NoContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task CreateAsync(
|
|
||||||
HttpClient client,
|
|
||||||
string targetId,
|
|
||||||
string targetAddr,
|
|
||||||
string modId,
|
|
||||||
string modAddr,
|
|
||||||
TimeSpan duration,
|
|
||||||
string reason
|
|
||||||
) {
|
|
||||||
if(client == null)
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
if(string.IsNullOrWhiteSpace(targetAddr))
|
|
||||||
throw new ArgumentNullException(nameof(targetAddr));
|
|
||||||
if(string.IsNullOrWhiteSpace(modAddr))
|
|
||||||
throw new ArgumentNullException(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(
|
|
||||||
CREATE_SIG_FMT,
|
|
||||||
now, targetId, targetAddr,
|
|
||||||
modId, modAddr,
|
|
||||||
durationStr, isPerma, reason
|
|
||||||
);
|
|
||||||
|
|
||||||
HttpRequestMessage req = new(HttpMethod.Post, FlashiiUrls.BansCreateURL) {
|
|
||||||
Headers = {
|
|
||||||
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
|
||||||
},
|
|
||||||
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 client.SendAsync(req);
|
|
||||||
|
|
||||||
res.EnsureSuccessStatusCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharpChat.Flashii {
|
|
||||||
public static class FlashiiUrls {
|
|
||||||
private const string BASE_URL_FILE = "msz_url.txt";
|
|
||||||
private const string BASE_URL_FALLBACK = "https://flashii.net";
|
|
||||||
|
|
||||||
private const string BUMP = "/_sockchat/bump";
|
|
||||||
private const string VERIFY = "/_sockchat/verify";
|
|
||||||
|
|
||||||
private const string BANS_CHECK = "/_sockchat/bans/check?u={0}&a={1}&x={2}&n={3}";
|
|
||||||
private const string BANS_CREATE = "/_sockchat/bans/create";
|
|
||||||
private const string BANS_REVOKE = "/_sockchat/bans/revoke?t={0}&s={1}&x={2}";
|
|
||||||
private const string BANS_LIST = "/_sockchat/bans/list?x={0}";
|
|
||||||
|
|
||||||
public static string BumpURL { get; }
|
|
||||||
public static string VerifyURL { get; }
|
|
||||||
|
|
||||||
public static string BansCheckURL { get; }
|
|
||||||
public static string BansCreateURL { get; }
|
|
||||||
public static string BansRevokeURL { get; }
|
|
||||||
public static string BansListURL { get; }
|
|
||||||
|
|
||||||
static FlashiiUrls() {
|
|
||||||
BumpURL = GetURL(BUMP);
|
|
||||||
VerifyURL = GetURL(VERIFY);
|
|
||||||
BansCheckURL = GetURL(BANS_CHECK);
|
|
||||||
BansCreateURL = GetURL(BANS_CREATE);
|
|
||||||
BansRevokeURL = GetURL(BANS_REVOKE);
|
|
||||||
BansListURL = GetURL(BANS_LIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetBaseURL() {
|
|
||||||
if(!File.Exists(BASE_URL_FILE))
|
|
||||||
return BASE_URL_FALLBACK;
|
|
||||||
string url = File.ReadAllText(BASE_URL_FILE).Trim().Trim('/');
|
|
||||||
if(string.IsNullOrEmpty(url))
|
|
||||||
return BASE_URL_FALLBACK;
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetURL(string path) {
|
|
||||||
return GetBaseURL() + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task BumpUsersOnlineAsync(HttpClient client, IEnumerable<(string userId, string ipAddr)> list) {
|
|
||||||
if(client == null)
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
if(list == null)
|
|
||||||
throw new ArgumentNullException(nameof(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, BumpURL) {
|
|
||||||
Headers = {
|
|
||||||
{ "X-SharpChat-Signature", sb.ToString().GetSignedHash() }
|
|
||||||
},
|
|
||||||
Content = new FormUrlEncodedContent(formData),
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponseMessage res = await client.SendAsync(req);
|
|
||||||
|
|
||||||
try {
|
|
||||||
res.EnsureSuccessStatusCode();
|
|
||||||
} catch(HttpRequestException) {
|
|
||||||
Logger.Debug(await res.Content.ReadAsStringAsync());
|
|
||||||
#if DEBUG
|
|
||||||
throw;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
30
SharpChat/Misuzu/MisuzuAuthInfo.cs
Normal file
30
SharpChat/Misuzu/MisuzuAuthInfo.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SharpChat.Misuzu {
|
||||||
|
public class MisuzuAuthInfo {
|
||||||
|
[JsonPropertyName("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("reason")]
|
||||||
|
public string Reason { get; set; } = "none";
|
||||||
|
|
||||||
|
[JsonPropertyName("user_id")]
|
||||||
|
public long UserId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("username")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("colour_raw")]
|
||||||
|
public int ColourRaw { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("hierarchy")]
|
||||||
|
public int Rank { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_silenced")]
|
||||||
|
public DateTimeOffset SilencedUntil { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("perms")]
|
||||||
|
public ChatUserPermissions Permissions { get; set; }
|
||||||
|
}
|
||||||
|
}
|
32
SharpChat/Misuzu/MisuzuBanInfo.cs
Normal file
32
SharpChat/Misuzu/MisuzuBanInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SharpChat.Misuzu {
|
||||||
|
public class MisuzuBanInfo {
|
||||||
|
[JsonPropertyName("is_ban")]
|
||||||
|
public bool IsBanned { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("user_id")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ip_addr")]
|
||||||
|
public string RemoteAddress { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_perma")]
|
||||||
|
public bool IsPermanent { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("expires")]
|
||||||
|
public DateTimeOffset ExpiresAt { get; set; }
|
||||||
|
|
||||||
|
// only populated in list request
|
||||||
|
[JsonPropertyName("user_name")]
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("user_colour")]
|
||||||
|
public int UserColourRaw { get; set; }
|
||||||
|
|
||||||
|
public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt;
|
||||||
|
|
||||||
|
public ChatColour UserColour => ChatColour.FromMisuzu(UserColourRaw);
|
||||||
|
}
|
||||||
|
}
|
249
SharpChat/Misuzu/MisuzuClient.cs
Normal file
249
SharpChat/Misuzu/MisuzuClient.cs
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
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) {
|
||||||
|
if(config == null)
|
||||||
|
throw new ArgumentNullException(nameof(config));
|
||||||
|
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(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));
|
||||||
|
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 == null)
|
||||||
|
throw new ArgumentNullException(nameof(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) {
|
||||||
|
if(banInfo == null)
|
||||||
|
throw new ArgumentNullException(nameof(banInfo));
|
||||||
|
|
||||||
|
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,
|
||||||
|
BanRevokeKind.RemoteAddress => banInfo.RemoteAddress,
|
||||||
|
_ => 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 ArgumentNullException(nameof(targetAddr));
|
||||||
|
if(string.IsNullOrWhiteSpace(modAddr))
|
||||||
|
throw new ArgumentNullException(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
using SharpChat.Flashii;
|
using SharpChat.Misuzu;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -12,9 +12,9 @@ namespace SharpChat.Packet {
|
||||||
|
|
||||||
public class AuthFailPacket : ServerPacket {
|
public class AuthFailPacket : ServerPacket {
|
||||||
public AuthFailReason Reason { get; private set; }
|
public AuthFailReason Reason { get; private set; }
|
||||||
public FlashiiBanInfo BanInfo { get; private set; }
|
public MisuzuBanInfo BanInfo { get; private set; }
|
||||||
|
|
||||||
public AuthFailPacket(AuthFailReason reason, FlashiiBanInfo fbi = null) {
|
public AuthFailPacket(AuthFailReason reason, MisuzuBanInfo fbi = null) {
|
||||||
Reason = reason;
|
Reason = reason;
|
||||||
|
|
||||||
if(reason == AuthFailReason.Banned)
|
if(reason == AuthFailReason.Banned)
|
||||||
|
|
|
@ -7,11 +7,18 @@ namespace SharpChat.Packet {
|
||||||
public ChatUser User { get; private set; }
|
public ChatUser User { get; private set; }
|
||||||
public ChatChannel Channel { get; private set; }
|
public ChatChannel Channel { get; private set; }
|
||||||
public ChatUserSession Session { get; private set; }
|
public ChatUserSession Session { get; private set; }
|
||||||
|
public int MaxMessageLength { get; private set; }
|
||||||
|
|
||||||
public AuthSuccessPacket(ChatUser user, ChatChannel channel, ChatUserSession sess) {
|
public AuthSuccessPacket(
|
||||||
|
ChatUser user,
|
||||||
|
ChatChannel channel,
|
||||||
|
ChatUserSession sess,
|
||||||
|
int maxMsgLength
|
||||||
|
) {
|
||||||
User = user ?? throw new ArgumentNullException(nameof(user));
|
User = user ?? throw new ArgumentNullException(nameof(user));
|
||||||
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||||
Session = sess ?? throw new ArgumentNullException(nameof(channel));
|
Session = sess ?? throw new ArgumentNullException(nameof(channel));
|
||||||
|
MaxMessageLength = maxMsgLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<string> Pack() {
|
public override IEnumerable<string> Pack() {
|
||||||
|
@ -23,7 +30,7 @@ namespace SharpChat.Packet {
|
||||||
sb.Append('\t');
|
sb.Append('\t');
|
||||||
sb.Append(Channel.Name);
|
sb.Append(Channel.Name);
|
||||||
sb.Append('\t');
|
sb.Append('\t');
|
||||||
sb.Append(SockChatServer.MSG_LENGTH_MAX);
|
sb.Append(MaxMessageLength);
|
||||||
|
|
||||||
return new[] { sb.ToString() };
|
return new[] { sb.ToString() };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using SharpChat.Flashii;
|
using SharpChat.Misuzu;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -6,9 +6,9 @@ using System.Text;
|
||||||
|
|
||||||
namespace SharpChat.Packet {
|
namespace SharpChat.Packet {
|
||||||
public class BanListPacket : ServerPacket {
|
public class BanListPacket : ServerPacket {
|
||||||
public IEnumerable<FlashiiBanInfo> Bans { get; private set; }
|
public IEnumerable<MisuzuBanInfo> Bans { get; private set; }
|
||||||
|
|
||||||
public BanListPacket(IEnumerable<FlashiiBanInfo> bans) {
|
public BanListPacket(IEnumerable<MisuzuBanInfo> bans) {
|
||||||
Bans = bans ?? throw new ArgumentNullException(nameof(bans));
|
Bans = bans ?? throw new ArgumentNullException(nameof(bans));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ namespace SharpChat.Packet {
|
||||||
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
|
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||||
sb.Append("\t-1\t0\fbanlist\f");
|
sb.Append("\t-1\t0\fbanlist\f");
|
||||||
|
|
||||||
foreach(FlashiiBanInfo ban in Bans) {
|
foreach(MisuzuBanInfo ban in Bans) {
|
||||||
string banStr = string.IsNullOrEmpty(ban.UserName) ? ban.RemoteAddress : ban.UserName;
|
string banStr = string.IsNullOrEmpty(ban.UserName) ? ban.RemoteAddress : ban.UserName;
|
||||||
sb.AppendFormat(@"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('/unban '+ this.innerHTML);"">{0}</a>, ", banStr);
|
sb.AppendFormat(@"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('/unban '+ this.innerHTML);"">{0}</a>, ", banStr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
using System;
|
using SharpChat.Config;
|
||||||
|
using SharpChat.Misuzu;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
public class Program {
|
public class Program {
|
||||||
public const ushort PORT = 6770;
|
public const string CONFIG = "sharpchat.cfg";
|
||||||
|
|
||||||
public static void Main(string[] args) {
|
public static void Main(string[] args) {
|
||||||
Console.WriteLine(@" _____ __ ________ __ ");
|
Console.WriteLine(@" _____ __ ________ __ ");
|
||||||
|
@ -12,12 +16,14 @@ namespace SharpChat {
|
||||||
Console.WriteLine(@" \__ \/ __ \/ __ `/ ___/ __ \/ / / __ \/ __ `/ __/");
|
Console.WriteLine(@" \__ \/ __ \/ __ `/ ___/ __ \/ / / __ \/ __ `/ __/");
|
||||||
Console.WriteLine(@" ___/ / / / / /_/ / / / /_/ / /___/ / / / /_/ / /_ ");
|
Console.WriteLine(@" ___/ / / / / /_/ / / / /_/ / /___/ / / / /_/ / /_ ");
|
||||||
Console.WriteLine(@"/____/_/ /_/\__,_/_/ / .___/\____/_/ /_/\__,_/\__/ ");
|
Console.WriteLine(@"/____/_/ /_/\__,_/_/ / .___/\____/_/ /_/\__,_/\__/ ");
|
||||||
Console.WriteLine(@" / _/ Sock Chat Server");
|
/**/Console.Write(@" /__/");
|
||||||
#if DEBUG
|
if(SharpInfo.IsDebugBuild) {
|
||||||
Console.WriteLine(@"============================================ DEBUG ==");
|
Console.WriteLine();
|
||||||
#endif
|
Console.Write(@"== ");
|
||||||
|
Console.Write(SharpInfo.VersionString);
|
||||||
Database.ReadConfig();
|
Console.WriteLine(@" == DBG ==");
|
||||||
|
} else
|
||||||
|
Console.WriteLine(SharpInfo.VersionStringShort.PadLeft(28, ' '));
|
||||||
|
|
||||||
using ManualResetEvent mre = new(false);
|
using ManualResetEvent mre = new(false);
|
||||||
bool hasCancelled = false;
|
bool hasCancelled = false;
|
||||||
|
@ -32,17 +38,105 @@ namespace SharpChat {
|
||||||
|
|
||||||
if(hasCancelled) return;
|
if(hasCancelled) return;
|
||||||
|
|
||||||
using HttpClient httpClient = new(new HttpClientHandler() {
|
string configFile = CONFIG;
|
||||||
UseProxy = false, // we will never and the initial resolving takes forever on linux
|
|
||||||
});
|
// If the config file doesn't exist and we're using the default path, run the converter
|
||||||
httpClient.DefaultRequestHeaders.Add("User-Agent", "SharpChat/20230206");
|
if(!File.Exists(configFile) && configFile == CONFIG)
|
||||||
|
ConvertConfiguration();
|
||||||
|
|
||||||
|
using IConfig config = new StreamConfig(configFile);
|
||||||
|
|
||||||
|
Database.Init(config.ScopeTo("mariadb"));
|
||||||
|
|
||||||
if(hasCancelled) return;
|
if(hasCancelled) return;
|
||||||
|
|
||||||
using SockChatServer scs = new(httpClient, PORT);
|
using HttpClient httpClient = new(new HttpClientHandler() {
|
||||||
|
UseProxy = false,
|
||||||
|
});
|
||||||
|
httpClient.DefaultRequestHeaders.Add("User-Agent", SharpInfo.ProgramName);
|
||||||
|
|
||||||
|
if(hasCancelled) return;
|
||||||
|
|
||||||
|
MisuzuClient msz = new(httpClient, config.ScopeTo("msz"));
|
||||||
|
|
||||||
|
if(hasCancelled) return;
|
||||||
|
|
||||||
|
using SockChatServer scs = new(httpClient, msz, config.ScopeTo("chat"));
|
||||||
scs.Listen(mre);
|
scs.Listen(mre);
|
||||||
|
|
||||||
mre.WaitOne();
|
mre.WaitOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ConvertConfiguration() {
|
||||||
|
using Stream s = new FileStream(CONFIG, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
||||||
|
s.SetLength(0);
|
||||||
|
s.Flush();
|
||||||
|
|
||||||
|
using StreamWriter sw = new(s, new UTF8Encoding(false));
|
||||||
|
sw.WriteLine("# and ; can be used at the start of a line for comments.");
|
||||||
|
sw.WriteLine();
|
||||||
|
|
||||||
|
sw.WriteLine("# General Configuration");
|
||||||
|
sw.WriteLine($"#chat:port {SockChatServer.DEFAULT_PORT}");
|
||||||
|
sw.WriteLine($"#chat:msgMaxLength {SockChatServer.DEFAULT_MSG_LENGTH_MAX}");
|
||||||
|
sw.WriteLine($"#chat:connMaxCount {SockChatServer.DEFAULT_MAX_CONNECTIONS}");
|
||||||
|
sw.WriteLine($"#chat:floodKickLength {SockChatServer.DEFAULT_FLOOD_KICK_LENGTH}");
|
||||||
|
|
||||||
|
|
||||||
|
sw.WriteLine();
|
||||||
|
sw.WriteLine("# Channels");
|
||||||
|
sw.WriteLine("chat:channels lounge staff");
|
||||||
|
sw.WriteLine();
|
||||||
|
|
||||||
|
sw.WriteLine("# Lounge channel settings");
|
||||||
|
sw.WriteLine("chat:channels:lounge:name Lounge");
|
||||||
|
sw.WriteLine("chat:channels:lounge:autoJoin true");
|
||||||
|
sw.WriteLine();
|
||||||
|
|
||||||
|
sw.WriteLine("# Staff channel settings");
|
||||||
|
sw.WriteLine("chat:channels:staff:name Staff");
|
||||||
|
sw.WriteLine("chat:channels:staff:minRank 5");
|
||||||
|
|
||||||
|
|
||||||
|
const string msz_secret = "login_key.txt";
|
||||||
|
const string msz_url = "msz_url.txt";
|
||||||
|
|
||||||
|
sw.WriteLine();
|
||||||
|
sw.WriteLine("# Misuzu integration settings");
|
||||||
|
if(File.Exists(msz_secret))
|
||||||
|
sw.WriteLine(string.Format("msz:secret {0}", File.ReadAllText(msz_secret).Trim()));
|
||||||
|
else
|
||||||
|
sw.WriteLine("#msz:secret woomy");
|
||||||
|
if(File.Exists(msz_url))
|
||||||
|
sw.WriteLine(string.Format("msz:url {0}/_sockchat", File.ReadAllText(msz_url).Trim()));
|
||||||
|
else
|
||||||
|
sw.WriteLine("#msz:url https://flashii.net/_sockchat");
|
||||||
|
|
||||||
|
|
||||||
|
const string mdb_config = @"mariadb.txt";
|
||||||
|
bool hasMDB = File.Exists(mdb_config);
|
||||||
|
string[] mdbCfg = File.Exists(mdb_config) ? File.ReadAllLines(mdb_config) : Array.Empty<string>();
|
||||||
|
|
||||||
|
sw.WriteLine();
|
||||||
|
sw.WriteLine("# MariaDB configuration");
|
||||||
|
if(!string.IsNullOrWhiteSpace(mdbCfg[0]))
|
||||||
|
sw.WriteLine($"mariadb:host {mdbCfg[0]}");
|
||||||
|
else
|
||||||
|
sw.WriteLine($"#mariadb:host <username>");
|
||||||
|
if(mdbCfg.Length > 1)
|
||||||
|
sw.WriteLine($"mariadb:user {mdbCfg[1]}");
|
||||||
|
else
|
||||||
|
sw.WriteLine($"#mariadb:user <username>");
|
||||||
|
if(mdbCfg.Length > 2)
|
||||||
|
sw.WriteLine($"mariadb:pass {mdbCfg[2]}");
|
||||||
|
else
|
||||||
|
sw.WriteLine($"#mariadb:pass <password>");
|
||||||
|
if(mdbCfg.Length > 3)
|
||||||
|
sw.WriteLine($"mariadb:db {mdbCfg[3]}");
|
||||||
|
else
|
||||||
|
sw.WriteLine($"#mariadb:db <database>");
|
||||||
|
|
||||||
|
sw.Flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,4 +10,16 @@
|
||||||
<PackageReference Include="MySqlConnector" Version="2.2.5" />
|
<PackageReference Include="MySqlConnector" Version="2.2.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||||
|
<Exec Command="git describe --tags --abbrev=0 --always > version.txt" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="version.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="version.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
37
SharpChat/SharpInfo.cs
Normal file
37
SharpChat/SharpInfo.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpChat {
|
||||||
|
public static class SharpInfo {
|
||||||
|
private const string NAME = @"SharpChat";
|
||||||
|
private const string UNKNOWN = @"XXXXXXX";
|
||||||
|
|
||||||
|
public static string VersionString { get; }
|
||||||
|
public static string VersionStringShort { get; }
|
||||||
|
public static bool IsDebugBuild { get; }
|
||||||
|
|
||||||
|
public static string ProgramName { get; }
|
||||||
|
|
||||||
|
static SharpInfo() {
|
||||||
|
#if DEBUG
|
||||||
|
IsDebugBuild = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
try {
|
||||||
|
using Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(@"SharpChat.version.txt");
|
||||||
|
using StreamReader sr = new(s);
|
||||||
|
VersionString = sr.ReadLine().Trim();
|
||||||
|
VersionStringShort = VersionString.Length > 10 ? VersionString[..10] : VersionString;
|
||||||
|
} catch {
|
||||||
|
VersionStringShort = VersionString = UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new();
|
||||||
|
sb.Append(NAME);
|
||||||
|
sb.Append('/');
|
||||||
|
sb.Append(VersionStringShort);
|
||||||
|
ProgramName = sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
using Fleck;
|
using Fleck;
|
||||||
using SharpChat.Commands;
|
using SharpChat.Commands;
|
||||||
|
using SharpChat.Config;
|
||||||
using SharpChat.Events;
|
using SharpChat.Events;
|
||||||
using SharpChat.Flashii;
|
using SharpChat.Misuzu;
|
||||||
using SharpChat.Packet;
|
using SharpChat.Packet;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -15,15 +16,10 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
public class SockChatServer : IDisposable {
|
public class SockChatServer : IDisposable {
|
||||||
public const int MSG_LENGTH_MAX = 5000;
|
public const ushort DEFAULT_PORT = 6770;
|
||||||
|
public const int DEFAULT_MSG_LENGTH_MAX = 5000;
|
||||||
#if DEBUG
|
public const int DEFAULT_MAX_CONNECTIONS = 5;
|
||||||
public const int MAX_CONNECTIONS = 9001;
|
public const int DEFAULT_FLOOD_KICK_LENGTH = 30;
|
||||||
public const int FLOOD_KICK_LENGTH = 5;
|
|
||||||
#else
|
|
||||||
public const int MAX_CONNECTIONS = 5;
|
|
||||||
public const int FLOOD_KICK_LENGTH = 30;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
@ -38,6 +34,11 @@ namespace SharpChat {
|
||||||
public ChatContext Context { get; }
|
public ChatContext Context { get; }
|
||||||
|
|
||||||
private readonly HttpClient HttpClient;
|
private readonly HttpClient HttpClient;
|
||||||
|
private readonly MisuzuClient Misuzu;
|
||||||
|
|
||||||
|
private readonly CachedValue<int> MaxMessageLength;
|
||||||
|
private readonly CachedValue<int> MaxConnections;
|
||||||
|
private readonly CachedValue<int> FloodKickLength;
|
||||||
|
|
||||||
private IReadOnlyCollection<IChatCommand> Commands { get; } = new IChatCommand[] {
|
private IReadOnlyCollection<IChatCommand> Commands { get; } = new IChatCommand[] {
|
||||||
new AFKCommand(),
|
new AFKCommand(),
|
||||||
|
@ -54,22 +55,36 @@ namespace SharpChat {
|
||||||
private ManualResetEvent Shutdown { get; set; }
|
private ManualResetEvent Shutdown { get; set; }
|
||||||
private bool IsShuttingDown = false;
|
private bool IsShuttingDown = false;
|
||||||
|
|
||||||
public SockChatServer(HttpClient httpClient, ushort port) {
|
public SockChatServer(HttpClient httpClient, MisuzuClient msz, IConfig config) {
|
||||||
Logger.Write("Starting Sock Chat server...");
|
Logger.Write("Initialising Sock Chat server...");
|
||||||
|
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||||
|
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
|
||||||
|
|
||||||
|
MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
|
||||||
|
MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
|
||||||
|
FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
|
||||||
|
|
||||||
Context = new ChatContext();
|
Context = new ChatContext();
|
||||||
|
|
||||||
Context.Channels.Add(new ChatChannel("Lounge"));
|
string[] channelNames = config.ReadValue("channels", new[] { "lounge" });
|
||||||
#if DEBUG
|
|
||||||
Context.Channels.Add(new ChatChannel("Programming"));
|
|
||||||
Context.Channels.Add(new ChatChannel("Games"));
|
|
||||||
Context.Channels.Add(new ChatChannel("Splatoon"));
|
|
||||||
Context.Channels.Add(new ChatChannel("Password") { Password = "meow", });
|
|
||||||
#endif
|
|
||||||
Context.Channels.Add(new ChatChannel("Staff") { Rank = 5 });
|
|
||||||
|
|
||||||
|
foreach(string channelName in channelNames) {
|
||||||
|
ChatChannel channelInfo = new(channelName);
|
||||||
|
IConfig channelCfg = config.ScopeTo($"channels:{channelName}");
|
||||||
|
|
||||||
|
string tmp;
|
||||||
|
tmp = channelCfg.SafeReadValue("name", string.Empty);
|
||||||
|
if(!string.IsNullOrWhiteSpace(tmp))
|
||||||
|
channelInfo.Name = tmp;
|
||||||
|
|
||||||
|
channelInfo.Password = channelCfg.SafeReadValue("password", string.Empty);
|
||||||
|
channelInfo.Rank = channelCfg.SafeReadValue("minRank", 0);
|
||||||
|
|
||||||
|
Context.Channels.Add(channelInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort port = config.SafeReadValue("port", DEFAULT_PORT);
|
||||||
Server = new SharpChatWebSocketServer($"ws://0.0.0.0:{port}");
|
Server = new SharpChatWebSocketServer($"ws://0.0.0.0:{port}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +102,8 @@ namespace SharpChat {
|
||||||
sock.OnError = err => OnError(sock, err);
|
sock.OnError = err => OnError(sock, err);
|
||||||
sock.OnMessage = msg => OnMessage(sock, msg);
|
sock.OnMessage = msg => OnMessage(sock, msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Logger.Write("Listening...");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnOpen(IWebSocketConnection conn) {
|
private void OnOpen(IWebSocketConnection conn) {
|
||||||
|
@ -148,10 +165,9 @@ namespace SharpChat {
|
||||||
|
|
||||||
if(sess.User.RateLimiter.State == ChatRateLimitState.Kick) {
|
if(sess.User.RateLimiter.State == ChatRateLimitState.Kick) {
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
TimeSpan duration = TimeSpan.FromSeconds(FLOOD_KICK_LENGTH);
|
TimeSpan duration = TimeSpan.FromSeconds(FloodKickLength);
|
||||||
|
|
||||||
await FlashiiBanInfo.CreateAsync(
|
await Misuzu.CreateBanAsync(
|
||||||
HttpClient,
|
|
||||||
sess.User.UserId.ToString(), sess.RemoteAddress.ToString(),
|
sess.User.UserId.ToString(), sess.RemoteAddress.ToString(),
|
||||||
string.Empty, "::1",
|
string.Empty, "::1",
|
||||||
duration,
|
duration,
|
||||||
|
@ -186,7 +202,7 @@ namespace SharpChat {
|
||||||
|
|
||||||
if(bumpList.Any())
|
if(bumpList.Any())
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
await FlashiiUrls.BumpUsersOnlineAsync(HttpClient, bumpList);
|
await Misuzu.BumpUsersOnlineAsync(bumpList);
|
||||||
}).Wait();
|
}).Wait();
|
||||||
|
|
||||||
LastBump = DateTimeOffset.UtcNow;
|
LastBump = DateTimeOffset.UtcNow;
|
||||||
|
@ -219,11 +235,11 @@ namespace SharpChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
FlashiiAuthInfo fai;
|
MisuzuAuthInfo fai;
|
||||||
string ipAddr = sess.RemoteAddress.ToString();
|
string ipAddr = sess.RemoteAddress.ToString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fai = await FlashiiAuthInfo.VerifyAsync(HttpClient, authMethod, authToken, ipAddr);
|
fai = await Misuzu.AuthVerifyAsync(authMethod, authToken, ipAddr);
|
||||||
} catch(Exception ex) {
|
} catch(Exception ex) {
|
||||||
Logger.Write($"<{sess.Id}> Failed to authenticate: {ex}");
|
Logger.Write($"<{sess.Id}> Failed to authenticate: {ex}");
|
||||||
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||||
|
@ -242,9 +258,9 @@ namespace SharpChat {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlashiiBanInfo fbi;
|
MisuzuBanInfo fbi;
|
||||||
try {
|
try {
|
||||||
fbi = await FlashiiBanInfo.CheckAsync(HttpClient, fai.UserId.ToString(), ipAddr);
|
fbi = await Misuzu.CheckBanAsync(fai.UserId.ToString(), ipAddr);
|
||||||
} catch(Exception ex) {
|
} catch(Exception ex) {
|
||||||
Logger.Write($"<{sess.Id}> Failed auth ban check: {ex}");
|
Logger.Write($"<{sess.Id}> Failed auth ban check: {ex}");
|
||||||
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||||
|
@ -273,7 +289,7 @@ namespace SharpChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce a maximum amount of connections per user
|
// Enforce a maximum amount of connections per user
|
||||||
if(aUser.SessionCount >= MAX_CONNECTIONS) {
|
if(aUser.SessionCount >= MaxConnections) {
|
||||||
sess.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
|
sess.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
|
||||||
sess.Dispose();
|
sess.Dispose();
|
||||||
return;
|
return;
|
||||||
|
@ -294,7 +310,7 @@ namespace SharpChat {
|
||||||
sess.Send(new LegacyCommandResponse(LCR.WELCOME, false, line));
|
sess.Send(new LegacyCommandResponse(LCR.WELCOME, false, line));
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.HandleJoin(aUser, Context.Channels.DefaultChannel, sess);
|
Context.HandleJoin(aUser, Context.Channels.DefaultChannel, sess, MaxMessageLength);
|
||||||
}).Wait();
|
}).Wait();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -327,8 +343,9 @@ namespace SharpChat {
|
||||||
mChannel.Send(new UserUpdatePacket(mUser));
|
mChannel.Send(new UserUpdatePacket(mUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(messageText.Length > MSG_LENGTH_MAX)
|
int maxMsgLength = MaxMessageLength;
|
||||||
messageText = messageText[..MSG_LENGTH_MAX];
|
if(messageText.Length > maxMsgLength)
|
||||||
|
messageText = messageText[..maxMsgLength];
|
||||||
|
|
||||||
messageText = messageText.Trim();
|
messageText = messageText.Trim();
|
||||||
|
|
||||||
|
@ -727,8 +744,8 @@ namespace SharpChat {
|
||||||
|
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
// obviously it makes no sense to only check for one ip address but that's current misuzu limitations
|
// obviously it makes no sense to only check for one ip address but that's current misuzu limitations
|
||||||
FlashiiBanInfo fbi = await FlashiiBanInfo.CheckAsync(
|
MisuzuBanInfo fbi = await Misuzu.CheckBanAsync(
|
||||||
HttpClient, banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString()
|
banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString()
|
||||||
);
|
);
|
||||||
|
|
||||||
if(fbi.IsBanned && !fbi.HasExpired) {
|
if(fbi.IsBanned && !fbi.HasExpired) {
|
||||||
|
@ -736,8 +753,7 @@ namespace SharpChat {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await FlashiiBanInfo.CreateAsync(
|
await Misuzu.CreateBanAsync(
|
||||||
HttpClient,
|
|
||||||
banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString(),
|
banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString(),
|
||||||
user.UserId.ToString(), sess.RemoteAddress.ToString(),
|
user.UserId.ToString(), sess.RemoteAddress.ToString(),
|
||||||
duration, banReason
|
duration, banReason
|
||||||
|
@ -770,14 +786,14 @@ namespace SharpChat {
|
||||||
unbanUserTarget = unbanUser.UserId.ToString();
|
unbanUserTarget = unbanUser.UserId.ToString();
|
||||||
|
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
FlashiiBanInfo banInfo = await FlashiiBanInfo.CheckAsync(HttpClient, unbanUserTarget, userIdIsName: unbanUserTargetIsName);
|
MisuzuBanInfo banInfo = await Misuzu.CheckBanAsync(unbanUserTarget, userIdIsName: unbanUserTargetIsName);
|
||||||
|
|
||||||
if(!banInfo.IsBanned || banInfo.HasExpired) {
|
if(!banInfo.IsBanned || banInfo.HasExpired) {
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));
|
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wasBanned = await banInfo.RevokeAsync(HttpClient, FlashiiBanInfo.RevokeKind.UserId);
|
bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.UserId);
|
||||||
if(wasBanned)
|
if(wasBanned)
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanUserTarget));
|
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanUserTarget));
|
||||||
else
|
else
|
||||||
|
@ -800,14 +816,14 @@ namespace SharpChat {
|
||||||
unbanAddrTarget = unbanAddr.ToString();
|
unbanAddrTarget = unbanAddr.ToString();
|
||||||
|
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
FlashiiBanInfo banInfo = await FlashiiBanInfo.CheckAsync(HttpClient, ipAddr: unbanAddrTarget);
|
MisuzuBanInfo banInfo = await Misuzu.CheckBanAsync(ipAddr: unbanAddrTarget);
|
||||||
|
|
||||||
if(!banInfo.IsBanned || banInfo.HasExpired) {
|
if(!banInfo.IsBanned || banInfo.HasExpired) {
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
|
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wasBanned = await banInfo.RevokeAsync(HttpClient, FlashiiBanInfo.RevokeKind.RemoteAddress);
|
bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.RemoteAddress);
|
||||||
if(wasBanned)
|
if(wasBanned)
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanAddrTarget));
|
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanAddrTarget));
|
||||||
else
|
else
|
||||||
|
@ -823,7 +839,7 @@ namespace SharpChat {
|
||||||
|
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
user.Send(new BanListPacket(
|
user.Send(new BanListPacket(
|
||||||
await FlashiiBanInfo.GetListAsync(HttpClient)
|
await Misuzu.GetBanListAsync()
|
||||||
));
|
));
|
||||||
}).Wait();
|
}).Wait();
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in a new issue