2024-05-03 03:04:43 +00:00
# frozen_string_literal: true
module DiscordInteractions
class YiffyAPI
attr_reader :raw
GREEN_TICK = APIKey :: WebhookMethods :: GREEN_TICK
RED_TICK = APIKey :: WebhookMethods :: RED_TICK
def initialize ( raw )
@raw = raw
end
def [] ( key )
@raw [ key . to_s ]
end
# rubocop:disable Metrics/BlockNesting
# noinspection RubyCaseWithoutElseBlockInspection
def handle
return { type : DiscordConstants :: InteractionResponseTypes :: PONG } if raw [ " type " ] == DiscordConstants :: InteractionTypes :: PING
if raw [ " guild_id " ] . nil?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " My commands cannot be used in Direct Messages. " ,
** common ,
} ,
}
end
CurrentUser . user = CurrentUser . user = APIUser . find_or_create_by ( id : raw [ " member " ] [ " user " ] [ " id " ] )
CurrentUser . ip_addr = " 0.0.0.0 "
case raw [ " type " ]
when DiscordConstants :: InteractionTypes :: APPLICATION_COMMAND
options = DiscordInteractions :: InteractionOptions . new ( raw [ " data " ] [ " options " ] , raw [ " data " ] [ " resolved " ] )
sub = options . get_subcommand
case raw [ " data " ] [ " name " ]
when " apikey "
keys = APIKey . for_owner ( raw [ " member " ] [ " user " ] [ " id " ] )
case sub [ 0 ]
when " create "
if keys . length > = 3
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " You already have the maximum amount of api keys. Contact a developer if you believe an exception should be made. " ,
** common ,
} ,
}
end
{
type : DiscordConstants :: InteractionResponseTypes :: MODAL ,
data : {
custom_id : " apikey-create " ,
components : [
{
type : DiscordConstants :: ComponentTypes :: ACTION_ROW ,
components : [
{
custom_id : " apikey-create.name " ,
placeholder : " My Awesome Application " ,
min_length : 3 ,
max_length : 50 ,
label : " Name " ,
style : DiscordConstants :: TextInputStyles :: SHORT ,
type : DiscordConstants :: ComponentTypes :: TEXT_INPUT ,
} ,
] ,
} ,
{
type : DiscordConstants :: ComponentTypes :: ACTION_ROW ,
components : [
{
custom_id : " apikey-create.usage " ,
placeholder : " Showing porn to my friends " ,
min_length : 5 ,
max_length : 400 ,
label : " Usage " ,
style : DiscordConstants :: TextInputStyles :: PARAGRAPH ,
type : DiscordConstants :: ComponentTypes :: TEXT_INPUT ,
} ,
] ,
} ,
] ,
title : " Create API Key " ,
} ,
}
when " delete "
key = keys . find { | k | k . encoded == options . get_string ( " key " , required : true ) }
if key . nil? || key . owner_id . to_s != raw [ " member " ] [ " user " ] [ " id " ]
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " Invalid key specified. " ,
** common ,
} ,
}
end
if key . disabled?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " This key has been disabled by a developer. To have this key deleted or removed, concat a developer. \n \n Disable Reason: ** #{ key . disabled_reason || '(None)' } ** " ,
** common ,
} ,
}
end
{
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " Are you sure you want to delete the key ** #{ key . application_name } **? This action cannot be undone. " ,
components : [
{
type : DiscordConstants :: ComponentTypes :: ACTION_ROW ,
components : [
{
custom_id : " apikey-delete-yes. #{ key . encoded } . #{ raw [ 'member' ] [ 'user' ] [ 'id' ] } " ,
label : " Yes " ,
style : DiscordConstants :: ButtonStyles :: SUCCESS ,
type : DiscordConstants :: ComponentTypes :: BUTTON ,
} ,
{
custom_id : " apikey-delete-no. #{ raw [ 'member' ] [ 'user' ] [ 'id' ] } " ,
label : " No " ,
style : DiscordConstants :: ButtonStyles :: DANGER ,
type : DiscordConstants :: ComponentTypes :: BUTTON ,
} ,
] ,
} ,
] ,
** common ,
} ,
}
when " list "
if keys . empty?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " You do not have any API keys. " ,
** common ,
} ,
}
end
text = [ ]
keys . each_with_index do | apikey , index |
text . push (
<< ~ TEXT ,
#{index + 1}.)
- Key : || #{apikey.key}||
- Application : ` #{ apikey . application_name } `
- Usage : ` #{ apikey . usage || 'None Specified' } `
- Active : #{apikey.active? ? GREEN_TICK : RED_TICK}
- Disabled : #{apikey.disabled? ? "#{GREEN_TICK} (Reason: #{apikey.disabled_reason || 'None Specified'})" : RED_TICK}
- Unlimited : #{apikey.unlimited? ? GREEN_TICK : RED_TICK}
- Services : #{apikey.access_string}
TEXT
)
end
{
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " We found the following api keys: \n \n #{ text . join ( " \n \n " ) } " ,
** common ,
} ,
}
end
when " apidev "
unless Websites . config . yiffyapi_administrators . include? ( raw [ " member " ] [ " user " ] [ " id " ] )
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " You are not allowed to use that. " ,
** common ,
} ,
}
end
case sub [ 0 ]
when " list "
user_id = options . get_user ( " user " , required : true ) [ " id " ]
keys = APIKey . for_owner ( user_id )
if keys . empty?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " That user does not have any API keys. " ,
** common ,
} ,
}
end
text = [ ]
keys . each_with_index do | apikey , index |
text . push (
<< ~ TEXT ,
#{index + 1}.)
- Key : || #{apikey.key}||
- Application : ` #{ apikey . application_name } `
- Usage : ` #{ apikey . usage || 'None Specified' } `
- Active : #{apikey.active? ? GREEN_TICK : RED_TICK}
- Disabled : #{apikey.disabled? ? "#{GREEN_TICK} (Reason: #{apikey.disabled_reason || 'None Specified'})" : RED_TICK}
- Unlimited : #{apikey.unlimited? ? GREEN_TICK : RED_TICK}
- Services : #{apikey.access_string}
TEXT
)
end
{
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " We found the following api keys for <@ #{ user_id } >: \n \n #{ text . join ( " \n \n " ) } " ,
** common ,
} ,
}
when " disable "
key = options . get_string ( " key " , required : true )
reason = options . get_string ( " reason " ) || " None Provided "
apikey = APIKey . find_by ( key : key )
if apikey . nil?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " I couldn't find that api key. " ,
** common ,
} ,
}
end
if apikey . disabled?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " That api key is already disabled. " ,
** common ,
} ,
}
end
apikey . update ( disabled : true , disabled_reason : reason )
{
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " Api key successfully disabled. " ,
** common ,
} ,
}
when " enable "
key = options . get_string ( " key " , required : true )
apikey = APIKey . find_by ( key : key )
if apikey . nil?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " I couldn't find that api key. " ,
** common ,
} ,
}
end
unless apikey . disabled?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " That api key is not disabled. " ,
** common ,
} ,
}
end
apikey . update ( disabled : false , disabled_reason : nil )
{
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " Api key successfully enabled. " ,
** common ,
} ,
}
when " stats "
key = options . get_string ( " key " )
ip = options . get_string ( " ip " )
if key
apikey = APIKey . find_by ( key : key )
if apikey . nil?
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " I couldn't find that api key. " ,
** common ,
} ,
}
end
end
2024-05-03 06:36:37 +00:00
stats = APIKey . stats ( ip : ip , key : apikey )
2024-05-03 03:04:43 +00:00
if key . present?
stats = flatten_hash ( stats [ :key ] )
text = " Stats for ** #{ apikey . application_name } ** (|| #{ apikey . key } ||) \n \n "
elsif ip . present?
stats = flatten_hash ( stats [ :ip ] )
text = " Stats for ** #{ ip } ** \n \n "
else
stats = flatten_hash ( stats [ :global ] )
text = " Global stats \n \n "
end
stats . each do | name , value |
text += " ** #{ name } **: #{ ActiveSupport :: NumberHelper . number_to_delimited ( value ) } \n "
end
{
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : text ,
** common ,
} ,
}
end
end
when DiscordConstants :: InteractionTypes :: MESSAGE_COMPONENT
action , * other , user = raw [ " data " ] [ " custom_id " ] . split ( " . " )
if user != raw [ " member " ] [ " user " ] [ " id " ]
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " That is not yours to play with. " ,
** common ,
} ,
}
end
case action
when " apikey-delete-yes "
key = APIKey . for_owner ( user ) . find { | k | k . encoded == other [ 0 ] }
if key . nil? || key . owner_id . to_s != user
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " Invalid key specified. " ,
** common ,
} ,
}
end
key . destroy
{
type : DiscordConstants :: InteractionResponseTypes :: UPDATE_MESSAGE ,
data : {
content : " Key deleted. " ,
components : [ ] ,
** common ,
} ,
}
when " apikey-delete-no "
{
type : DiscordConstants :: InteractionResponseTypes :: UPDATE_MESSAGE ,
data : {
content : " Cancelled. " ,
components : [ ] ,
** common ,
} ,
}
end
when DiscordConstants :: InteractionTypes :: APPLICATION_COMMAND_AUTOCOMPLETE
options = DiscordInteractions :: InteractionOptions . new ( raw [ " data " ] [ " options " ] , raw [ " data " ] [ " resolved " ] )
sub , = options . get_subcommand
opt = options . get_focused_option ( required : true )
case raw [ " data " ] [ " name " ]
when " apikey "
case sub
when " delete "
keys = APIKey . for_owner ( raw [ " member " ] [ " user " ] [ " id " ] )
value = opt [ " value " ]
keys = keys . filter { | k | k . application_name . start_with? ( value ) }
{
type : DiscordConstants :: InteractionResponseTypes :: APPLICATION_COMMAND_AUTOCOMPLETE_RESULT ,
data : {
choices : keys . map do | k |
{
name : k . application_name ,
value : k . encoded ,
}
end ,
} ,
}
end
end
when DiscordConstants :: InteractionTypes :: MODAL_SUBMIT
case raw [ " data " ] [ " custom_id " ]
when " apikey-create "
name = raw [ " data " ] [ " components " ] [ 0 ] [ " components " ] [ 0 ] [ " value " ]
usage = raw [ " data " ] [ " components " ] [ 1 ] [ " components " ] [ 0 ] [ " value " ]
if name . length < 3 || name . length > 50
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " Name must be between 3 and 50 characters. " ,
components : [ ] ,
** common ,
} ,
}
end
if usage . length < 5 || usage . length > 400
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " Usage must be between 5 and 400 characters. " ,
** common ,
} ,
}
end
key = APIKey . create (
owner_id : raw [ " member " ] [ " user " ] [ " id " ] ,
application_name : name ,
usage : usage ,
)
if key . errors . present?
Rails . logger . error ( " Failed to create apikey for #{ raw [ 'member' ] [ 'user' ] [ 'id' ] } : #{ key . errors . full_messages . join ( ', ' ) } " )
return {
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " An error occurred while creating your API key. " ,
** common ,
} ,
}
end
{
type : DiscordConstants :: InteractionResponseTypes :: CHANNEL_MESSAGE_WITH_SOURCE ,
data : {
content : " Your API key: ` #{ key . key } `. Provide this in the `Authorization` header. You must still provide a unique user agent. If you have any issues, contact a developer. " ,
** common ,
} ,
}
end
end
end
# rubocop:enable Metrics/BlockNesting
private
def flatten_hash ( hash )
hash . each_with_object ( { } ) do | ( k , v ) , h |
if v . is_a? ( Hash )
flatten_hash ( v ) . map do | h_k , h_v |
2024-10-21 06:32:17 +00:00
h [ :" #{ k } . #{ h_k } " ] = h_v
2024-05-03 03:04:43 +00:00
end
else
h [ k ] = v
end
end
end
def common
{
flags : DiscordConstants :: MessageFlags :: EPHEMERAL ,
allowed_mentions : {
users : [ ] ,
roles : [ ] ,
everyone : [ ] ,
replied_user : false ,
} ,
}
end
end
end