2022-01-10 16:10:32 +01:00
using Discord ;
using Discord.Rest ;
using Discord.WebSocket ;
using Fleck ;
using Newtonsoft.Json ;
using System.Collections.Concurrent ;
2022-01-10 11:33:18 +01:00
2022-01-10 16:10:32 +01:00
namespace MinecraftDiscordBot ;
2022-01-10 11:33:18 +01:00
2022-01-10 16:10:32 +01:00
public class Program {
private const string WebSocketSource = "WebSocket" ;
private readonly object _logLock = new ( ) ;
private readonly DiscordSocketClient _client = new ( new ( ) {
LogLevel = LogSeverity . Verbose
} ) ;
private readonly WebSocketServer _wssv ;
private readonly BotConfiguration _config ;
private readonly HashSet < ulong > _whitelistedChannels ;
private readonly ConcurrentDictionary < Guid , IWebSocketConnection > _connections = new ( ) ;
public Program ( BotConfiguration config ) {
_config = config ;
_client . Log + = Log ;
_client . MessageReceived + = MessageReceived ;
_wssv = new WebSocketServer ( $"ws://0.0.0.0:{config.Port}" ) {
RestartAfterListenError = true
} ;
_whitelistedChannels = config . Channels . ToHashSet ( ) ;
}
public static Task < int > Main ( string [ ] args )
= > JsonConvert . DeserializeObject < BotConfiguration > ( File . ReadAllText ( "config.json" ) ) is BotConfiguration config
? new Program ( config ) . RunAsync ( )
: throw new InvalidProgramException ( "Configuration file missing!" ) ;
public async Task < int > RunAsync ( ) {
_wssv . Start ( socket = > {
socket . OnOpen = async ( ) = > await SocketOpened ( socket ) ;
socket . OnClose = async ( ) = > await SocketClosed ( socket ) ;
socket . OnMessage = async message = > await SocketReceived ( socket , message ) ;
} ) ;
await _client . LoginAsync ( TokenType . Bot , _config . Token ) ;
await _client . StartAsync ( ) ;
await VerifyTextChannels ( ) ;
// Block this task until the program is closed.
await Task . Delay ( - 1 ) ;
return 0 ;
}
private async Task VerifyTextChannels ( ) {
var channels = await Task . WhenAll ( _whitelistedChannels . Select ( id = > _client . GetChannelAsync ( id ) . AsTask ( ) ) . ToArray ( ) ) ;
await Task . WhenAll ( channels . Where ( i = > i is ITextChannel { Guild : RestGuild } ) . Select ( i = > ( ( RestGuild ) ( ( ITextChannel ) i ) . Guild ) . UpdateAsync ( ) ) ) ;
foreach ( var channel in channels ) {
if ( channel is ITextChannel tchannel ) Console . WriteLine ( $"Whitelisted in channel: {channel.Name} [{channel.Id}] on server {tchannel.Guild.Name} [{tchannel.Guild.Id}]" ) ;
else throw new InvalidProgramException ( $"Cannot use this bot on non-text channel {channel.Name} [{channel.Id}]!" ) ;
}
}
private async Task SocketReceived ( IWebSocketConnection socket , string message )
= > await Log ( new LogMessage ( LogSeverity . Info , WebSocketSource , $"[{socket.ConnectionInfo.Id}] Received: {message}" ) ) . ConfigureAwait ( false ) ;
private async Task SocketClosed ( IWebSocketConnection socket ) {
if ( ! _connections . TryRemove ( socket . ConnectionInfo . Id , out _ ) )
throw new InvalidProgramException ( "Could not remove non-existing client!" ) ;
await Log ( new LogMessage ( LogSeverity . Info , WebSocketSource , $"[{socket.ConnectionInfo.Id}] Client disconnected!" ) ) . ConfigureAwait ( false ) ;
}
private async Task SocketOpened ( IWebSocketConnection socket ) {
if ( ! _connections . TryAdd ( socket . ConnectionInfo . Id , socket ) )
throw new InvalidProgramException ( "Could not add already-existing client!" ) ;
await Log ( new LogMessage ( LogSeverity . Info , WebSocketSource , $"[{socket.ConnectionInfo.Id}] Client connected from {socket.ConnectionInfo.ClientIpAddress}:{socket.ConnectionInfo.ClientPort}!" ) ) . ConfigureAwait ( false ) ;
}
private async Task MessageReceived ( SocketMessage arg ) {
if ( arg . Author . IsBot ) return ;
if ( IsChannelWhitelisted ( arg . Channel ) )
await Log ( new LogMessage ( LogSeverity . Info , "Discord" , $"[{arg.Author.Username}] {arg.Content}" ) ) . ConfigureAwait ( false ) ;
await SendToAll ( JsonConvert . SerializeObject ( new TextMessage ( arg ) ) ) ;
}
private bool IsChannelWhitelisted ( ISocketMessageChannel channel )
= > _whitelistedChannels . Contains ( channel . Id ) ;
private async Task SendToAll ( string message ) {
async Task SendToClient ( KeyValuePair < Guid , IWebSocketConnection > cp ) {
try {
await cp . Value . Send ( message ) ;
} catch ( Exception e ) {
await Log ( new LogMessage ( LogSeverity . Warning , WebSocketSource , $"[{cp.Key}] Sending message failed!" , e ) ) . ConfigureAwait ( false ) ;
}
}
await Task . WhenAll ( _connections . Select ( SendToClient ) . ToArray ( ) ) ;
}
private async Task Log ( LogMessage msg ) {
lock ( _logLock )
Console . WriteLine ( msg . ToString ( ) ) ;
await Task . CompletedTask ;
}
}