2021-11-19 20:54:39 +00:00
using System.Text.RegularExpressions ;
2020-02-12 14:16:19 +00:00
2020-12-25 12:19:35 +00:00
using Myriad.Extensions ;
2020-12-25 11:56:46 +00:00
using Myriad.Rest.Exceptions ;
2020-12-25 12:19:35 +00:00
using Myriad.Rest.Types.Requests ;
using Myriad.Types ;
2019-10-05 05:41:00 +00:00
2020-02-12 14:16:19 +00:00
using PluralKit.Core ;
2019-06-20 19:15:57 +00:00
2021-11-27 02:10:56 +00:00
namespace PluralKit.Bot ;
public class Api
2019-06-20 19:15:57 +00:00
{
2021-11-27 02:10:56 +00:00
private static readonly Regex _webhookRegex =
new ( "https://(?:\\w+.)?discord(?:app)?.com/api(?:/v.*)?/webhooks/(.*)" ) ;
2021-11-27 16:09:08 +00:00
private readonly BotConfig _botConfig ;
2021-11-27 02:10:56 +00:00
private readonly DispatchService _dispatch ;
2022-01-22 07:47:47 +00:00
private readonly PrivateChannelService _dmCache ;
2021-11-27 02:10:56 +00:00
2022-01-22 08:05:01 +00:00
public Api ( BotConfig botConfig , DispatchService dispatch , PrivateChannelService dmCache )
2019-06-20 19:15:57 +00:00
{
2021-11-27 16:09:08 +00:00
_botConfig = botConfig ;
2021-11-27 02:10:56 +00:00
_dispatch = dispatch ;
2022-01-22 07:47:47 +00:00
_dmCache = dmCache ;
2021-11-27 02:10:56 +00:00
}
2019-10-05 05:41:00 +00:00
2021-11-27 02:10:56 +00:00
public async Task GetToken ( Context ctx )
{
ctx . CheckSystem ( ) ;
2020-08-25 22:17:05 +00:00
2021-11-27 02:10:56 +00:00
// Get or make a token
2022-01-22 08:05:01 +00:00
var token = ctx . System . Token ? ? await MakeAndSetNewToken ( ctx , ctx . System ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
try
{
// DM the user a security disclaimer, and then the token in a separate message (for easy copying on mobile)
2022-01-22 07:47:47 +00:00
var dm = await _dmCache . GetOrCreateDmChannel ( ctx . Author . Id ) ;
await ctx . Rest . CreateMessage ( dm ,
2021-11-27 02:10:56 +00:00
new MessageRequest
2020-12-25 12:19:35 +00:00
{
2021-11-27 02:10:56 +00:00
Content = $"{Emojis.Warn} Please note that this grants access to modify (and delete!) all your system data, so keep it safe and secure."
+ $" If it leaks or you need a new one, you can invalidate this one with `pk;token refresh`.\n\nYour token is below:"
2020-12-25 12:19:35 +00:00
} ) ;
2022-01-22 07:47:47 +00:00
await ctx . Rest . CreateMessage ( dm , new MessageRequest { Content = token } ) ;
2021-11-27 02:10:56 +00:00
2021-11-27 16:09:08 +00:00
if ( _botConfig . IsBetaBot )
2022-01-22 07:47:47 +00:00
await ctx . Rest . CreateMessage ( dm , new MessageRequest
2021-11-27 16:09:08 +00:00
{
Content = $"{Emojis.Note} The beta bot's API base URL is currently <{_botConfig.BetaBotAPIUrl}>."
+ " You need to use this URL instead of the base URL listed on the documentation website."
} ) ;
2021-11-27 02:10:56 +00:00
// If we're not already in a DM, reply with a reminder to check
if ( ctx . Channel . Type ! = Channel . ChannelType . Dm )
await ctx . Reply ( $"{Emojis.Success} Check your DMs!" ) ;
2019-06-20 19:15:57 +00:00
}
2021-11-27 02:10:56 +00:00
catch ( ForbiddenException )
{
// Can't check for permission errors beforehand, so have to handle here :/
if ( ctx . Channel . Type ! = Channel . ChannelType . Dm )
await ctx . Reply ( $"{Emojis.Error} Could not send token in DMs. Are your DMs closed?" ) ;
}
}
2022-01-22 08:05:01 +00:00
private async Task < string > MakeAndSetNewToken ( Context ctx , PKSystem system )
2021-11-27 02:10:56 +00:00
{
2022-01-22 08:05:01 +00:00
system = await ctx . Repository . UpdateSystem ( system . Id , new SystemPatch { Token = StringUtils . GenerateToken ( ) } ) ;
2021-11-27 02:10:56 +00:00
return system . Token ;
}
public async Task RefreshToken ( Context ctx )
{
ctx . CheckSystem ( ) ;
2019-06-20 19:15:57 +00:00
2021-11-27 02:10:56 +00:00
if ( ctx . System . Token = = null )
2019-06-20 19:15:57 +00:00
{
2021-11-27 02:10:56 +00:00
// If we don't have a token, call the other method instead
// This does pretty much the same thing, except words the messages more appropriately for that :)
await GetToken ( ctx ) ;
return ;
2019-06-20 19:15:57 +00:00
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
try
2019-06-20 19:15:57 +00:00
{
2021-11-27 02:10:56 +00:00
// DM the user an invalidation disclaimer, and then the token in a separate message (for easy copying on mobile)
2022-01-22 07:47:47 +00:00
var dm = await _dmCache . GetOrCreateDmChannel ( ctx . Author . Id ) ;
await ctx . Rest . CreateMessage ( dm ,
2021-11-27 02:10:56 +00:00
new MessageRequest
2020-12-25 12:19:35 +00:00
{
Content = $"{Emojis.Warn} Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\n\nYour token is below:"
} ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
// Make the new token after sending the first DM; this ensures if we can't DM, we also don't end up
// breaking their existing token as a side effect :)
2022-01-22 08:05:01 +00:00
var token = await MakeAndSetNewToken ( ctx , ctx . System ) ;
2022-01-22 07:47:47 +00:00
await ctx . Rest . CreateMessage ( dm , new MessageRequest { Content = token } ) ;
2021-11-27 02:10:56 +00:00
2021-11-27 16:09:08 +00:00
if ( _botConfig . IsBetaBot )
2022-01-22 07:47:47 +00:00
await ctx . Rest . CreateMessage ( dm , new MessageRequest
2021-11-27 16:09:08 +00:00
{
Content = $"{Emojis.Note} The beta bot's API base URL is currently <{_botConfig.BetaBotAPIUrl}>."
+ " You need to use this URL instead of the base URL listed on the documentation website."
} ) ;
2021-11-27 02:10:56 +00:00
// If we're not already in a DM, reply with a reminder to check
if ( ctx . Channel . Type ! = Channel . ChannelType . Dm )
await ctx . Reply ( $"{Emojis.Success} Check your DMs!" ) ;
}
catch ( ForbiddenException )
{
// Can't check for permission errors beforehand, so have to handle here :/
if ( ctx . Channel . Type ! = Channel . ChannelType . Dm )
await ctx . Reply ( $"{Emojis.Error} Could not send token in DMs. Are your DMs closed?" ) ;
}
}
public async Task SystemWebhook ( Context ctx )
{
ctx . CheckSystem ( ) . CheckDMContext ( ) ;
if ( ! ctx . HasNext ( false ) )
{
if ( ctx . System . WebhookUrl = = null )
await ctx . Reply ( "Your system does not have a webhook URL set. Set one with `pk;system webhook <url>`!" ) ;
else
await ctx . Reply ( $"Your system's webhook URL is <{ctx.System.WebhookUrl}>." ) ;
return ;
2019-06-20 19:15:57 +00:00
}
2021-11-03 06:01:35 +00:00
2021-11-27 02:10:56 +00:00
if ( await ctx . MatchClear ( "your system's webhook URL" ) )
2021-11-03 06:01:35 +00:00
{
2022-01-22 08:05:01 +00:00
await ctx . Repository . UpdateSystem ( ctx . System . Id , new SystemPatch { WebhookUrl = null , WebhookToken = null } ) ;
2021-11-27 02:10:56 +00:00
await ctx . Reply ( $"{Emojis.Success} System webhook URL removed." ) ;
return ;
}
var newUrl = ctx . RemainderOrNull ( ) ;
if ( ! await DispatchExt . ValidateUri ( newUrl ) )
throw new PKError ( $"The URL {newUrl.AsCode()} is invalid or I cannot access it. Are you sure this is a valid, publicly accessible URL?" ) ;
if ( _webhookRegex . IsMatch ( newUrl ) )
throw new PKError ( "PluralKit does not currently support setting a Discord webhook URL as your system's webhook URL." ) ;
2021-11-03 06:01:35 +00:00
2021-11-27 02:10:56 +00:00
try
{
await _dispatch . DoPostRequest ( ctx . System . Id , newUrl , null , true ) ;
}
catch ( Exception e )
{
throw new PKError ( $"Could not verify that the new URL is working: {e.Message}" ) ;
2021-11-03 06:01:35 +00:00
}
2021-11-27 02:10:56 +00:00
var newToken = StringUtils . GenerateToken ( ) ;
2022-01-22 08:05:01 +00:00
await ctx . Repository . UpdateSystem ( ctx . System . Id , new SystemPatch { WebhookUrl = newUrl , WebhookToken = newToken } ) ;
2021-11-27 02:10:56 +00:00
await ctx . Reply ( $"{Emojis.Success} Successfully the new webhook URL for your system."
+ $"\n\n{Emojis.Warn} The following token is used to authenticate requests from PluralKit to you."
+ " If it leaks, you should clear and re-set the webhook URL to get a new token."
+ "\ntodo: add link to docs or something"
) ;
await ctx . Reply ( newToken ) ;
2019-06-20 19:15:57 +00:00
}
}