barebones prototype
This commit is contained in:
parent
83f340ea01
commit
b53d33584c
60 changed files with 3743 additions and 1 deletions
211
addons/discord_gd/classes/application_command.gd
Normal file
211
addons/discord_gd/classes/application_command.gd
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
class_name ApplicationCommand
|
||||
"""
|
||||
Represents a Discord application command.
|
||||
"""
|
||||
|
||||
enum COMMAND_TYPES {
|
||||
__,
|
||||
CHAT_INPUT,
|
||||
USER,
|
||||
MESSAGE
|
||||
}
|
||||
|
||||
const _COMMAND_TYPES = {
|
||||
1: 'CHAT_INPUT',
|
||||
2: 'USER',
|
||||
3: 'MESSAGE'
|
||||
}
|
||||
|
||||
enum OPTION_TYPES {
|
||||
__,
|
||||
SUB_COMMAND,
|
||||
SUB_COMMAND_GROUP,
|
||||
STRING,
|
||||
INTEGER,
|
||||
BOOLEAN,
|
||||
USER,
|
||||
CHANNEL,
|
||||
ROLE,
|
||||
MENTIONABLE,
|
||||
NUMBER
|
||||
}
|
||||
|
||||
const _OPTION_TYPES = {
|
||||
1: 'SUB_COMMAND',
|
||||
2: 'SUB_COMMAND_GROUP',
|
||||
3: 'STRING',
|
||||
4: 'INTEGER',
|
||||
5: 'BOOLEAN',
|
||||
6: 'COMMAND',
|
||||
7: 'CHANNEL',
|
||||
8: 'ROLE',
|
||||
9: 'MENTIONABLE',
|
||||
10: 'NUMBER',
|
||||
}
|
||||
|
||||
const _CHANNEL_TYPES = {
|
||||
'GUILD_TEXT': 0,
|
||||
'DM': 1,
|
||||
'GUILD_VOICE': 2,
|
||||
'GROUP_DM': 3,
|
||||
'GUILD_CATEGORY': 4,
|
||||
'GUILD_NEWS': 5,
|
||||
'GUILD_STORE': 6,
|
||||
'GUILD_NEWS_THREAD': 10,
|
||||
'GUILD_PUBLIC_THREAD': 11,
|
||||
'GUILD_PRIVATE_THREAD': 12,
|
||||
'GUILD_STAGE_VOICE': 13
|
||||
}
|
||||
|
||||
var id: String: get = get_id
|
||||
var type: int = 1
|
||||
var application_id: String: get = get_application_id
|
||||
var guild_id: String: get = get_guild_id
|
||||
var name: String: set = set_name, get = get_name
|
||||
var description: String: set = set_description, get = get_description
|
||||
|
||||
var options: Array: set = set_options, get = get_options
|
||||
var default_permission: bool = true
|
||||
var version: String
|
||||
|
||||
func get_id() -> String:
|
||||
return id
|
||||
|
||||
func set_type(p_type: String):
|
||||
type = COMMAND_TYPES[p_type]
|
||||
return self
|
||||
|
||||
func get_type():
|
||||
return _OPTION_TYPES[type]
|
||||
|
||||
func get_application_id() -> String:
|
||||
return application_id
|
||||
|
||||
func set_name(new_name: String):
|
||||
name = new_name
|
||||
return self
|
||||
|
||||
func get_name() -> String:
|
||||
return name
|
||||
|
||||
func set_description(new_description: String):
|
||||
description = new_description
|
||||
return self
|
||||
|
||||
func get_description() -> String:
|
||||
return description
|
||||
|
||||
func get_guild_id() -> String:
|
||||
return guild_id
|
||||
|
||||
func set_options(new_options: Array):
|
||||
options = new_options
|
||||
return self
|
||||
|
||||
func get_options() -> Array:
|
||||
return options
|
||||
|
||||
func add_option(option_data: Dictionary) -> ApplicationCommand:
|
||||
# Generic method to add an option to the command
|
||||
assert(option_data.has('type'), 'ApplicationCommand option must have a type')
|
||||
assert(option_data.has('name') and Helpers.is_valid_str(option_data.name), 'ApplicationCommand option must have a name')
|
||||
assert(option_data.has('description') and Helpers.is_valid_str(option_data.description), 'ApplicationCommand option must have a description')
|
||||
options.append(option_data)
|
||||
return self
|
||||
|
||||
static func sub_command_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.SUB_COMMAND, name, description, data)
|
||||
|
||||
static func sub_command_group_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.SUB_COMMAND_GROUP, name, description, data)
|
||||
|
||||
static func string_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.STRING, name, description, data)
|
||||
|
||||
static func integer_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.INTEGER, name, description, data)
|
||||
|
||||
static func boolean_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.BOOLEAN, name, description, data)
|
||||
|
||||
static func user_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.USER, name, description, data)
|
||||
|
||||
static func channel_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.CHANNEL, name, description, data)
|
||||
|
||||
static func role_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.ROLE, name, description, data)
|
||||
|
||||
static func mentionable_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.MENTIONABLE, name, description, data)
|
||||
|
||||
static func number_option(name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
return _make_option(OPTION_TYPES.NUMBER, name, description, data)
|
||||
|
||||
static func choice(name: String, value) -> Dictionary:
|
||||
return {
|
||||
'name': name,
|
||||
'value': value
|
||||
}
|
||||
|
||||
static func _make_option(type: int, name: String, description: String, data: Dictionary = {}) -> Dictionary:
|
||||
if data.has('channel_types'):
|
||||
for i in range(len(data.channel_types)):
|
||||
if _CHANNEL_TYPES.has(data.channel_types[i]):
|
||||
data.channel_types[i] = _CHANNEL_TYPES[data.channel_types[i]]
|
||||
|
||||
return {
|
||||
'type': type,
|
||||
'name': name,
|
||||
'description': description,
|
||||
# Optional data
|
||||
'required': data.required if data.has('required') else null,
|
||||
'choices': data.choices if data.has('choices') else null,
|
||||
'options': data.options if data.has('options') else null,
|
||||
'channel_types': data.channel_types if data.has('channel_types') else null,
|
||||
'min_value': data.min_value if data.has('min_value') else null,
|
||||
'max_value': data.max_value if data.has('max_value') else null,
|
||||
'autocomplete': data.autocomplete if data.has('autocomplete') else false
|
||||
}
|
||||
|
||||
func _init(data: Dictionary = {}):
|
||||
id = data.id if data.has('id') else ''
|
||||
type = data.type if data.has('type') else 1
|
||||
application_id = data.application_id if data.has('application_id') else ''
|
||||
|
||||
guild_id = data.guild_id if data.has('guild_id') else ''
|
||||
name = data.name if data.has('name') else ''
|
||||
description = data.description if data.has('description') else ''
|
||||
options = data.options if data.has('options') else []
|
||||
default_permission = data.default_permission if data.has('default_permission') else true
|
||||
version = data.version if data.has('version') else ''
|
||||
|
||||
|
||||
func _to_string(pretty: bool = false) -> String:
|
||||
return JSON.stringify(_to_dict(), '\t') if pretty else JSON.stringify(_to_dict())
|
||||
|
||||
func print():
|
||||
print(_to_string(true))
|
||||
|
||||
func _to_dict(is_register = false) -> Dictionary:
|
||||
if is_register:
|
||||
return {
|
||||
'name': name,
|
||||
'type': type,
|
||||
'description': description,
|
||||
'default_permission': default_permission,
|
||||
'options': options
|
||||
}
|
||||
|
||||
return {
|
||||
'id': id,
|
||||
'type': type,
|
||||
'application_id': application_id,
|
||||
'guild_id': '',
|
||||
'name': name,
|
||||
'description': description,
|
||||
'options': options,
|
||||
'default_permission': default_permission,
|
||||
'version': version
|
||||
}
|
||||
1
addons/discord_gd/classes/application_command.gd.uid
Normal file
1
addons/discord_gd/classes/application_command.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cpk5b2jequ6my
|
||||
126
addons/discord_gd/classes/bit_field.gd
Normal file
126
addons/discord_gd/classes/bit_field.gd
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
class_name BitField
|
||||
"""
|
||||
Helper class for bit operations.
|
||||
"""
|
||||
|
||||
var default_bit = 0
|
||||
var FLAGS = {}
|
||||
|
||||
var bitfield: int
|
||||
|
||||
|
||||
func any(bit):
|
||||
return (bitfield & resolve(bit)) != default_bit
|
||||
|
||||
func equals(bit):
|
||||
return bitfield == resolve(bit)
|
||||
|
||||
func has(bit):
|
||||
bit = resolve(bit)
|
||||
return (bitfield & bit) == bit
|
||||
|
||||
func missing(bits):
|
||||
pass
|
||||
|
||||
func add(bits):
|
||||
if not typeof(bits) == TYPE_ARRAY:
|
||||
bits = [bits]
|
||||
var total = default_bit
|
||||
for bit in bits:
|
||||
total |= resolve(bit)
|
||||
bitfield |= total
|
||||
return self
|
||||
|
||||
func remove(bits):
|
||||
|
||||
if typeof(bits) == TYPE_OBJECT and bits.is_class(self.get_class()):
|
||||
bits = bits.bitfield
|
||||
|
||||
if not typeof(bits) == TYPE_ARRAY:
|
||||
bits = [bits]
|
||||
|
||||
var total = default_bit
|
||||
for bit in bits:
|
||||
total |= resolve(bit)
|
||||
bitfield &= ~total
|
||||
return self
|
||||
|
||||
func serialize():
|
||||
var serialized = {}
|
||||
|
||||
var flags = FLAGS.keys()
|
||||
var bits = FLAGS.values()
|
||||
|
||||
var i = 0
|
||||
for flag in flags:
|
||||
var bit = bits[i]
|
||||
serialized[flag] = has(bit)
|
||||
i += 1
|
||||
|
||||
return serialized
|
||||
|
||||
func to_array():
|
||||
var ret = []
|
||||
|
||||
var flags = FLAGS.keys()
|
||||
var bits = FLAGS.values()
|
||||
|
||||
var i = 0
|
||||
for flag in flags:
|
||||
var bit = bits[i]
|
||||
if has(bit):
|
||||
ret.append(flag)
|
||||
i += 1
|
||||
|
||||
return ret
|
||||
|
||||
func resolve(bit):
|
||||
if typeof(default_bit) == TYPE_INT or typeof(default_bit) == TYPE_FLOAT:
|
||||
default_bit = int(default_bit)
|
||||
|
||||
if typeof(bit) == TYPE_INT or typeof(bit) == TYPE_FLOAT:
|
||||
bit = int(bit)
|
||||
|
||||
if typeof(default_bit) == typeof(bit):
|
||||
if bit >= default_bit:
|
||||
return bit
|
||||
|
||||
if typeof(bit) == TYPE_OBJECT and bit.is_class(self.get_class()):
|
||||
return bit.bitfield
|
||||
|
||||
if (typeof(bit) == TYPE_ARRAY):
|
||||
var ret = default_bit
|
||||
|
||||
for b in bit:
|
||||
ret = ret | resolve(b)
|
||||
return ret
|
||||
|
||||
if (Helpers.is_valid_str(bit)):
|
||||
if (FLAGS.has(bit)):
|
||||
return FLAGS[bit]
|
||||
|
||||
if (not is_nan(float(bit))):
|
||||
return int(bit)
|
||||
|
||||
assert(false, 'Bitfield is invalid.')
|
||||
|
||||
|
||||
func _init(bits = default_bit):
|
||||
if bits == null:
|
||||
bits = default_bit
|
||||
bitfield = resolve(bits)
|
||||
|
||||
|
||||
func _to_dict():
|
||||
if typeof(bitfield) == TYPE_INT:
|
||||
return bitfield
|
||||
else:
|
||||
return str(bitfield)
|
||||
|
||||
|
||||
func value_of():
|
||||
return bitfield
|
||||
|
||||
|
||||
func _to_string():
|
||||
return str(bitfield)
|
||||
1
addons/discord_gd/classes/bit_field.gd.uid
Normal file
1
addons/discord_gd/classes/bit_field.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://csgxh04sf3r75
|
||||
343
addons/discord_gd/classes/discord_interaction.gd
Normal file
343
addons/discord_gd/classes/discord_interaction.gd
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
class_name DiscordInteraction
|
||||
"""
|
||||
Represents a Discord interaction.
|
||||
"""
|
||||
|
||||
var bot
|
||||
var replied = false
|
||||
var deferred = false
|
||||
var ephemeral = false
|
||||
|
||||
# Compulsory
|
||||
var id: String
|
||||
var application_id: String
|
||||
var type: String
|
||||
var token: String
|
||||
|
||||
# Optional
|
||||
var message: Message
|
||||
var channel_id: String
|
||||
var guild_id: String
|
||||
var member: Dictionary
|
||||
var data: Dictionary
|
||||
|
||||
var RESPONSE_TYPES = {
|
||||
'CHANNEL_MESSAGE_WITH_SOURCE': 4,
|
||||
'DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE': 5,
|
||||
'DEFERRED_UPDATE_MESSAGE': 6,
|
||||
'UPDATE_MESSAGE': 7,
|
||||
'APPLICATION_COMMAND_AUTOCOMPLETE_RESULT': 8
|
||||
}
|
||||
|
||||
var TYPES = {
|
||||
1: 'PING',
|
||||
2: 'APPLICATION_COMMAND',
|
||||
3: 'MESSAGE_COMPONENT',
|
||||
4: 'APPLICATION_COMMAND_AUTOCOMPLETE'
|
||||
}
|
||||
|
||||
|
||||
func is_command() -> bool:
|
||||
return type == 'APPLICATION_COMMAND'
|
||||
|
||||
|
||||
func is_autocomplete() -> bool:
|
||||
return type == 'APPLICATION_COMMAND_AUTOCOMPLETE'
|
||||
|
||||
|
||||
func is_message_component() -> bool:
|
||||
return type == 'MESSAGE_COMPONENT'
|
||||
|
||||
|
||||
func is_button() -> bool:
|
||||
return is_message_component() and data.component_type == 2
|
||||
|
||||
|
||||
func is_select_menu() -> bool:
|
||||
return is_message_component() and data.component_type == 3
|
||||
|
||||
|
||||
func in_guild() -> bool:
|
||||
return guild_id != '' and member != {}
|
||||
|
||||
|
||||
func respond_autocomplete(choices: Array):
|
||||
var payload = {
|
||||
'type': RESPONSE_TYPES['APPLICATION_COMMAND_AUTOCOMPLETE_RESULT'],
|
||||
'data': {
|
||||
'choices': choices
|
||||
}
|
||||
}
|
||||
var res = await bot._send_request('/interactions/%s/%s/callback' % [id, token], payload)
|
||||
return res
|
||||
|
||||
|
||||
func fetch_reply(message_id: String = '@original'):
|
||||
#assert(not ephemeral, 'Unable to fetch ephemeral Interaction reply.')
|
||||
if ephemeral:
|
||||
push_error('Unable to fetch ephemeral reply.')
|
||||
return
|
||||
|
||||
var msg = await bot._send_get('/webhooks/%s/%s/messages/%s' % [application_id, token, message_id])
|
||||
await bot._parse_message(msg)
|
||||
|
||||
return Message.new(msg)
|
||||
|
||||
|
||||
func reply(options: Dictionary):
|
||||
if replied or deferred:
|
||||
push_error('Already replied to Interaction.')
|
||||
return
|
||||
|
||||
options.type = RESPONSE_TYPES['CHANNEL_MESSAGE_WITH_SOURCE']
|
||||
var res = await _send_request('/interactions/%s/%s/callback' % [id, token], options)
|
||||
replied = true
|
||||
|
||||
return res
|
||||
|
||||
|
||||
func edit_reply(options: Dictionary):
|
||||
if (not replied) and (not deferred):
|
||||
push_error('Unable to edit Interaction. Not replied.')
|
||||
return
|
||||
|
||||
var res = await _edit_message('@original', options)
|
||||
replied = true
|
||||
return res
|
||||
|
||||
|
||||
func delete_reply():
|
||||
if ephemeral:
|
||||
push_error('Unable to delete ephemeral Interaction reply.')
|
||||
return
|
||||
|
||||
return await _delete_message()
|
||||
|
||||
|
||||
func defer_reply(options: Dictionary = {}):
|
||||
if replied or deferred:
|
||||
push_error('Already replied to Interaction.')
|
||||
return
|
||||
|
||||
options.type = RESPONSE_TYPES['DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE']
|
||||
var res = await _send_request('/interactions/%s/%s/callback' % [id, token], options)
|
||||
deferred = true
|
||||
return res
|
||||
|
||||
|
||||
func update(options: Dictionary):
|
||||
if replied or deferred:
|
||||
push_error('Already replied to Interaction.')
|
||||
return
|
||||
|
||||
options.type = RESPONSE_TYPES['UPDATE_MESSAGE']
|
||||
var msg = await _send_request('/interactions/%s/%s/callback' % [id, token], options)
|
||||
replied = true
|
||||
return msg
|
||||
|
||||
|
||||
func defer_update(options: Dictionary = {}):
|
||||
if replied or deferred:
|
||||
push_error('Already replied to Interaction.')
|
||||
return
|
||||
|
||||
options.type = RESPONSE_TYPES['DEFERRED_UPDATE_MESSAGE']
|
||||
var res = await _send_request('/interactions/%s/%s/callback' % [id, token], options)
|
||||
deferred = true
|
||||
return res
|
||||
|
||||
|
||||
func follow_up(options: Dictionary):
|
||||
options.type = RESPONSE_TYPES['CHANNEL_MESSAGE_WITH_SOURCE']
|
||||
var res = await _send_request(
|
||||
'/webhooks/%s/%s' % [application_id, token], options, HTTPClient.METHOD_POST, true
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
func edit_follow_up(msg: Message, options: Dictionary):
|
||||
var res = await _edit_message(msg.id, options)
|
||||
return res
|
||||
|
||||
|
||||
func delete_follow_up(msg: Message):
|
||||
var res = await _delete_message(msg.id)
|
||||
return res
|
||||
|
||||
|
||||
func has(attribute):
|
||||
return true if self[attribute] else false
|
||||
|
||||
|
||||
func _delete_message(message_id: String = '@original'):
|
||||
var res = await bot._send_get(
|
||||
'/webhooks/%s/%s/messages/%s' % [application_id, token, message_id],
|
||||
HTTPClient.METHOD_DELETE
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
func _edit_message(message_id: String, options: Dictionary):
|
||||
options.type = RESPONSE_TYPES['CHANNEL_MESSAGE_WITH_SOURCE']
|
||||
var msg = await _send_request(
|
||||
'/webhooks/%s/%s/messages/%s' % [application_id, token, message_id],
|
||||
options,
|
||||
HTTPClient.METHOD_PATCH
|
||||
)
|
||||
return msg
|
||||
|
||||
|
||||
func _send_request(
|
||||
slug: String, options: Dictionary, method = HTTPClient.METHOD_POST, is_follow_up = false
|
||||
):
|
||||
var files = []
|
||||
if options.has('files'):
|
||||
files = options.files
|
||||
options.erase('files')
|
||||
|
||||
var _type = options.type
|
||||
options.erase('type')
|
||||
|
||||
options.attachments = message.attachments if message != null else []
|
||||
|
||||
if options.has('ephemeral') and typeof(options.ephemeral) == TYPE_BOOL:
|
||||
ephemeral = options.ephemeral
|
||||
options.erase('ephemeral')
|
||||
|
||||
var _fetch_reply = false
|
||||
if options.has('fetch_reply'):
|
||||
_fetch_reply = options.fetch_reply
|
||||
options.erase('fetch_reply')
|
||||
|
||||
var _embeds = []
|
||||
if options.has('embeds') and options.embeds.size() > 0:
|
||||
for embed in options.embeds:
|
||||
if typeof(embed) == TYPE_DICTIONARY:
|
||||
_embeds.append(embed)
|
||||
else:
|
||||
_embeds.append(embed._to_dict())
|
||||
|
||||
var _components = []
|
||||
if options.has('components') and options.components.size() > 0:
|
||||
for component in options.components:
|
||||
if typeof(component) == TYPE_DICTIONARY:
|
||||
_components.append(component)
|
||||
else:
|
||||
_components.append(component._to_dict())
|
||||
|
||||
var payload = {
|
||||
'type': _type,
|
||||
'data':
|
||||
{
|
||||
'tts': options.tts if options.has('tts') else false,
|
||||
'content': options.content if options.has('content') else null,
|
||||
'embeds': _embeds,
|
||||
'allowed_mentions': options.allowed_mentions if options.has('allowed_mentions') else {},
|
||||
'attachments': options.attachments if options.has('attachments') else [],
|
||||
'flags': MessageFlags.new('EPHEMERAL') if ephemeral else null,
|
||||
'components': _components
|
||||
}
|
||||
}
|
||||
|
||||
if _type == RESPONSE_TYPES['UPDATE_MESSAGE']:
|
||||
# Append the message parts from the original message if the options doesnt contain that part
|
||||
if not options.has('tts'):
|
||||
payload.data.tts = message.tts
|
||||
if not options.has('content'):
|
||||
payload.data.content = message.content
|
||||
if not options.has('embeds'):
|
||||
payload.data.embeds = message.embeds
|
||||
if not options.has('components'):
|
||||
payload.data.components = message.components
|
||||
|
||||
if method == HTTPClient.METHOD_PATCH or is_follow_up:
|
||||
payload = payload.data
|
||||
|
||||
var res
|
||||
var coroutine = await bot._send_raw_request(slug, {'payload': payload, 'files': files}, method)
|
||||
|
||||
if is_follow_up:
|
||||
coroutine = await bot._parse_message(res)
|
||||
|
||||
return Message.new(res)
|
||||
|
||||
if _fetch_reply:
|
||||
return await fetch_reply('@original')
|
||||
else:
|
||||
return true
|
||||
|
||||
|
||||
func _init(_bot, interaction: Dictionary):
|
||||
bot = _bot
|
||||
assert(Helpers.is_valid_str(interaction.id), 'Interaction must have an id')
|
||||
assert(
|
||||
Helpers.is_valid_str(interaction.application_id), 'Interaction must have an application id'
|
||||
)
|
||||
assert(Helpers.is_valid_str(interaction.token), 'Interaction must have a token')
|
||||
assert(interaction.has('type'), 'Interaction must have a type')
|
||||
assert(Helpers.is_num(interaction.version), 'Interaction must have a version')
|
||||
|
||||
id = interaction.id
|
||||
application_id = interaction.application_id
|
||||
token = interaction.token
|
||||
type = TYPES[int(interaction.type)]
|
||||
|
||||
if interaction.has('member'):
|
||||
member = interaction.member
|
||||
# Try to parse the member permissions
|
||||
if member.has('permissions'):
|
||||
member.permissions = Permissions.new(member.permissions)
|
||||
|
||||
# Try to parse the member user
|
||||
if member.has('user'):
|
||||
member.user = User.new(bot, member.user)
|
||||
|
||||
if interaction.has('guild_id'):
|
||||
guild_id = interaction.guild_id
|
||||
|
||||
if interaction.has('channel_id'):
|
||||
channel_id = interaction.channel_id
|
||||
|
||||
if interaction.has('data'):
|
||||
data = interaction.data
|
||||
if type == 'APPLICATION_COMMAND':
|
||||
data.type = ApplicationCommand._COMMAND_TYPES[int(data.type)]
|
||||
data = _parse_data_options(interaction.data)
|
||||
|
||||
if interaction.has('message'):
|
||||
await bot._parse_message(interaction.message)
|
||||
|
||||
message = Message.new(interaction.message)
|
||||
|
||||
|
||||
func _parse_data_options(data, option = false):
|
||||
if option and data.has('type'):
|
||||
data.type = ApplicationCommand._OPTION_TYPES[int(data.type)]
|
||||
|
||||
if data.has('options'):
|
||||
for i in range(len(data.options)):
|
||||
data.options[i] = _parse_data_options(data.options[i], true)
|
||||
return data
|
||||
|
||||
|
||||
func _to_string(pretty: bool = false) -> String:
|
||||
return JSON.stringify(_to_dict(), '\t') if pretty else JSON.stringify(_to_dict())
|
||||
|
||||
|
||||
func print():
|
||||
print(_to_string(true))
|
||||
|
||||
|
||||
func _to_dict() -> Dictionary:
|
||||
return {
|
||||
'version': 1,
|
||||
'type': type,
|
||||
'token': token,
|
||||
'message': message._to_string() if message is Message else {},
|
||||
'member': member,
|
||||
'id': id,
|
||||
'guild_id': guild_id,
|
||||
'data': data,
|
||||
'channel_id': channel_id,
|
||||
'application_id': application_id,
|
||||
}
|
||||
1
addons/discord_gd/classes/discord_interaction.gd.uid
Normal file
1
addons/discord_gd/classes/discord_interaction.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dklo5j3tsw3qa
|
||||
237
addons/discord_gd/classes/embed.gd
Normal file
237
addons/discord_gd/classes/embed.gd
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
class_name Embed
|
||||
"""
|
||||
Stores data about a Discord Embed
|
||||
and has functions to add, modify and edit
|
||||
the various properties of an Embed.
|
||||
"""
|
||||
|
||||
var title: String: set = set_title, get = get_title
|
||||
var type: String = 'rich': set = set_type, get = get_type
|
||||
var description: String: set = set_description, get = get_description
|
||||
var url: String: set = set_url, get = get_url
|
||||
var timestamp: String: set = set_timestamp, get = get_timestamp
|
||||
var color: set = set_color, get = get_color
|
||||
|
||||
var footer = null
|
||||
var image = null
|
||||
var thumbnail = null
|
||||
var video = null
|
||||
var provider = null
|
||||
var author = null
|
||||
var fields: Array
|
||||
|
||||
|
||||
func get_title():
|
||||
return title if Helpers.is_valid_str(title) else null
|
||||
|
||||
|
||||
func get_type():
|
||||
return type if Helpers.is_valid_str(type) else null
|
||||
|
||||
|
||||
func get_description():
|
||||
return description if Helpers.is_valid_str(description) else null
|
||||
|
||||
|
||||
func get_url():
|
||||
return url if Helpers.is_valid_str(url) else null
|
||||
|
||||
|
||||
func get_timestamp():
|
||||
return timestamp if Helpers.is_valid_str(timestamp) else null
|
||||
|
||||
|
||||
func get_color():
|
||||
return color
|
||||
|
||||
|
||||
func set_title(_title):
|
||||
assert(Helpers.is_valid_str(_title), 'Invalid Type: title of Embed must be a String')
|
||||
assert(_title.length() <= 256, 'title of Embed must be <= 256 characters')
|
||||
title = _title
|
||||
return self
|
||||
|
||||
|
||||
func set_type(_type):
|
||||
assert(Helpers.is_valid_str(_type), 'Invalid Type: type of Embed must be a String')
|
||||
type = _type
|
||||
return self
|
||||
|
||||
|
||||
func set_description(_description):
|
||||
assert(
|
||||
Helpers.is_valid_str(_description), 'Invalid Type: description of Embed must be a String'
|
||||
)
|
||||
assert(_description.length() <= 4096, 'Embed description must be <= 4096 characters')
|
||||
description = _description
|
||||
return self
|
||||
|
||||
|
||||
func set_url(_url):
|
||||
assert(Helpers.is_valid_str(_url), 'Invalid Type: url of Embed must be a String')
|
||||
url = _url
|
||||
return self
|
||||
|
||||
|
||||
func set_timestamp(_timestamp = ''):
|
||||
timestamp = Helpers.make_iso_string()
|
||||
return self
|
||||
|
||||
|
||||
func set_color(_color):
|
||||
# RBG color
|
||||
if typeof(_color) == TYPE_ARRAY:
|
||||
color = (int(_color[0]) * 256 * 256) + (int(_color[1]) * 256) + int(_color[2])
|
||||
|
||||
# Hex color
|
||||
elif typeof(_color) == TYPE_STRING and _color.begins_with('#'):
|
||||
color = _color.replace('#', '0x').hex_to_int()
|
||||
|
||||
# Decimal color
|
||||
elif _color.is_valid_integer:
|
||||
color = int(_color)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
func set_footer(text: String, icon_url: String = '', proxy_icon_url: String = ''):
|
||||
assert(Helpers.is_valid_str(text), 'Invalid Type: footer text of Embed must be a valid String')
|
||||
assert(text.length() <= 2048, 'Embed footer text must be <= 2048 characters')
|
||||
|
||||
footer = {'text': text, 'icon_url': icon_url, 'proxy_icon_url': proxy_icon_url}
|
||||
return self
|
||||
|
||||
|
||||
func set_image(url: String, width: int = -1, height: int = -1, proxy_url: String = ''):
|
||||
assert(Helpers.is_valid_str(url), 'Invalid Type: image url of Embed must be a valid String')
|
||||
image = {
|
||||
'url': url,
|
||||
'width': width if width != -1 else null,
|
||||
'height': height if height != -1 else null,
|
||||
'proxy_url': proxy_url if Helpers.is_valid_str(proxy_url) else null
|
||||
}
|
||||
return self
|
||||
|
||||
|
||||
func set_thumbnail(url: String, width: int = -1, height: int = -1, proxy_url: String = ''):
|
||||
assert(Helpers.is_valid_str(url), 'Embed thumbnail url must be a valid String')
|
||||
thumbnail = {
|
||||
'url': url,
|
||||
'width': width if width != -1 else null,
|
||||
'height': height if height != -1 else null,
|
||||
'proxy_url': proxy_url if Helpers.is_valid_str(proxy_url) else null
|
||||
}
|
||||
return self
|
||||
|
||||
|
||||
func set_video(url: String, width: int = -1, height: int = -1, proxy_url: String = ''):
|
||||
assert(Helpers.is_valid_str(url), 'Invalid Type: video url of Embed must be a valid String')
|
||||
video = {
|
||||
'url': url,
|
||||
'width': width if width != -1 else null,
|
||||
'height': height if height != -1 else null,
|
||||
'proxy_url': proxy_url if Helpers.is_valid_str(proxy_url) else null
|
||||
}
|
||||
return self
|
||||
|
||||
|
||||
func set_provider(name: String, url: String = ''):
|
||||
assert(
|
||||
Helpers.is_valid_str(name), 'Invalid Type: provider name of Embed must be a valid String'
|
||||
)
|
||||
provider = {'name': name, 'url': url if Helpers.is_valid_str(url) else null}
|
||||
return self
|
||||
|
||||
|
||||
func set_author(
|
||||
name: String, url: String = '', icon_url: String = '', proxy_icon_url: String = ''
|
||||
):
|
||||
assert(Helpers.is_valid_str(name), 'Invalid Type: author name of Embed must be a valid String')
|
||||
assert(name.length() <= 256, 'Embed author name must be <= 256 characters')
|
||||
|
||||
author = {
|
||||
'name': name,
|
||||
'url': url if Helpers.is_valid_str(url) else null,
|
||||
'icon_url': icon_url if Helpers.is_valid_str(icon_url) else null,
|
||||
'proxy_icon_url': proxy_icon_url if Helpers.is_valid_str(proxy_icon_url) else null
|
||||
}
|
||||
return self
|
||||
|
||||
|
||||
func add_field(name: String, value: String, inline: bool = false, index = -1):
|
||||
assert(Helpers.is_valid_str(name), 'Invalid Type: field name of Embed must be a valid String')
|
||||
assert(Helpers.is_valid_str(value), 'Invalid Type: field value of Embed must be a valid String')
|
||||
|
||||
assert(name.length() <= 256, 'Embed field name must be <= 256 characters')
|
||||
assert(value.length() <= 1024, 'Embed field value must be <= 1024 characters')
|
||||
assert(fields.size() <= 25, 'Embed can have a max of 25 fields')
|
||||
|
||||
var new_field = {'name': name, 'value': value, 'inline': inline}
|
||||
if index == -1:
|
||||
fields.append(new_field)
|
||||
else:
|
||||
fields.insert(index, new_field)
|
||||
return self
|
||||
|
||||
|
||||
func slice_fields(index: int, delete_count: int = 1, replace_fields: Array = []):
|
||||
var n = fields.size()
|
||||
assert(Helpers.is_num(index), 'Missing index must be provided to Embed.slice_fields')
|
||||
assert(index > -1 and index < n, 'index out of bounds in Embed.slice_fields')
|
||||
|
||||
var max_deletable = n - index
|
||||
assert(delete_count <= max_deletable, 'delete_count out of bounds in Embed.slice_fields')
|
||||
|
||||
while delete_count != 0:
|
||||
fields.remove_at(index)
|
||||
delete_count -= 1
|
||||
|
||||
if replace_fields.size() != 0:
|
||||
# add fields
|
||||
for field in replace_fields:
|
||||
var inline = false
|
||||
if field.size() == 3:
|
||||
inline = field[2]
|
||||
add_field(field[0], field[1], inline, index)
|
||||
index += 1
|
||||
|
||||
return self
|
||||
|
||||
|
||||
func _to_string(pretty: bool = false) -> String:
|
||||
return JSON.stringify(_to_dict(), '\t') if pretty else JSON.stringify(_to_dict())
|
||||
|
||||
func print():
|
||||
print(_to_string(true))
|
||||
|
||||
func _to_dict() -> Dictionary:
|
||||
var total = title + description
|
||||
|
||||
if footer and footer.text:
|
||||
total += footer.text
|
||||
|
||||
if author and author.name:
|
||||
total += author.name
|
||||
|
||||
for field in fields:
|
||||
total += field.name
|
||||
total += field.value
|
||||
|
||||
total = str(total).length()
|
||||
assert(total <= 6000, 'Embed content must be <= 6000 characters in total')
|
||||
|
||||
return {
|
||||
'title': title,
|
||||
'type': type,
|
||||
'description': description,
|
||||
'url': url,
|
||||
'timestamp': timestamp,
|
||||
'color': color,
|
||||
'footer': footer,
|
||||
'image': image,
|
||||
'thumbnail': thumbnail,
|
||||
'video': video,
|
||||
'provider': provider,
|
||||
'author': author,
|
||||
'fields': fields
|
||||
}
|
||||
1
addons/discord_gd/classes/embed.gd.uid
Normal file
1
addons/discord_gd/classes/embed.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cx0cqy20w5ygt
|
||||
75
addons/discord_gd/classes/helpers.gd
Normal file
75
addons/discord_gd/classes/helpers.gd
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
class_name Helpers
|
||||
"""
|
||||
General purpose Helpers functions
|
||||
used by discord.gd plugin
|
||||
"""
|
||||
|
||||
# Returns true if value if an int or real float
|
||||
static func is_num(value) -> bool:
|
||||
return typeof(value) == TYPE_INT or typeof(value) == TYPE_FLOAT
|
||||
|
||||
|
||||
# Returns true if value is a string
|
||||
static func is_str(value) -> bool:
|
||||
return typeof(value) == TYPE_STRING
|
||||
|
||||
|
||||
# Returns true if the string has more than 1 character
|
||||
static func is_valid_str(value) -> bool:
|
||||
return is_str(value) and value.length() > 0
|
||||
|
||||
|
||||
# Return a ISO 8601 timestamp as a String
|
||||
static func make_iso_string(datetime: Dictionary = Time.get_datetime_dict_from_system(true)) -> String:
|
||||
var iso_string = '%s-%02d-%02dT%02d:%02d:%02d' % [datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second]
|
||||
|
||||
return iso_string
|
||||
|
||||
|
||||
# Pretty prints a Dictionary
|
||||
static func print_dict(d: Dictionary) -> void:
|
||||
print(JSON.stringify(d, '\t'))
|
||||
|
||||
|
||||
# Saves a Dictionary to a file for debugging large dictionaries
|
||||
static func save_dict(d: Dictionary, filename = 'saved_dict') -> void:
|
||||
assert(typeof(d) == TYPE_DICTIONARY, 'type of d is not Dictionary in save_dict')
|
||||
var file = FileAccess.open('user://%s%s.json' % [filename, str(Time.get_ticks_msec())], FileAccess.WRITE)
|
||||
file.store_string(JSON.stringify(d, '\t'))
|
||||
file.close()
|
||||
print('Dictionary saved to file')
|
||||
|
||||
|
||||
# Converts a raw image bytes to a png Image
|
||||
static func to_png_image(bytes: PackedByteArray) -> Image:
|
||||
var image = Image.new()
|
||||
image.load_png_from_buffer(bytes)
|
||||
return image
|
||||
|
||||
|
||||
# Converts a Image to ImageTexture
|
||||
static func to_image_texture(image: Image) -> ImageTexture:
|
||||
var texture = ImageTexture.new()
|
||||
texture.create_from_image(image)
|
||||
return texture
|
||||
|
||||
|
||||
# Ensures that the String's length is less than or equal to the specified length
|
||||
static func assert_length(variable: String, length: int, msg: String):
|
||||
assert(variable.length() <= length, msg)
|
||||
|
||||
|
||||
# Convert the ISO string to a unix timestamp
|
||||
static func iso2unix(iso_string: String) -> int:
|
||||
var date := iso_string.split("T")[0].split("-")
|
||||
var time := iso_string.split("T")[1].trim_suffix("Z").split(":")
|
||||
|
||||
var datetime = {
|
||||
year = date[0],
|
||||
month = date[1],
|
||||
day = date[2],
|
||||
hour = time[0],
|
||||
minute = time[1],
|
||||
second = time[2],
|
||||
}
|
||||
return Time.get_unix_time_from_datetime_dict(datetime)
|
||||
1
addons/discord_gd/classes/helpers.gd.uid
Normal file
1
addons/discord_gd/classes/helpers.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://q15tb8w4uum
|
||||
190
addons/discord_gd/classes/message.gd
Normal file
190
addons/discord_gd/classes/message.gd
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
class_name Message
|
||||
"""
|
||||
Represents a Discord Message.
|
||||
"""
|
||||
|
||||
var id: String
|
||||
var channel_id: String
|
||||
var guild_id: String
|
||||
var content: String
|
||||
var timestamp: String
|
||||
var edited_timestamp: String
|
||||
var webhook_id: String
|
||||
var type: String
|
||||
|
||||
var author: User
|
||||
var member: Dictionary
|
||||
var activity: Dictionary
|
||||
var message_reference: Dictionary
|
||||
var referenced_message: Dictionary
|
||||
|
||||
var tts: bool
|
||||
var mention_everyone: bool
|
||||
var pinned: bool
|
||||
|
||||
var mentions: Array
|
||||
var mention_roles: Array
|
||||
var mention_channels: Array
|
||||
var attachments: Array
|
||||
var components: Array
|
||||
var embeds: Array
|
||||
var reactions: Array
|
||||
var flags: MessageFlags
|
||||
|
||||
var nonce
|
||||
|
||||
var MESSAGE_TYPES = {
|
||||
'0': 'DEFAULT',
|
||||
'1': 'RECIPIENT_ADD',
|
||||
'2': 'RECIPIENT_REMOVE',
|
||||
'3': 'CALL',
|
||||
'4': 'CHANNEL_NAME_CHANGE',
|
||||
'5': 'CHANNEL_ICON_CHANGE',
|
||||
'6': 'CHANNEL_PINNED_MESSAGE',
|
||||
'7': 'GUILD_MEMBER_JOIN',
|
||||
'8': 'USER_PREMIUM_GUILD_SUBSCRIPTION',
|
||||
'9': 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1',
|
||||
'10': 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2',
|
||||
'11': 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3',
|
||||
'12': 'CHANNEL_FOLLOW_ADD',
|
||||
'14': 'GUILD_DISCOVERY_DISQUALIFIED',
|
||||
'15': 'GUILD_DISCOVERY_REQUALIFIED',
|
||||
'16': 'GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING',
|
||||
'17': 'GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING',
|
||||
'18': 'THREAD_CREATED',
|
||||
'19': 'REPLY',
|
||||
'20': 'APPLICATION_COMMAND',
|
||||
'21': 'THREAD_STARTER_MESSAGE',
|
||||
'22': 'GUILD_INVITE_REMINDER'
|
||||
}
|
||||
|
||||
func _init(message: Dictionary):
|
||||
# Compulsory
|
||||
assert(typeof(message) == TYPE_DICTIONARY, 'Invalid type: message must be a Dictionary')
|
||||
assert(message.id, 'Message must have an id')
|
||||
id = message.id
|
||||
assert(message.has('type'), 'Message must have a type')
|
||||
|
||||
assert(message.has('channel_id') and message.channel_id and Helpers.is_valid_str(message.channel_id), 'Message must have a valid channel_id')
|
||||
channel_id = message.channel_id
|
||||
|
||||
if MESSAGE_TYPES.get(str(int(message.type))):
|
||||
type = MESSAGE_TYPES.get(str(int(message.type)))
|
||||
else:
|
||||
assert(false, 'Message must have a valid type')
|
||||
|
||||
assert(message.has('author'), 'Message must have an author')
|
||||
|
||||
# Check if the message is sent by webhook
|
||||
if message.has('webhook_id') and Helpers.is_str(message.webhook_id) and message.webhook_id.length() > 0:
|
||||
# webhook sent a message
|
||||
pass
|
||||
else:
|
||||
# sent by user
|
||||
assert(message.author is User, 'author attribute of Mesage must be of type User')
|
||||
author = message.author
|
||||
|
||||
if message.has('flags'):
|
||||
flags = MessageFlags.new(message.flags)
|
||||
|
||||
# if message.channel.type != 'DM':
|
||||
# assert(message.has('guild_id') and message.guild_id and Helpers.is_valid_str(message.guild_id), 'Message must have a valid guild_id')
|
||||
# guild_id = message.guild_id
|
||||
|
||||
if message.has('guild_id') and message.guild_id:
|
||||
guild_id = message.guild_id
|
||||
|
||||
#if not message.has('webhook_id'):
|
||||
#assert(message.content.length() > 0 or message.embeds.size() > 0 or message.components.size() > 0 or message.attachments.size() > 0, 'Message must have a content or at least one of (embeds, components or attachments)')
|
||||
content = message.content
|
||||
|
||||
assert(message.timestamp, 'Message must have a timestamp')
|
||||
timestamp = message.timestamp
|
||||
|
||||
|
||||
|
||||
# Optional
|
||||
if message.has('edited_timestamp') and message.edited_timestamp != null:
|
||||
edited_timestamp = message.edited_timestamp
|
||||
|
||||
if message.has('tts'):
|
||||
tts = true if message.tts else false
|
||||
|
||||
if message.has('mention_everyone'):
|
||||
mention_everyone = true if message.mention_everyone else false
|
||||
|
||||
if message.has('mentions') and typeof(message.mentions) == TYPE_ARRAY and message.mentions.size() > 0:
|
||||
mentions = message.mentions
|
||||
|
||||
if message.has('member') and message.member:
|
||||
member = message.member
|
||||
if message.has('mention_roles') and message.mention_roles:
|
||||
mention_roles = message.mention_roles
|
||||
if message.has('mention_channels') and message.mention_channels:
|
||||
mention_channels = message.mention_channels
|
||||
if message.has('attachments') and message.attachments:
|
||||
attachments = message.attachments
|
||||
if message.has('components') and message.components:
|
||||
components = message.components
|
||||
if message.has('embeds') and message.embeds:
|
||||
embeds = message.embeds
|
||||
if message.has('reactions') and message.reactions:
|
||||
reactions = message.reactions
|
||||
if message.has('pinned') and typeof(message.pinned) == TYPE_BOOL:
|
||||
pinned = message.pinned
|
||||
if message.has('message_reference') and message.message_reference:
|
||||
message_reference = message.message_reference
|
||||
if message.has('referenced_message') and message.referenced_message:
|
||||
referenced_message = message.referenced_message
|
||||
func _to_string(pretty: bool = false):
|
||||
var data = {
|
||||
'id': id,
|
||||
'channel_id': channel_id,
|
||||
'guild_id': guild_id,
|
||||
'author': author,
|
||||
'member': member,
|
||||
'content': content,
|
||||
'timestamp': timestamp,
|
||||
'edited_timestamp': edited_timestamp,
|
||||
'tts': tts,
|
||||
'mention_everyone': mention_everyone,
|
||||
'mentions': mentions,
|
||||
'mention_roles': mention_roles,
|
||||
'mention_channels': mention_channels,
|
||||
'attachments': attachments,
|
||||
'components': components,
|
||||
'embeds': embeds,
|
||||
'reactions': reactions,
|
||||
'pinned': pinned,
|
||||
'type': type,
|
||||
'message_reference': message_reference,
|
||||
'referenced_message': referenced_message,
|
||||
'flags': flags.bitfield
|
||||
}
|
||||
|
||||
return JSON.stringify(data, '\t') if pretty else JSON.stringify(data)
|
||||
|
||||
func print():
|
||||
print(_to_string(true))
|
||||
|
||||
func has(attribute):
|
||||
return true if self[attribute] else false
|
||||
|
||||
func slice_attachments(index: int, delete_count: int = 1, replace_attachments: Array = []):
|
||||
var n = attachments.size()
|
||||
assert(Helpers.is_num(index), 'index must be provided to Message.slice_attachments')
|
||||
assert(index > -1 and index < n, 'index out of bounds in Message.slice_attachments')
|
||||
|
||||
var max_deletable = n - index
|
||||
assert(delete_count <= max_deletable, 'delete_count out of bounds in Message.attachments')
|
||||
|
||||
while delete_count != 0:
|
||||
attachments.remove_at(index)
|
||||
delete_count -= 1
|
||||
|
||||
if replace_attachments.size() > 0:
|
||||
for attachment in replace_attachments:
|
||||
assert(attachment.has('id') and Helpers.is_valid_str(attachment.id), 'Missing id for attachment in replace_attachments in slice_attachments')
|
||||
attachments.append_array(replace_attachments)
|
||||
|
||||
return self
|
||||
1
addons/discord_gd/classes/message.gd.uid
Normal file
1
addons/discord_gd/classes/message.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dtoffh2nlc7di
|
||||
68
addons/discord_gd/classes/message_action_row.gd
Normal file
68
addons/discord_gd/classes/message_action_row.gd
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
class_name MessageActionRow
|
||||
"""
|
||||
Represnts a Discord message action row which has components
|
||||
"""
|
||||
|
||||
var components: Array
|
||||
|
||||
|
||||
func add_component(component, index = -1):
|
||||
assert(components.size() + 1 <= 5, 'MessageActionRow cannot have more than 5 components.')
|
||||
assert(component.type != 1, 'MessageActionRow cannot contain another MessageActionRow.')
|
||||
|
||||
var same_custom_id = false
|
||||
for _component in components:
|
||||
if Helpers.is_valid_str(_component.get_custom_id()):
|
||||
if _component.get_custom_id() == component.get_custom_id():
|
||||
same_custom_id = true
|
||||
break
|
||||
assert(same_custom_id == false, 'MessageActionRow must contain components with unique custom_id')
|
||||
|
||||
if index == -1:
|
||||
components.append(component)
|
||||
else:
|
||||
components.insert(index, component)
|
||||
return self
|
||||
|
||||
|
||||
func slice_components(index: int, delete_count: int = 1, replace_components: Array = []):
|
||||
var n = components.size()
|
||||
assert(Helpers.is_num(index), 'index must be provided to MessageActionRow.slice_components')
|
||||
assert(index > -1 and index < n, 'index out of bounds in MessageActionRow.slice_components')
|
||||
|
||||
var max_deletable = n - index
|
||||
assert(delete_count <= max_deletable, 'delete_count out of bounds in MessageActionRow.slice_components')
|
||||
|
||||
while delete_count != 0:
|
||||
components.remove_at(index)
|
||||
delete_count -= 1
|
||||
|
||||
if replace_components.size() != 0:
|
||||
# add components
|
||||
for component in replace_components:
|
||||
add_component(component, index)
|
||||
index += 1
|
||||
|
||||
return self
|
||||
|
||||
|
||||
|
||||
func _to_string(pretty: bool = false) -> String:
|
||||
return JSON.stringify(_to_dict(), '\t') if pretty else JSON.stringify(_to_dict())
|
||||
|
||||
|
||||
func print():
|
||||
print(_to_string(true))
|
||||
|
||||
|
||||
func _to_dict() -> Dictionary:
|
||||
assert(components.size() <= 5, 'MessageActionRow cannot have more than 5 components.')
|
||||
|
||||
var _components = []
|
||||
for component in components:
|
||||
_components.append(component._to_dict())
|
||||
|
||||
return {
|
||||
'type': 1,
|
||||
'components': _components
|
||||
}
|
||||
1
addons/discord_gd/classes/message_action_row.gd.uid
Normal file
1
addons/discord_gd/classes/message_action_row.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dr83pxgdkl46x
|
||||
101
addons/discord_gd/classes/message_button.gd
Normal file
101
addons/discord_gd/classes/message_button.gd
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
class_name MessageButton
|
||||
"""
|
||||
Represents a Discord message button.
|
||||
"""
|
||||
|
||||
var _STYLES = {0: 'DEFAULT', 1: 'PRIMARY', 2: 'SECONDARY', 3: 'SUCCESS', 4: 'DANGER', 5: 'LINK'}
|
||||
|
||||
enum STYLES { DEFAULT, PRIMARY, SECONDARY, SUCCESS, DANGER, LINK }
|
||||
|
||||
var label: String: set = set_label, get = get_label
|
||||
var custom_id: String: set = set_custom_id, get = get_custom_id
|
||||
var url: String: set = set_url, get = get_url
|
||||
var disabled: bool = false: set = set_disabled, get = get_disabled
|
||||
var emoji: Dictionary
|
||||
|
||||
var _style: set = set_style
|
||||
var type: int = 2
|
||||
|
||||
|
||||
func set_style(style_number: int):
|
||||
_style = style_number
|
||||
return self
|
||||
|
||||
|
||||
func get_style_string():
|
||||
return _STYLES[_style]
|
||||
|
||||
|
||||
func set_label(new_label: String):
|
||||
assert(new_label.length() <= 80, 'label of MessageButton must be max 80 characters.')
|
||||
label = new_label
|
||||
return self
|
||||
|
||||
|
||||
func get_label() -> String:
|
||||
return label
|
||||
|
||||
|
||||
func set_custom_id(new_custom_id):
|
||||
assert(new_custom_id.length() <= 80, 'custom_id of MessageButton must be max 100 characters.')
|
||||
custom_id = new_custom_id
|
||||
return self
|
||||
|
||||
|
||||
func get_custom_id() -> String:
|
||||
return custom_id
|
||||
|
||||
|
||||
func set_url(new_url):
|
||||
url = new_url
|
||||
return self
|
||||
|
||||
|
||||
func get_url() -> String:
|
||||
return url
|
||||
|
||||
|
||||
func set_disabled(new_value: bool):
|
||||
disabled = new_value
|
||||
return self
|
||||
|
||||
|
||||
func get_disabled() -> bool:
|
||||
return disabled
|
||||
|
||||
|
||||
func set_emoji(new_emoji: Dictionary):
|
||||
emoji = new_emoji
|
||||
return self
|
||||
|
||||
func get_emoji() -> Dictionary:
|
||||
return emoji
|
||||
|
||||
|
||||
func _to_string(pretty: bool = false) -> String:
|
||||
return JSON.stringify(_to_dict(), '\t') if pretty else JSON.stringify(_to_dict())
|
||||
|
||||
|
||||
func print():
|
||||
print(_to_string(true))
|
||||
|
||||
|
||||
func _to_dict() -> Dictionary:
|
||||
# Default style is primary
|
||||
if _style == 0:
|
||||
_style = 1
|
||||
|
||||
if _style == STYLES.LINK:
|
||||
# Must have a url
|
||||
assert(Helpers.is_valid_str(url), 'A LINK MessageButton must have a url.')
|
||||
return {'type': type, 'style': _style, 'label': label, 'url': url, 'disabled': disabled}
|
||||
else:
|
||||
assert(Helpers.is_valid_str(custom_id), 'A button must have a custom_id.')
|
||||
return {
|
||||
'type': type,
|
||||
'style': _style,
|
||||
'label': label,
|
||||
'custom_id': custom_id,
|
||||
'disabled': disabled,
|
||||
'emoji': emoji if emoji else null
|
||||
}
|
||||
1
addons/discord_gd/classes/message_button.gd.uid
Normal file
1
addons/discord_gd/classes/message_button.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c2mdufrdsqisx
|
||||
28
addons/discord_gd/classes/message_flags.gd
Normal file
28
addons/discord_gd/classes/message_flags.gd
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
class_name MessageFlags extends BitField
|
||||
"""
|
||||
Represents a bitfield of Discord message flags.
|
||||
"""
|
||||
|
||||
func _init(bits = default_bit):
|
||||
default_bit = 0
|
||||
|
||||
if bits == null:
|
||||
bits = default_bit
|
||||
|
||||
FLAGS = {
|
||||
'CROSSPOSTED': 1 << 0,
|
||||
'IS_CROSSPOST': 1 << 1,
|
||||
'SUPPRESS_EMBEDS': 1 << 2,
|
||||
'SOURCE_MESSAGE_DELETED': 1 << 3,
|
||||
'URGENT': 1 << 4,
|
||||
'HAS_THREAD': 1 << 5,
|
||||
'EPHEMERAL': 1 << 6,
|
||||
'LOADING': 1 << 7,
|
||||
}
|
||||
|
||||
bitfield = resolve(bits)
|
||||
|
||||
|
||||
func missing(bits):
|
||||
var BF = load('res://addons/discord_gd/classes/message_flags.gd')
|
||||
return BF.new(bits).remove(self).to_array()
|
||||
1
addons/discord_gd/classes/message_flags.gd.uid
Normal file
1
addons/discord_gd/classes/message_flags.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dbkf1n3hfcdj8
|
||||
67
addons/discord_gd/classes/permissions.gd
Normal file
67
addons/discord_gd/classes/permissions.gd
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
class_name Permissions extends BitField
|
||||
"""
|
||||
Represents a bitfield of Discord permissions.
|
||||
"""
|
||||
|
||||
var ALL
|
||||
const DEFAULT = 104324673
|
||||
|
||||
|
||||
func _init(bits = default_bit):
|
||||
default_bit = 0
|
||||
|
||||
if bits == null:
|
||||
bits = default_bit
|
||||
|
||||
FLAGS = {
|
||||
'CREATE_INSTANT_INVITE': 1 << 0,
|
||||
'KICK_MEMBERS': 1 << 1,
|
||||
'BAN_MEMBERS': 1 << 2,
|
||||
'ADMINISTRATOR': 1 << 3,
|
||||
'MANAGE_CHANNELS': 1 << 4,
|
||||
'MANAGE_GUILD': 1 << 5,
|
||||
'ADD_REACTIONS': 1 << 6,
|
||||
'VIEW_AUDIT_LOG': 1 << 7,
|
||||
'PRIORITY_SPEAKER': 1 << 8,
|
||||
'STREAM': 1 << 9,
|
||||
'VIEW_CHANNEL': 1 << 10,
|
||||
'SEND_MESSAGES': 1 << 11,
|
||||
'SEND_TTS_MESSAGES': 1 << 12,
|
||||
'MANAGE_MESSAGES': 1 << 13,
|
||||
'EMBED_LINKS': 1 << 14,
|
||||
'ATTACH_FILES': 1 << 15,
|
||||
'READ_MESSAGE_HISTORY': 1 << 16,
|
||||
'MENTION_EVERYONE': 1 << 17,
|
||||
'USE_EXTERNAL_EMOJIS': 1 << 18,
|
||||
'VIEW_GUILD_INSIGHTS': 1 << 19,
|
||||
'CONNECT': 1 << 20,
|
||||
'SPEAK': 1 << 21,
|
||||
'MUTE_MEMBERS': 1 << 22,
|
||||
'DEAFEN_MEMBERS': 1 << 23,
|
||||
'MOVE_MEMBERS': 1 << 24,
|
||||
'USE_VAD': 1 << 25,
|
||||
'CHANGE_NICKNAME': 1 << 26,
|
||||
'MANAGE_NICKNAMES': 1 << 27,
|
||||
'MANAGE_ROLES': 1 << 28,
|
||||
'MANAGE_WEBHOOKS': 1 << 29,
|
||||
'MANAGE_EMOJIS_AND_STICKERS': 1 << 30,
|
||||
'USE_APPLICATION_COMMANDS': 1 << 31,
|
||||
'REQUEST_TO_SPEAK': 1 << 32,
|
||||
'MANAGE_THREADS': 1 << 34,
|
||||
'USE_PUBLIC_THREADS': 1 << 35,
|
||||
'USE_PRIVATE_THREADS': 1 << 36,
|
||||
'USE_EXTERNAL_STICKERS': 1 << 37,
|
||||
}
|
||||
|
||||
bitfield = resolve(bits)
|
||||
|
||||
var values = FLAGS.values()
|
||||
var prev = default_bit
|
||||
for value in values:
|
||||
prev |= value
|
||||
ALL = prev
|
||||
|
||||
|
||||
func missing(bits):
|
||||
var BF = load('res://addons/discord_gd/classes/permissions.gd')
|
||||
return BF.new(bits).remove(self).to_array()
|
||||
1
addons/discord_gd/classes/permissions.gd.uid
Normal file
1
addons/discord_gd/classes/permissions.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://pov5klkwcdw3
|
||||
124
addons/discord_gd/classes/select_menu.gd
Normal file
124
addons/discord_gd/classes/select_menu.gd
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
class_name SelectMenu
|
||||
"""
|
||||
Represents a Discord select menu.
|
||||
"""
|
||||
|
||||
var custom_id: String: set = set_custom_id, get = get_custom_id
|
||||
var placeholder: String: set = set_placeholder, get = get_placeholder
|
||||
var options: Array: set = set_options, get = get_options
|
||||
|
||||
var min_values = 1: set = set_min_values, get = get_min_values
|
||||
var max_values = 1: set = set_max_values, get = get_max_values
|
||||
|
||||
var disabled: bool = false: set = set_disabled, get = get_disabled
|
||||
|
||||
var type: int = 3
|
||||
|
||||
|
||||
func set_custom_id(new_custom_id):
|
||||
Helpers.assert_length(new_custom_id, 100, 'custom_id of SelectMenu cannot be more than 100 characters.')
|
||||
custom_id = new_custom_id
|
||||
return self
|
||||
|
||||
|
||||
func get_custom_id() -> String:
|
||||
return custom_id
|
||||
|
||||
|
||||
func add_option(value: String, label: String, data: Dictionary = {}):
|
||||
assert(options.size() <= 25, 'options of SelectMenu cannot have more than 25 options')
|
||||
assert(Helpers.is_valid_str(value), 'value of SelectMenu option must be a valid String')
|
||||
Helpers.assert_length(value, 100, 'value of SelectMenu option cannot be more than 100 characters')
|
||||
assert(Helpers.is_valid_str(label), 'SelectMenu option must have a label')
|
||||
Helpers.assert_length(label, 100, 'label of SelectMenu option cannot be more than 100 characters')
|
||||
|
||||
# Parse data
|
||||
#{description: "", emoji: {}, default = false}
|
||||
var _data = {
|
||||
'value': value,
|
||||
'label': label
|
||||
}
|
||||
if data.has('description'):
|
||||
assert(typeof(data.description) == TYPE_STRING, 'description of SelectMenu option must be a String')
|
||||
Helpers.assert_length(data.description, 100, 'description of SelectMenu cannot be more than 100 characters')
|
||||
_data['description'] = data.description
|
||||
|
||||
if data.has('emoji'):
|
||||
assert(typeof(data.emoji) == TYPE_DICTIONARY, 'emoji of SelectMenu option must be a Dictionary')
|
||||
_data['emoji'] = data.emoji
|
||||
|
||||
if data.has('default'):
|
||||
assert(typeof(data.default) == TYPE_BOOL, 'default of SelectMenu option must be a bool')
|
||||
_data['default'] = data.default
|
||||
|
||||
options.append(_data)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
func set_options(new_options: Array):
|
||||
options = new_options
|
||||
return self
|
||||
|
||||
|
||||
func get_options() -> Array:
|
||||
return options
|
||||
|
||||
|
||||
func set_placeholder(new_placeholder: String):
|
||||
Helpers.assert_length(new_placeholder, 100, 'placeholder of SelectMenu cannot be more than 100 characters.')
|
||||
placeholder = new_placeholder
|
||||
return self
|
||||
|
||||
|
||||
func get_placeholder() -> String:
|
||||
return placeholder
|
||||
|
||||
|
||||
func set_min_values(new_min_values: int):
|
||||
assert(new_min_values <= 25, 'min_values of SelectMenu cannot be more than 25')
|
||||
min_values = new_min_values
|
||||
return self
|
||||
|
||||
|
||||
func get_min_values() -> int:
|
||||
return min_values
|
||||
|
||||
|
||||
func set_max_values(new_max_values: int):
|
||||
assert(new_max_values <= 25, 'max_values of SelectMenu cannot be more than 25')
|
||||
max_values = new_max_values
|
||||
return self
|
||||
|
||||
func get_max_values() -> int:
|
||||
return max_values
|
||||
|
||||
|
||||
func set_disabled(new_value: bool):
|
||||
disabled = new_value
|
||||
return self
|
||||
|
||||
func get_disabled() -> bool:
|
||||
return disabled
|
||||
|
||||
|
||||
func _to_string(pretty: bool = false) -> String:
|
||||
return JSON.stringify(_to_dict(), '\t') if pretty else JSON.stringify(_to_dict())
|
||||
|
||||
|
||||
func print():
|
||||
print(_to_string(true))
|
||||
|
||||
|
||||
func _to_dict() -> Dictionary:
|
||||
# Default style is primary
|
||||
assert(Helpers.is_valid_str(custom_id), 'A button must have a custom_id.')
|
||||
return {
|
||||
'type': type,
|
||||
'custom_id': custom_id,
|
||||
'options': options,
|
||||
'placeholder': placeholder,
|
||||
'min_values': min_values,
|
||||
'max_values': max_values,
|
||||
'disabled': disabled,
|
||||
}
|
||||
1
addons/discord_gd/classes/select_menu.gd.uid
Normal file
1
addons/discord_gd/classes/select_menu.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://v5s48xly81hk
|
||||
156
addons/discord_gd/classes/user.gd
Normal file
156
addons/discord_gd/classes/user.gd
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
class_name User
|
||||
"""
|
||||
Represents a Discord User.
|
||||
"""
|
||||
|
||||
var id: String
|
||||
var username: String
|
||||
var discriminator: String
|
||||
var avatar: String
|
||||
|
||||
# Optional
|
||||
var bot: bool
|
||||
var system: bool
|
||||
var mfa_enabled: bool
|
||||
var locale: String
|
||||
var verified: bool
|
||||
var email: String
|
||||
var flags: int
|
||||
var premium_type: int
|
||||
var public_flags: int
|
||||
|
||||
var client
|
||||
|
||||
const AVATAR_URL_FORMATS = ['webp', 'png', 'jpg', 'jpeg', 'gif']
|
||||
const AVATAR_URL_SIZES = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096]
|
||||
|
||||
|
||||
func get_display_avatar_url(options: Dictionary = {}) -> String:
|
||||
"""
|
||||
options {
|
||||
format: String, one of webp, png, jpg, jpeg, gif (default png),
|
||||
size: int, one of 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 (default 256),
|
||||
dynamic: bool, if true the format will automatically change to gif for animated avatars (default false)
|
||||
}
|
||||
"""
|
||||
|
||||
if options.has('format'):
|
||||
assert(options.format in AVATAR_URL_FORMATS, 'Invalid avatar_url provided to get_display_avatar')
|
||||
else:
|
||||
options.format = 'png'
|
||||
|
||||
if options.has('size'):
|
||||
assert(int(options.size) in AVATAR_URL_SIZES, 'Invalid size provided to get_display_avatar')
|
||||
else:
|
||||
options.size = 256
|
||||
|
||||
if options.has('dynamic'):
|
||||
assert(typeof(options.dynamic) == TYPE_BOOL, 'dynamic attribute must be of type bool in get_display_avatar')
|
||||
if Helpers.is_valid_str(avatar) and avatar.begins_with('a_'):
|
||||
options.format = 'gif'
|
||||
else:
|
||||
options.dynamic = false
|
||||
|
||||
if not Helpers.is_valid_str(avatar):
|
||||
return get_default_avatar_url()
|
||||
|
||||
return client._cdn_base + '/avatars/%s/%s.%s?size=%s' % [id, avatar, options.format, options.size]
|
||||
|
||||
|
||||
func get_default_avatar_url() -> String:
|
||||
var moduloed_discriminator = int(discriminator) % 5
|
||||
return client._cdn_base + '/embed/avatars/%s.png' % moduloed_discriminator
|
||||
|
||||
|
||||
func get_display_avatar(options: Dictionary = {}) -> PackedByteArray:
|
||||
var png_bytes = await client._send_get_cdn(get_display_avatar_url(options))
|
||||
return png_bytes
|
||||
|
||||
|
||||
func get_default_avatar() -> PackedByteArray:
|
||||
var png_bytes = await client._send_get_cdn(get_default_avatar_url())
|
||||
return png_bytes
|
||||
|
||||
|
||||
func _init(_client, user):
|
||||
client = _client
|
||||
# Compulsory
|
||||
assert(user.has('id'), 'User must have an id')
|
||||
assert(user.has('username'), 'User must have a username')
|
||||
assert(user.has('discriminator'), 'User must have a discriminator')
|
||||
|
||||
|
||||
id = user.id
|
||||
username = user.username
|
||||
discriminator = user.discriminator
|
||||
if user.avatar:
|
||||
avatar = user.avatar
|
||||
|
||||
# Optional
|
||||
|
||||
if user.has('bot') and user.bot != null:
|
||||
assert(typeof(user.bot) == TYPE_BOOL, 'bot attribute of User must be bool')
|
||||
bot = user.bot
|
||||
else:
|
||||
bot = false
|
||||
|
||||
if user.has('system') and user.system != null:
|
||||
assert(typeof(user.system) == TYPE_BOOL, 'system attribute of User must be bool')
|
||||
system = user.system
|
||||
else:
|
||||
system = false
|
||||
|
||||
if user.has('mfa_enabled') and user.mfa_enabled != null:
|
||||
assert(typeof(user.mfa_enabled) == TYPE_BOOL, 'mfa_enabled attribute of User must be bool')
|
||||
mfa_enabled = user.mfa_enabled
|
||||
else:
|
||||
mfa_enabled = false
|
||||
|
||||
if user.has('verified') and user.verified != null:
|
||||
assert(typeof(user.verified) == TYPE_BOOL, 'verified attribute of User must be bool')
|
||||
verified = user.verified
|
||||
else:
|
||||
verified = false
|
||||
|
||||
if user.has('locale') and user.locale != null:
|
||||
assert(typeof(user.locale) == TYPE_STRING, 'locale attribute of User must be String')
|
||||
locale = user.locale
|
||||
|
||||
if user.has('email') and user.email != null:
|
||||
assert(typeof(user.email) == TYPE_STRING, 'email attribute of User must be String')
|
||||
email = user.email
|
||||
|
||||
if user.has('flags') and user.flags != null:
|
||||
assert(Helpers.is_num(user.flags), 'flags attribute of User must be int')
|
||||
flags = user.flags
|
||||
|
||||
if user.has('premium_type') and user.premium_type != null:
|
||||
assert(Helpers.is_num(user.premium_type), 'premium_type attribute of User must be int')
|
||||
premium_type = user.premium_type
|
||||
|
||||
if user.has('public_flags') and user.public_flags != null:
|
||||
assert(Helpers.is_num(user.public_flags), 'public_flags attribute of User must be int')
|
||||
public_flags = user.public_flags
|
||||
|
||||
|
||||
func _to_string(pretty: bool = false):
|
||||
var data = {
|
||||
'id': id,
|
||||
'username': username,
|
||||
'discriminator': discriminator,
|
||||
'avatar': avatar,
|
||||
'bot': bot,
|
||||
'system': system,
|
||||
'mfa_enabled': mfa_enabled,
|
||||
'locale': locale,
|
||||
'verified': verified,
|
||||
'email': email,
|
||||
'flags': flags,
|
||||
'premium_type': premium_type,
|
||||
'public_flags': public_flags
|
||||
}
|
||||
return JSON.stringify(data, '\t') if pretty else JSON.stringify(data)
|
||||
|
||||
|
||||
func print():
|
||||
print(_to_string(true))
|
||||
1
addons/discord_gd/classes/user.gd.uid
Normal file
1
addons/discord_gd/classes/user.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://1uaxjy7fw8f2
|
||||
Loading…
Add table
Add a link
Reference in a new issue