2022-01-11 20:32:25 +01:00
using Discord ;
using Discord.WebSocket ;
using Fleck ;
using Newtonsoft.Json ;
using System.Diagnostics ;
2022-01-15 21:26:32 +01:00
using System.Runtime.Serialization ;
2022-01-11 20:32:25 +01:00
namespace MinecraftDiscordBot ;
2022-01-15 21:26:32 +01:00
public delegate Task < TResponse > HandleCommandDelegate < TResponse > ( SocketUserMessage message , string [ ] parameters , CancellationToken ct ) ;
public delegate Task HandleCommandDelegate ( SocketUserMessage message , string [ ] parameters , CancellationToken ct ) ;
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class CommandHandlerAttribute : Attribute {
public CommandHandlerAttribute ( string commandName ) = > CommandName = commandName ;
public string CommandName { get ; }
2022-01-16 15:58:35 +01:00
public string? HelpText { get ; init ; }
2022-01-15 21:26:32 +01:00
}
2022-01-16 15:58:35 +01:00
public class ConnectedComputer : CommandRouter , ITaskWaitSource {
protected readonly IWebSocketConnection _socket ;
public override string HelpTextPrefix = > "!" ;
public ConnectedComputer ( IWebSocketConnection socket ) : base ( ) {
socket . OnMessage = OnMessage ;
_socket = socket ;
_rs = new RefinedStorageService ( this ) ;
}
private void OnMessage ( string message ) {
if ( JsonConvert . DeserializeObject < ReplyMessage > ( message ) is not ReplyMessage msg ) return ;
IChunkWaiter ? waiter ;
lock ( _syncRoot ) {
if ( ! _waits . TryGetValue ( msg . AnswerId , out waiter ) ) {
Program . LogWarningAsync ( "Socket" , $"Invalid wait id '{msg.AnswerId}'!" ) ;
return ;
}
2022-01-11 20:32:25 +01:00
}
2022-01-16 15:58:35 +01:00
if ( ! msg . Success ) waiter . SetUnsuccessful ( ) ;
waiter . AddChunk ( msg . Chunk , msg . Total , msg . Result ) ;
if ( waiter . Finished | | waiter . IsCancellationRequested )
lock ( _syncRoot )
_waits . Remove ( waiter . ID ) ;
2022-01-11 20:32:25 +01:00
}
2022-01-15 21:26:32 +01:00
2022-01-16 15:58:35 +01:00
public Task Send ( string message ) = > _socket . Send ( message ) ;
public Task Send ( Message message ) = > Send ( JsonConvert . SerializeObject ( message ) ) ;
private readonly object _syncRoot = new ( ) ;
private readonly Dictionary < int , IChunkWaiter > _waits = new ( ) ;
private readonly Random _rnd = new ( ) ;
public IWebSocketConnectionInfo ConnectionInfo = > _socket . ConnectionInfo ;
private int GetFreeId ( ) {
var attempts = 0 ;
while ( true ) {
var id = _rnd . Next ( ) ;
if ( ! _waits . ContainsKey ( id ) )
return id ;
Program . LogWarningAsync ( Program . WebSocketSource , $"Could not get a free ID after {++attempts} attempts!" ) ;
}
}
public ChunkWaiter < T > GetWaiter < T > ( Func < string , T > resultParser , CancellationToken ct ) {
ChunkWaiter < T > waiter ;
lock ( _syncRoot ) {
waiter = new ChunkWaiter < T > ( GetFreeId ( ) , resultParser , ct ) ;
_waits . Add ( waiter . ID , waiter ) ;
}
return waiter ;
}
private readonly ICommandHandler < ResponseType > _rs ;
2022-01-16 21:31:07 +01:00
[CommandHandler("rs", HelpText = "Provides some commands for interacting with the Refined Storage system.")]
2022-01-16 15:58:35 +01:00
public Task < ResponseType > RefinedStorageHandler ( SocketUserMessage message , string [ ] parameters , CancellationToken ct )
= > _rs . HandleCommand ( message , parameters , ct ) ;
public static Func < string , T > Deserialize < T > ( ) = > msg
= > JsonConvert . DeserializeObject < T > ( msg ) ? ? throw new InvalidProgramException ( "Empty response!" ) ;
2022-01-15 21:26:32 +01:00
public override Task < ResponseType > RootAnswer ( SocketUserMessage message , CancellationToken ct )
= > Task . FromResult ( ResponseType . AsString ( "The Minecraft server is connected!" ) ) ;
public override Task < ResponseType > FallbackHandler ( SocketUserMessage message , string method , string [ ] parameters , CancellationToken ct )
= > Task . FromResult ( ResponseType . AsString ( $"What the fuck do you mean by '{method}'?" ) ) ;
}
2022-01-16 15:58:35 +01:00
public interface ITaskWaitSource {
ChunkWaiter < T > GetWaiter < T > ( Func < string , T > resultParser , CancellationToken ct ) ;
Task Send ( Message requestMessage ) ;
}
2022-01-15 21:26:32 +01:00
[Serializable]
public class ReplyException : Exception {
public ReplyException ( ) { }
public ReplyException ( string message ) : base ( message ) { }
public ReplyException ( string message , Exception inner ) : base ( message , inner ) { }
protected ReplyException ( SerializationInfo info , StreamingContext context ) : base ( info , context ) { }
2022-01-11 20:32:25 +01:00
}
[JsonObject(MemberSerialization.OptIn, Description = "Describes an item in a Refined Storage system.", MissingMemberHandling = MissingMemberHandling.Ignore)]
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
public class Item : Fluid {
[JsonProperty("fingerprint", Required = Required.Always)]
public Md5Hash Fingerprint { get ; set ; } = default ! ;
[JsonProperty("nbt", Required = Required.DisallowNull)]
public dynamic? NBT { get ; set ; }
public override string ToString ( ) = > $"{Amount:n0}x {DisplayName}" ;
}
[JsonObject(MemberSerialization.OptIn, Description = "Describes a fluid in a Refined Storage system.", MissingMemberHandling = MissingMemberHandling.Ignore)]
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
public class Fluid {
[JsonProperty("amount", Required = Required.Always)]
public int Amount { get ; set ; }
[JsonProperty("displayName", Required = Required.Always)]
public string DisplayName { get ; set ; } = default ! ;
[JsonProperty("tags", Required = Required.DisallowNull)]
public string [ ] ? Tags { get ; set ; } = default ;
[JsonProperty("name", Required = Required.Always)]
public ModItemId ItemId { get ; set ; } = default ! ;
public override string ToString ( ) = > Amount > 10000
? $"{Amount / 1000.0f:n2} B of {DisplayName}"
: $"{Amount:n0} mB of {DisplayName}" ;
}
[JsonConverter(typeof(ModItemIdJsonConverter))]
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
public class ModItemId {
public ModItemId ( string name ) {
var colon = name . IndexOf ( ':' ) ;
if ( colon < 0 ) throw new ArgumentException ( "Invalid mod item id!" , nameof ( name ) ) ;
ModName = name [ . . colon ] ;
ModItem = name [ ( colon + 1 ) . . ] ;
if ( ToString ( ) ! = name ) throw new InvalidProgramException ( "Bad Parsing!" ) ;
}
public override string ToString ( ) = > $"{ModName}:{ModItem}" ;
public string ModName { get ; }
public string ModItem { get ; }
public class ModItemIdJsonConverter : JsonConverter < ModItemId > {
public override ModItemId ? ReadJson ( JsonReader reader , Type objectType , ModItemId ? existingValue , bool hasExistingValue , JsonSerializer serializer )
= > reader . Value is string value
? new ( value )
: throw new JsonException ( $"Could not parse mod name with token '{reader.Value}'" ) ;
public override void WriteJson ( JsonWriter writer , ModItemId ? value , JsonSerializer serializer ) {
if ( value is null ) writer . WriteNull ( ) ;
else writer . WriteValue ( value . ToString ( ) ) ;
}
}
}
[JsonConverter(typeof(Md5JsonConverter))]
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
public class Md5Hash : IEquatable < Md5Hash ? > {
private readonly byte [ ] _hash ;
public Md5Hash ( string hash ) : this ( Convert . FromHexString ( hash ) ) { }
public Md5Hash ( byte [ ] hash ) {
if ( hash is not { Length : 16 } ) throw new ArgumentException ( "Invalid digest size!" , nameof ( hash ) ) ;
_hash = hash ;
}
public override bool Equals ( object? obj ) = > Equals ( obj as Md5Hash ) ;
public bool Equals ( Md5Hash ? other ) = > other ! = null & & _hash . SequenceEqual ( other . _hash ) ;
public override int GetHashCode ( ) {
var hashCode = new HashCode ( ) ;
hashCode . AddBytes ( _hash ) ;
return hashCode . ToHashCode ( ) ;
}
public override string ToString ( ) = > Convert . ToHexString ( _hash ) ;
public class Md5JsonConverter : JsonConverter < Md5Hash > {
public override Md5Hash ? ReadJson ( JsonReader reader , Type objectType , Md5Hash ? existingValue , bool hasExistingValue , JsonSerializer serializer )
= > reader . Value is string { Length : 32 } value
? new ( value )
: throw new JsonException ( $"Could not parse MD5 hash with token '{reader.Value}'" ) ;
public override void WriteJson ( JsonWriter writer , Md5Hash ? value , JsonSerializer serializer ) {
if ( value is null ) writer . WriteNull ( ) ;
else writer . WriteValue ( value . ToString ( ) ) ;
}
}
}