2023-06-01 00:03:23 -04:00
//https://discord.com/oauth2/authorize?client_id=913003037348491264&permissions=274877942784&scope=bot%20messages.read
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
using Discord ;
using Discord.WebSocket ;
2023-06-05 14:55:48 -04:00
using vassago.Models ;
2023-06-01 00:03:23 -04:00
using vassago.Behavior ;
2023-06-05 14:55:48 -04:00
using Discord.Rest ;
2023-06-28 00:14:32 -04:00
using Microsoft.EntityFrameworkCore ;
using System.Threading ;
using System.Reactive.Linq ;
2023-06-01 00:03:23 -04:00
namespace vassago.DiscordInterface ;
public class DiscordInterface
{
2023-06-05 14:55:48 -04:00
internal const string PROTOCOL = "discord" ;
internal DiscordSocketClient client ;
2023-06-01 00:03:23 -04:00
private bool eventsSignedUp = false ;
private ChattingContext _db ;
2023-06-28 00:14:32 -04:00
private static SemaphoreSlim discordChannelSetup = new SemaphoreSlim ( 1 , 1 ) ;
private Channel protocolAsChannel ;
2023-06-01 00:03:23 -04:00
public DiscordInterface ( )
{
2023-06-20 21:26:44 -04:00
_db = new ChattingContext ( ) ;
2023-06-01 00:03:23 -04:00
}
public async Task Init ( string token )
{
2023-06-28 00:14:32 -04:00
await SetupDiscordChannel ( ) ;
2023-06-05 14:55:48 -04:00
client = new DiscordSocketClient ( new DiscordSocketConfig ( ) { GatewayIntents = GatewayIntents . All } ) ;
client . Log + = ( msg ) = >
2023-06-01 00:03:23 -04:00
{
Console . WriteLine ( msg . ToString ( ) ) ;
return Task . CompletedTask ;
} ;
2023-06-20 21:26:44 -04:00
client . Connected + = SelfConnected ;
2023-06-28 00:14:32 -04:00
client . Ready + = ClientReady ;
await client . LoginAsync ( TokenType . Bot , token ) ;
await client . StartAsync ( ) ;
}
2023-06-01 00:03:23 -04:00
2023-06-28 00:14:32 -04:00
private async Task SetupDiscordChannel ( )
{
await discordChannelSetup . WaitAsync ( ) ;
try
2023-06-01 00:03:23 -04:00
{
2023-07-04 12:58:21 -04:00
protocolAsChannel = _db . Channels . FirstOrDefault ( c = > c . ParentChannel = = null & & c . Protocol = = PROTOCOL ) ;
2023-06-28 00:14:32 -04:00
if ( protocolAsChannel = = null )
2023-06-01 00:03:23 -04:00
{
2023-06-28 00:14:32 -04:00
protocolAsChannel = new Channel ( )
{
DisplayName = "discord (itself)" ,
2024-04-05 23:59:39 -04:00
MeannessFilterLevel = Enumerations . MeannessFilterLevel . Strict ,
LewdnessFilterLevel = Enumerations . LewdnessFilterLevel . Moderate ,
MaxTextChars = 2000 ,
MaxAttachmentBytes = 25 * 1024 * 1024 , //allegedly it's 25, but I worry it's not actually.
LinksAllowed = true ,
ReactionsPossible = true ,
2023-06-28 00:14:32 -04:00
ExternalId = null ,
Protocol = PROTOCOL ,
SubChannels = new List < Channel > ( )
} ;
protocolAsChannel . SendMessage = ( t ) = > { throw new InvalidOperationException ( $"discord itself cannot accept text" ) ; } ;
protocolAsChannel . SendFile = ( f , t ) = > { throw new InvalidOperationException ( $"discord itself cannot send file" ) ; } ;
_db . Channels . Add ( protocolAsChannel ) ;
_db . SaveChanges ( ) ;
2023-06-01 00:03:23 -04:00
}
2023-06-28 00:14:32 -04:00
}
finally
{
discordChannelSetup . Release ( ) ;
}
}
2023-06-01 00:03:23 -04:00
2023-06-28 00:14:32 -04:00
private async Task ClientReady ( )
{
if ( ! eventsSignedUp )
{
eventsSignedUp = true ;
Console . WriteLine ( $"Bot is connected ({client.CurrentUser.Username}; {client.CurrentUser.Mention})! going to sign up for message received and user joined in client ready" ) ;
client . MessageReceived + = MessageReceived ;
// _client.MessageUpdated +=
//client.UserJoined += UserJoined;
client . SlashCommandExecuted + = SlashCommandHandler ;
//client.ChannelCreated +=
// _client.ChannelDestroyed +=
// _client.ChannelUpdated +=
// _client.GuildMemberUpdated +=
// _client.UserBanned +=
// _client.UserLeft +=
// _client.ThreadCreated +=
// _client.ThreadUpdated +=
// _client.ThreadDeleted +=
// _client.JoinedGuild +=
// _client.GuildUpdated +=
// _client.LeftGuild +=
await SlashCommandsHelper . Register ( client ) ;
}
else
{
Console . WriteLine ( "bot appears to be RE connected, so I'm not going to sign up twice" ) ;
}
2023-06-01 00:03:23 -04:00
}
2023-06-19 12:56:40 -04:00
2023-06-20 21:26:44 -04:00
private async Task SelfConnected ( )
{
2024-01-05 21:39:31 -05:00
var selfAccount = UpsertAccount ( client . CurrentUser , protocolAsChannel . Id ) ;
selfAccount . DisplayName = client . CurrentUser . Username ;
2023-06-20 21:26:44 -04:00
await _db . SaveChangesAsync ( ) ;
2024-01-05 21:39:31 -05:00
Behaver . Instance . MarkSelf ( selfAccount ) ;
2023-06-20 21:26:44 -04:00
}
2023-06-01 00:03:23 -04:00
private async Task MessageReceived ( SocketMessage messageParam )
{
var suMessage = messageParam as SocketUserMessage ;
2023-06-05 14:55:48 -04:00
if ( suMessage = = null )
2023-06-01 00:03:23 -04:00
{
2023-06-05 14:55:48 -04:00
Console . WriteLine ( $"{messageParam.Content}, but not a user message" ) ;
2023-06-05 15:25:43 -04:00
return ;
2023-06-01 00:03:23 -04:00
}
Console . WriteLine ( $"#{suMessage.Channel}[{DateTime.Now}][{suMessage.Author.Username} [id={suMessage.Author.Id}]][msg id: {suMessage.Id}] {suMessage.Content}" ) ;
2023-06-05 14:55:48 -04:00
var m = UpsertMessage ( suMessage ) ;
2023-06-01 00:03:23 -04:00
2023-06-05 14:55:48 -04:00
if ( suMessage . MentionedUsers ? . FirstOrDefault ( muid = > muid . Id = = client . CurrentUser . Id ) ! = null )
2023-06-01 00:03:23 -04:00
{
2023-06-05 14:55:48 -04:00
var mentionOfMe = "<@" + client . CurrentUser . Id + ">" ;
2023-06-01 00:03:23 -04:00
m . MentionsMe = true ;
}
2023-06-28 00:14:32 -04:00
if ( await Behaver . Instance . ActOn ( m ) )
2023-06-01 00:03:23 -04:00
{
2023-06-28 00:14:32 -04:00
m . ActedOn = true ;
2023-06-01 00:03:23 -04:00
}
2023-06-05 14:55:48 -04:00
_db . SaveChanges ( ) ;
}
2023-06-01 00:03:23 -04:00
2023-06-19 11:03:06 -04:00
private void UserJoined ( SocketGuildUser arg )
2023-06-01 00:03:23 -04:00
{
2023-06-20 21:26:44 -04:00
var guild = UpsertChannel ( arg . Guild ) ;
var defaultChannel = UpsertChannel ( arg . Guild . DefaultChannel ) ;
defaultChannel . ParentChannel = guild ;
2023-07-04 20:40:37 -04:00
var u = UpsertAccount ( arg , guild . Id ) ;
2023-06-28 00:14:32 -04:00
u . DisplayName = arg . DisplayName ;
2023-06-01 00:03:23 -04:00
}
private async Task ButtonHandler ( SocketMessageComponent component )
{
switch ( component . Data . CustomId )
{
case "custom-id" :
await component . RespondAsync ( $"{component.User.Mention}, it's been here the whole time!" ) ;
break ;
}
}
internal static async Task SlashCommandHandler ( SocketSlashCommand command )
{
switch ( command . CommandName )
{
case "freedomunits" :
try
{
var amt = Convert . ToDecimal ( ( double ) ( command . Data . Options . First ( o = > o . Name = = "amount" ) . Value ) ) ;
var src = ( string ) command . Data . Options . First ( o = > o . Name = = "src-unit" ) . Value ;
var dest = ( string ) command . Data . Options . First ( o = > o . Name = = "dest-unit" ) . Value ;
var conversionResult = Conversion . Converter . Convert ( amt , src , dest ) ;
await command . RespondAsync ( $"> {amt} {src} -> {dest}\n{conversionResult}" ) ;
}
catch ( Exception e )
{
await command . RespondAsync ( $"error: {e.Message}. aaadam!" ) ;
}
break ;
default :
await command . RespondAsync ( $"\\*smiles and nods*\n" ) ;
await command . Channel . SendFileAsync ( $"assets/loud sweating.gif" ) ;
Console . Error . WriteLine ( $"can't understand command name: {command.CommandName}" ) ;
break ;
}
}
2023-06-05 14:55:48 -04:00
internal vassago . Models . Attachment UpsertAttachment ( IAttachment dAttachment )
{
var a = _db . Attachments . FirstOrDefault ( ai = > ai . ExternalId = = dAttachment . Id ) ;
if ( a = = null )
{
2023-06-05 15:25:43 -04:00
a = new vassago . Models . Attachment ( ) ;
2023-06-28 00:14:32 -04:00
_db . Attachments . Add ( a ) ;
2023-06-05 14:55:48 -04:00
}
a . ContentType = dAttachment . ContentType ;
a . Description = dAttachment . Description ;
a . Filename = dAttachment . Filename ;
a . Size = dAttachment . Size ;
a . Source = new Uri ( dAttachment . Url ) ;
2023-06-05 15:25:43 -04:00
2023-06-05 14:55:48 -04:00
return a ;
}
internal Message UpsertMessage ( IUserMessage dMessage )
2023-06-01 00:03:23 -04:00
{
2023-07-04 12:58:21 -04:00
var m = _db . Messages . FirstOrDefault ( mi = > mi . ExternalId = = dMessage . Id . ToString ( ) & & mi . Protocol = = PROTOCOL ) ;
2023-06-05 14:55:48 -04:00
if ( m = = null )
{
m = new Message ( ) ;
2023-08-22 15:33:09 -04:00
m . Protocol = PROTOCOL ;
2023-06-28 00:14:32 -04:00
_db . Messages . Add ( m ) ;
2023-06-05 14:55:48 -04:00
}
2023-06-05 15:25:43 -04:00
m . Attachments = m . Attachments ? ? new List < vassago . Models . Attachment > ( ) ;
2023-06-05 14:55:48 -04:00
if ( dMessage . Attachments ? . Any ( ) = = true )
2023-06-01 00:03:23 -04:00
{
2023-06-05 14:55:48 -04:00
m . Attachments = new List < vassago . Models . Attachment > ( ) ;
foreach ( var da in dMessage . Attachments )
{
m . Attachments . Add ( UpsertAttachment ( da ) ) ;
}
2023-06-01 00:03:23 -04:00
}
2023-06-05 14:55:48 -04:00
m . Content = dMessage . Content ;
2023-07-04 12:58:21 -04:00
m . ExternalId = dMessage . Id . ToString ( ) ;
2023-06-05 14:55:48 -04:00
m . Timestamp = dMessage . EditedTimestamp ? ? dMessage . CreatedAt ;
2023-07-03 12:51:23 -04:00
m . Channel = UpsertChannel ( dMessage . Channel ) ;
2023-07-04 20:40:37 -04:00
m . Author = UpsertAccount ( dMessage . Author , m . Channel . Id ) ;
2023-07-03 12:51:23 -04:00
m . Author . SeenInChannel = m . Channel ;
if ( dMessage . Channel is IGuildChannel )
2023-06-05 14:55:48 -04:00
{
2023-07-03 12:51:23 -04:00
m . Author . DisplayName = ( dMessage . Author as IGuildUser ) . DisplayName ; //discord forgot how display names work.
2023-06-05 14:55:48 -04:00
}
2023-07-03 12:51:23 -04:00
m . MentionsMe = ( dMessage . Author . Id ! = client . CurrentUser . Id
& & ( dMessage . MentionedUserIds ? . FirstOrDefault ( muid = > muid = = client . CurrentUser . Id ) > 0 ) ) ;
2023-06-05 14:55:48 -04:00
2023-06-05 15:25:43 -04:00
m . Reply = ( t ) = > { return dMessage . ReplyAsync ( t ) ; } ;
2023-06-19 12:56:40 -04:00
m . React = ( e ) = > { return attemptReact ( dMessage , e ) ; } ;
2023-06-05 14:55:48 -04:00
return m ;
}
internal Channel UpsertChannel ( IMessageChannel channel )
{
2023-07-04 12:58:21 -04:00
Channel c = _db . Channels . FirstOrDefault ( ci = > ci . ExternalId = = channel . Id . ToString ( ) & & ci . Protocol = = PROTOCOL ) ;
2023-06-05 14:55:48 -04:00
if ( c = = null )
{
c = new Channel ( ) ;
2023-06-28 00:14:32 -04:00
_db . Channels . Add ( c ) ;
2023-06-05 14:55:48 -04:00
}
c . DisplayName = channel . Name ;
2023-07-04 12:58:21 -04:00
c . ExternalId = channel . Id . ToString ( ) ;
2023-12-03 14:33:58 -05:00
c . ChannelType = ( channel is IPrivateChannel ) ? vassago . Models . Enumerations . ChannelType . DM : vassago . Models . Enumerations . ChannelType . Normal ;
2023-06-05 14:55:48 -04:00
c . Messages = c . Messages ? ? new List < Message > ( ) ;
c . Protocol = PROTOCOL ;
if ( channel is IGuildChannel )
2023-06-01 00:03:23 -04:00
{
c . ParentChannel = UpsertChannel ( ( channel as IGuildChannel ) . Guild ) ;
2023-06-18 22:10:51 -04:00
c . ParentChannel . SubChannels . Add ( c ) ;
2023-06-01 00:03:23 -04:00
}
else if ( channel is IPrivateChannel )
{
2023-06-28 00:14:32 -04:00
c . ParentChannel = protocolAsChannel ;
2023-06-01 00:03:23 -04:00
}
else
{
2023-06-28 00:14:32 -04:00
c . ParentChannel = protocolAsChannel ;
2023-06-05 14:55:48 -04:00
Console . Error . WriteLine ( $"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg" ) ;
2023-06-01 00:03:23 -04:00
}
2023-06-05 14:55:48 -04:00
c . SubChannels = c . SubChannels ? ? new List < Channel > ( ) ;
c . SendMessage = ( t ) = > { return channel . SendMessageAsync ( t ) ; } ;
c . SendFile = ( f , t ) = > { return channel . SendFileAsync ( f , t ) ; } ;
2023-12-03 14:33:58 -05:00
switch ( c . ChannelType )
{
case vassago . Models . Enumerations . ChannelType . DM :
c . DisplayName = "DM: " + ( channel as IPrivateChannel ) . Recipients ? . FirstOrDefault ( u = > u . Id ! = client . CurrentUser . Id ) . Username ;
break ;
}
2023-06-05 14:55:48 -04:00
return c ;
2023-06-01 00:03:23 -04:00
}
2023-06-05 14:55:48 -04:00
internal Channel UpsertChannel ( IGuild channel )
2023-06-01 00:03:23 -04:00
{
2023-07-04 12:58:21 -04:00
Channel c = _db . Channels . FirstOrDefault ( ci = > ci . ExternalId = = channel . Id . ToString ( ) & & ci . Protocol = = PROTOCOL ) ;
2023-06-05 14:55:48 -04:00
if ( c = = null )
{
c = new Channel ( ) ;
2023-06-28 00:14:32 -04:00
_db . Channels . Add ( c ) ;
2023-06-05 14:55:48 -04:00
}
c . DisplayName = channel . Name ;
2023-07-04 12:58:21 -04:00
c . ExternalId = channel . Id . ToString ( ) ;
2023-12-03 14:33:58 -05:00
c . ChannelType = vassago . Models . Enumerations . ChannelType . Normal ;
2023-06-05 14:55:48 -04:00
c . Messages = c . Messages ? ? new List < Message > ( ) ;
2023-06-28 00:14:32 -04:00
c . Protocol = protocolAsChannel . Protocol ;
c . ParentChannel = protocolAsChannel ;
2023-06-05 14:55:48 -04:00
c . SubChannels = c . SubChannels ? ? new List < Channel > ( ) ;
2024-04-05 23:59:39 -04:00
c . MaxAttachmentBytes = channel . MaxUploadLimit ;
2023-06-05 14:55:48 -04:00
c . SendMessage = ( t ) = > { throw new InvalidOperationException ( $"channel {channel.Name} is guild; cannot accept text" ) ; } ;
c . SendFile = ( f , t ) = > { throw new InvalidOperationException ( $"channel {channel.Name} is guild; send file" ) ; } ;
2023-06-01 00:03:23 -04:00
return c ;
}
2023-07-04 20:40:37 -04:00
internal Account UpsertAccount ( IUser user , Guid inChannel )
2023-06-01 00:03:23 -04:00
{
2023-07-04 20:40:37 -04:00
var acc = _db . Accounts . FirstOrDefault ( ui = > ui . ExternalId = = user . Id . ToString ( ) & & ui . SeenInChannel . Id = = inChannel ) ;
2023-06-28 00:14:32 -04:00
if ( acc = = null )
2023-06-01 00:03:23 -04:00
{
2023-06-28 00:14:32 -04:00
acc = new Account ( ) ;
_db . Accounts . Add ( acc ) ;
2023-06-05 14:55:48 -04:00
}
2023-06-28 00:14:32 -04:00
acc . Username = user . Username ;
2023-07-04 12:58:21 -04:00
acc . ExternalId = user . Id . ToString ( ) ;
2023-06-28 00:14:32 -04:00
acc . IsBot = user . IsBot | | user . IsWebhook ;
acc . Protocol = PROTOCOL ;
2023-06-05 14:55:48 -04:00
2023-12-01 14:02:47 -05:00
acc . IsUser = _db . Users . FirstOrDefault ( u = > u . Accounts . Any ( a = > a . ExternalId = = acc . ExternalId & & a . Protocol = = acc . Protocol ) ) ;
if ( acc . IsUser = = null )
2023-07-03 12:51:23 -04:00
{
2023-12-01 14:02:47 -05:00
acc . IsUser = new User ( ) { Accounts = new List < Account > ( ) { acc } } ;
_db . Users . Add ( acc . IsUser ) ;
2023-07-03 12:51:23 -04:00
}
2023-06-28 00:14:32 -04:00
return acc ;
2023-06-01 00:03:23 -04:00
}
2023-06-15 23:29:07 -04:00
2023-06-28 00:14:32 -04:00
private Task attemptReact ( IUserMessage msg , string e )
{
2023-07-04 12:58:21 -04:00
var c = _db . Channels . FirstOrDefault ( c = > c . ExternalId = = msg . Channel . Id . ToString ( ) ) ;
2023-06-28 00:14:32 -04:00
//var preferredEmote = c.EmoteOverrides?[e] ?? e; //TODO: emote overrides
var preferredEmote = e ;
2024-01-05 21:39:31 -05:00
if ( Emoji . TryParse ( preferredEmote , out Emoji emoji ) )
2023-06-15 23:29:07 -04:00
{
2023-06-28 00:14:32 -04:00
return msg . AddReactionAsync ( emoji ) ;
2023-06-15 23:29:07 -04:00
}
2024-01-05 21:39:31 -05:00
if ( ! Emote . TryParse ( preferredEmote , out Emote emote ) )
2023-06-15 23:29:07 -04:00
{
2023-06-28 00:14:32 -04:00
if ( preferredEmote = = e )
Console . Error . WriteLine ( $"never heard of emote {e}" ) ;
return Task . CompletedTask ;
2023-06-15 23:29:07 -04:00
}
2023-06-28 00:14:32 -04:00
return msg . AddReactionAsync ( emote ) ;
}
2023-06-01 00:03:23 -04:00
}