barebones prototype
This commit is contained in:
parent
83f340ea01
commit
b53d33584c
60 changed files with 3743 additions and 1 deletions
4
.editorconfig
Normal file
4
.editorconfig
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Normalize EOL for all files that Git considers text files.
|
||||||
|
* text=auto eol=lf
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Godot 4+ specific ignores
|
||||||
|
.godot/
|
||||||
|
/android/
|
||||||
6
Library.tscn
Normal file
6
Library.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://1oww0utk77w7"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://hkh1ewsuji8m" path="res://library.gd" id="1_gqcys"]
|
||||||
|
|
||||||
|
[node name="Library" type="Node"]
|
||||||
|
script = ExtResource("1_gqcys")
|
||||||
12
Main.tscn
Normal file
12
Main.tscn
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cd60nfxe4lnq1"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://cus8nh0g3yyj2" path="res://main.gd" id="1_glv2v"]
|
||||||
|
|
||||||
|
[node name="Main" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_glv2v")
|
||||||
|
|
@ -1 +1 @@
|
||||||
# FabsocBot
|
# FabsocBot
|
||||||
|
|
|
||||||
21
addons/discord_gd/LICENSE.md
Normal file
21
addons/discord_gd/LICENSE.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021-present Delano Lourenco
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
111
addons/discord_gd/README.md
Normal file
111
addons/discord_gd/README.md
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
Discord.gd
|
||||||
|
=========================================
|
||||||
|
###### (Get it from Godot Asset Library - https://godotengine.org/asset-library/asset/1010)
|
||||||
|
|
||||||
|
|
||||||
|
### A Godot plugin to interact with the Discord Bot API. Make Discord Bots in Godot!
|
||||||
|
|
||||||
|
> 100% GDScript
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<img alt="Godot4" src="https://img.shields.io/badge/-Godot 4.x-478CBF?style=for-the-badge&logo=godotengine&logoWidth=20&logoColor=white" />
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
#### Godot version compatibility
|
||||||
|
|
||||||
|
- Godot 4.x - [main branch](https://github.com/3ddelano/discord.gd/tree/main)
|
||||||
|
- Godot 3.x - [godot3 branch](https://github.com/3ddelano/discord.gd/tree/godot3)
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- Make a Discord Bot in less than 10 lines of code
|
||||||
|
- Supports `Buttons` and `SelectMenus`
|
||||||
|
- Supports `Application Commands` aka `Slash Commands`
|
||||||
|
- Uses Godot signals to emit events like `bot_ready`, `guild_create`, `message_create`, `message_delete`, etc.
|
||||||
|
- Get User Avatar and Guild Icon as Godot's `ImageTexture`
|
||||||
|
- Uses coroutine async functions i.e Promises
|
||||||
|
|
||||||
|
|
||||||
|
## [🚀 Check out out GDAI MCP from the creator of Discord.gd](https://gdaimcp.com?ref=discordgd-readme)
|
||||||
|
<a href="https://gdaimcp.com?ref=discordgd-readme" target="_blank">
|
||||||
|
<img src="https://gdaimcp.com/images/og/gdai-mcp.png" width="400" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
Supercharge your Godot 4.2+ workflow with GDAI MCP – the ultimate Godot MCP server that lets AI tools like Claude, Cursor, Windsurf, VSCode and more automate scene creation, node editing, reading godot errors, creating scripts, debugging, and more.
|
||||||
|
|
||||||
|
Vibe code like never before!
|
||||||
|
|
||||||
|
### 🔗 **[https://gdaimcp.com](https://gdaimcp.com?ref=discordgd-readme)**
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
This is a regular plugin for Godot.
|
||||||
|
Copy the contents of `addons/discord_gd` into the `addons/` folder in the same directory as your project, and activate it in your project settings.
|
||||||
|
|
||||||
|
The plugin now comes with no extra assets to stay lightweight.
|
||||||
|
If you want to try an example scene, you can see the examples from: [Discord.gd Examples](https://github.com/3ddelano/discord_gd_examples)
|
||||||
|
|
||||||
|
> For in-depth installation instructions check the [Installation Wiki](https://3ddelano.github.io/discord.gd/installation)
|
||||||
|
|
||||||
|
> Note: You will need a valid Discord Bot token available at [Discord Applications](https://discord.com/developers/applications)
|
||||||
|
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
----------
|
||||||
|
|
||||||
|
1. After activating the plugin. There will be a new `DiscordBot` node added to Godot.
|
||||||
|
Click on any node in the scene tree of your scene for example `Root` and add the `DiscordBot` node as a child.
|
||||||
|
|
||||||
|
2. Connect the various signals (`bot_ready`, `guild_create`, `message_create`, `message_delete`, etc) of the `DiscordBot` node to the parent node, either through the editor or in the script using the `connect()` method.
|
||||||
|
|
||||||
|
3. Attach a script to the `Root` node.
|
||||||
|
|
||||||
|
```GDScript
|
||||||
|
extends Node2D
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var discord_bot = $DiscordBot
|
||||||
|
discord_bot.TOKEN = "your_bot_token_here"
|
||||||
|
discord_bot.login()
|
||||||
|
discord_bot.bot_ready.connect(_on_DiscordBot_bot_ready)
|
||||||
|
discord_bot.message_create.connect(_on_DiscordBot_message_create)
|
||||||
|
|
||||||
|
func _on_DiscordBot_bot_ready(bot: DiscordBot):
|
||||||
|
print("Logged in as %s#%s" % [bot.user.username, bot.user.discriminator])
|
||||||
|
print("Listening on %d channels and %d guilds." % [bot.channels.size(), bot.guilds.size()])
|
||||||
|
|
||||||
|
func _on_DiscordBot_message_create(bot: DiscordBot, msg: Message, channel: Dictionary):
|
||||||
|
print("New message from %s: %s" % [msg.author.username, msg.content])
|
||||||
|
|
||||||
|
if msg.author.bot:
|
||||||
|
return
|
||||||
|
|
||||||
|
await bot.reply(msg, "Hi!")
|
||||||
|
```
|
||||||
|
|
||||||
|
[Documentation](https://3ddelano.github.io/discord.gd)
|
||||||
|
----------
|
||||||
|
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This plugin is a non-profit project developped by voluntary contributors.
|
||||||
|
|
||||||
|
### Supporters
|
||||||
|
|
||||||
|
```
|
||||||
|
- YaBoyTwiz#6733
|
||||||
|
```
|
||||||
|
|
||||||
|
### Support the project development
|
||||||
|
<a href="https://www.buymeacoffee.com/3ddelano" target="_blank"><img height="41" width="174" src="https://cdn.buymeacoffee.com/buttons/v2/default-red.png" alt="Buy Me A Coffee" width="150" ></a>
|
||||||
|
|
||||||
|
Want to support in other ways? Contact me on Discord: `@3ddelano#6033`
|
||||||
|
|
||||||
|
For doubts / help / bugs / problems / suggestions do join: [3ddelano Cafe](https://discord.gg/FZY9TqW)
|
||||||
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
|
||||||
1308
addons/discord_gd/discord.gd
Normal file
1308
addons/discord_gd/discord.gd
Normal file
File diff suppressed because it is too large
Load diff
1
addons/discord_gd/discord.gd.uid
Normal file
1
addons/discord_gd/discord.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dqss6smw7w8nh
|
||||||
7
addons/discord_gd/plugin.cfg
Normal file
7
addons/discord_gd/plugin.cfg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="discord.gd"
|
||||||
|
description="A Discord bot API wrapper for Godot."
|
||||||
|
author="Delano Lourenco"
|
||||||
|
version="2.0.0"
|
||||||
|
script="plugin.gd"
|
||||||
9
addons/discord_gd/plugin.gd
Normal file
9
addons/discord_gd/plugin.gd
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
"""
|
||||||
|
Discord.gd
|
||||||
|
|
||||||
|
Nothing to do here,
|
||||||
|
check discord.gd script file
|
||||||
|
"""
|
||||||
1
addons/discord_gd/plugin.gd.uid
Normal file
1
addons/discord_gd/plugin.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://h5i4g1mvqc8u
|
||||||
18
application_cmds/test_appcmd.gd
Normal file
18
application_cmds/test_appcmd.gd
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
#func on_ready(main, bot: DiscordBot) -> void:
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
#func on_autocomplete(main, bot: DiscordBot, interaction: DiscordInteraction, options: Array) -> void:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
func execute(main, bot: DiscordBot, interaction: DiscordInteraction, options: Array) -> void:
|
||||||
|
print("hiya!")
|
||||||
|
interaction.reply(
|
||||||
|
{
|
||||||
|
"content" : "meow meow! hearing you loud and clear tabby!"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
|
var data = ApplicationCommand.new().set_name("meow_test").set_description("meow_desc")
|
||||||
1
application_cmds/test_appcmd.gd.uid
Normal file
1
application_cmds/test_appcmd.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://d3dwi5xxpdp2x
|
||||||
5
datatypes/librarySave.gd
Normal file
5
datatypes/librarySave.gd
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
extends Resource
|
||||||
|
class_name LibrarySave
|
||||||
|
|
||||||
|
@export var spools : Array[Spool]
|
||||||
|
@export var printers : Array[Printer]
|
||||||
1
datatypes/librarySave.gd.uid
Normal file
1
datatypes/librarySave.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c634rg4aki0oy
|
||||||
2
datatypes/printer.gd
Normal file
2
datatypes/printer.gd
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
extends Resource
|
||||||
|
class_name Printer
|
||||||
1
datatypes/printer.gd.uid
Normal file
1
datatypes/printer.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bnyoyarpn5qml
|
||||||
7
datatypes/spool.gd
Normal file
7
datatypes/spool.gd
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
extends Resource
|
||||||
|
class_name Spool
|
||||||
|
|
||||||
|
@export var name : String
|
||||||
|
@export var material : String
|
||||||
|
@export var link : String
|
||||||
|
# tags?
|
||||||
1
datatypes/spool.gd.uid
Normal file
1
datatypes/spool.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://daj15kpbagd33
|
||||||
29
demo.tscn
Normal file
29
demo.tscn
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
[gd_scene load_steps=4 format=3 uid="uid://dyob0p2l7g5fj"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bv8cjh12rwg4t" path="res://test.gd" id="1_0bhed"]
|
||||||
|
[ext_resource type="Script" uid="uid://dqss6smw7w8nh" path="res://addons/discord_gd/discord.gd" id="2_m0rpm"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dta0nr1cvl70v" path="res://fabcatserver.png" id="3_m0rpm"]
|
||||||
|
|
||||||
|
[node name="Control" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_0bhed")
|
||||||
|
|
||||||
|
[node name="DiscordBot" type="Node" parent="."]
|
||||||
|
script = ExtResource("2_m0rpm")
|
||||||
|
metadata/_custom_type_script = "uid://dqss6smw7w8nh"
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
texture = ExtResource("3_m0rpm")
|
||||||
|
expand_mode = 3
|
||||||
|
stretch_mode = 5
|
||||||
42
export_presets.cfg
Normal file
42
export_presets.cfg
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
[preset.0]
|
||||||
|
|
||||||
|
name="Linux"
|
||||||
|
platform="Linux"
|
||||||
|
runnable=true
|
||||||
|
advanced_options=false
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="../../Exports/Fabcat/FabsocBot.x86_64"
|
||||||
|
patches=PackedStringArray()
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
seed=0
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
|
[preset.0.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_wrapper=1
|
||||||
|
binary_format/embed_pck=false
|
||||||
|
texture_format/s3tc_bptc=true
|
||||||
|
texture_format/etc2_astc=false
|
||||||
|
shader_baker/enabled=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
ssh_remote_deploy/enabled=false
|
||||||
|
ssh_remote_deploy/host="user@host_ip"
|
||||||
|
ssh_remote_deploy/port="22"
|
||||||
|
ssh_remote_deploy/extra_args_ssh=""
|
||||||
|
ssh_remote_deploy/extra_args_scp=""
|
||||||
|
ssh_remote_deploy/run_script="#!/usr/bin/env bash
|
||||||
|
export DISPLAY=:0
|
||||||
|
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||||
|
\"{temp_dir}/{exe_name}\" {cmd_args}"
|
||||||
|
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||||
|
kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
|
||||||
|
rm -rf \"{temp_dir}\""
|
||||||
BIN
fabcatserver.png
Normal file
BIN
fabcatserver.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
40
fabcatserver.png.import
Normal file
40
fabcatserver.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dta0nr1cvl70v"
|
||||||
|
path="res://.godot/imported/fabcatserver.png-5b3643fea72e10ebca565cbc1124bb9e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://fabcatserver.png"
|
||||||
|
dest_files=["res://.godot/imported/fabcatserver.png-5b3643fea72e10ebca565cbc1124bb9e.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
1
icon.svg
Normal file
1
icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 995 B |
43
icon.svg.import
Normal file
43
icon.svg.import
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b4bahh7qjl64"
|
||||||
|
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
40
library.gd
Normal file
40
library.gd
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var save_path : String = "user://librarySave.tres"
|
||||||
|
var save : LibrarySave
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready() -> void:
|
||||||
|
load_data()
|
||||||
|
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
func load_data():
|
||||||
|
# check if save file exists
|
||||||
|
print("real? " + str(FileAccess.file_exists(save_path)))
|
||||||
|
if FileAccess.file_exists(save_path):
|
||||||
|
print("yes, loading...")
|
||||||
|
save = ResourceLoader.load(save_path) as LibrarySave
|
||||||
|
print(save)
|
||||||
|
#save = load(save_path) as GameSave
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("nope, creating...")
|
||||||
|
save_new()
|
||||||
|
pass
|
||||||
|
|
||||||
|
func save_data():
|
||||||
|
ResourceSaver.save(save, save_path)
|
||||||
|
pass
|
||||||
|
|
||||||
|
func save_new():
|
||||||
|
#data = FileAccess.open(save_path, FileAccess.WRITE_READ)
|
||||||
|
save = LibrarySave.new()
|
||||||
|
var error = ResourceSaver.save(save, save_path)
|
||||||
|
print(error)
|
||||||
|
|
||||||
1
library.gd.uid
Normal file
1
library.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://hkh1ewsuji8m
|
||||||
205
main.gd
Normal file
205
main.gd
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
extends Node
|
||||||
|
signal interaction_create(world, bot, interaction, data)
|
||||||
|
|
||||||
|
#const prefix = "gd."
|
||||||
|
|
||||||
|
var interactions = {}
|
||||||
|
var application_commands = {}
|
||||||
|
|
||||||
|
|
||||||
|
#func _load_bot_token() -> String:
|
||||||
|
## read from .env file DISORD_BOT_TOKEN
|
||||||
|
#var lines = FileAccess.get_file_as_string(".env").split("\n")
|
||||||
|
#for line in lines:
|
||||||
|
#if line.begins_with("DISCORD_BOT_TOKEN="):
|
||||||
|
#return line.split("=")[1]
|
||||||
|
#return ""
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
var bot = DiscordBot.new()
|
||||||
|
add_child(bot)
|
||||||
|
|
||||||
|
# Try to read token from global environment variables
|
||||||
|
var token = OS.get_environment("DISCORD_BOT_TOKEN")
|
||||||
|
if not token or (token and len(token) < 10):
|
||||||
|
# Read token from local .env file
|
||||||
|
#token = _load_bot_token()
|
||||||
|
token = "MTQzMzAyNTMwMzYzODQ0MjAzNQ.G3r4My.IdnvCw6xTBfoitEzvhPgxeErSDgcMCsznmLnvI"
|
||||||
|
|
||||||
|
bot.TOKEN = token
|
||||||
|
#bot.INTENTS = 4609
|
||||||
|
bot.bot_ready.connect(_on_bot_ready)
|
||||||
|
#bot.message_create.connect(_on_message_create)
|
||||||
|
bot.interaction_create.connect(_on_interaction_create)
|
||||||
|
bot.login()
|
||||||
|
#_load_commands(bot)
|
||||||
|
_load_application_commands(bot)
|
||||||
|
|
||||||
|
func _on_bot_ready(bot: DiscordBot):
|
||||||
|
print("Logged in as " + bot.user.username + "#" + bot.user.discriminator)
|
||||||
|
bot.set_presence({
|
||||||
|
"activity": {
|
||||||
|
"type": "Watching",
|
||||||
|
"name": "Watching the printers"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Registering commands is not needed on every run,
|
||||||
|
# only register the command if you change any options
|
||||||
|
# or if you add/remove commands
|
||||||
|
|
||||||
|
# For development use the single server command and
|
||||||
|
# once you have the commands working you can register
|
||||||
|
# them as global commands
|
||||||
|
|
||||||
|
# -----Single server (updates instantly)
|
||||||
|
#_register_application_commands(bot, "guild_id_here")
|
||||||
|
_register_application_commands(bot, "679917161195765822")
|
||||||
|
|
||||||
|
# -----Global (may take upto 1hr to update)
|
||||||
|
#_register_application_commands(bot)
|
||||||
|
|
||||||
|
|
||||||
|
#func _on_message_create(bot: DiscordBot, message: Message, channel: Dictionary) -> void:
|
||||||
|
#if message.author.bot or not message.content.begins_with(prefix):
|
||||||
|
#return
|
||||||
|
#
|
||||||
|
## Make sure to use trim_prefix() instead of lstrip()
|
||||||
|
#var raw_content = message.content.trim_prefix(prefix)
|
||||||
|
#var tokens = []
|
||||||
|
#var r = RegEx.new()
|
||||||
|
#r.compile("\\S+") # Negated whitespace character class
|
||||||
|
#for token in r.search_all(raw_content):
|
||||||
|
#tokens.append(token.get_string())
|
||||||
|
#var cmd_or_alias = tokens[0].to_lower()
|
||||||
|
#tokens.remove(0) # Remove the command name from the tokens
|
||||||
|
#var args = tokens
|
||||||
|
#_handle_command(bot, message, channel, cmd_or_alias, args)
|
||||||
|
|
||||||
|
|
||||||
|
#func _load_commands(bot: DiscordBot) -> void:
|
||||||
|
#var cmd_path = "res://cmds/"
|
||||||
|
#var dir = DirAccess.open(cmd_path)
|
||||||
|
#if not dir:
|
||||||
|
#push_error("An error occurred when trying to open /cmds/ folder.")
|
||||||
|
#return
|
||||||
|
#
|
||||||
|
#dir.list_dir_begin()
|
||||||
|
#while true:
|
||||||
|
#var file = dir.get_next()
|
||||||
|
#if file == "": # End of files
|
||||||
|
#break
|
||||||
|
#elif not file.begins_with(".") and (file.ends_with(".gd") or file.ends_with(".gdc")):
|
||||||
|
#var script = load(cmd_path + file.get_basename() + ".gd").new()
|
||||||
|
#var data = script.help
|
||||||
|
#
|
||||||
|
## Ensure that the commands don't have the default help values
|
||||||
|
#assert(data.name != "test_name" and data.category != "test_category" and data.description != "test_description" and script.get_usage(prefix) != "test usage", "Must change default values for Command in " + file)
|
||||||
|
#
|
||||||
|
#if not data.enabled:
|
||||||
|
#continue
|
||||||
|
#
|
||||||
|
#if script.has_method("on_ready"):
|
||||||
|
#script.on_ready(self, bot)
|
||||||
|
#
|
||||||
|
#commands[data.name] = script
|
||||||
|
#
|
||||||
|
#if not data.has("aliases"):
|
||||||
|
#continue
|
||||||
|
#
|
||||||
|
#for alias in data.aliases:
|
||||||
|
#assert(not command_aliases.has(alias), "Duplicate cmd aliases found in cmd: " + file)
|
||||||
|
#
|
||||||
|
#command_aliases[alias] = data.name
|
||||||
|
#
|
||||||
|
#print("Loaded " + str(commands.size()) + " cmds")
|
||||||
|
#dir.list_dir_end()
|
||||||
|
|
||||||
|
func _load_application_commands(bot: DiscordBot) -> void:
|
||||||
|
var app_cmd_path = "res://application_cmds/"
|
||||||
|
var dir = DirAccess.open(app_cmd_path)
|
||||||
|
|
||||||
|
if not dir:
|
||||||
|
push_error("An error occurred when trying to open /application_cmds/ folder.")
|
||||||
|
return
|
||||||
|
|
||||||
|
dir.list_dir_begin()
|
||||||
|
while true:
|
||||||
|
var file = dir.get_next()
|
||||||
|
if file == "": # End of files
|
||||||
|
break
|
||||||
|
elif not file.begins_with(".") and (file.ends_with(".gd") or file.ends_with(".gdc")):
|
||||||
|
var script = load(app_cmd_path + file.get_basename() + ".gd").new()
|
||||||
|
var data = script.data
|
||||||
|
|
||||||
|
# Ensure that the commands don't have the default help values
|
||||||
|
assert(data.name != "test_name" and data.description != "test_description", "Must change default values for ApplicationCommand in " + file)
|
||||||
|
|
||||||
|
if script.has_method("on_ready"):
|
||||||
|
script.on_ready(self, bot)
|
||||||
|
|
||||||
|
application_commands[data.name] = script
|
||||||
|
|
||||||
|
print("Loaded " + str(application_commands.size()) + " application cmds")
|
||||||
|
dir.list_dir_end()
|
||||||
|
|
||||||
|
func _register_application_commands(bot, guild_id: String = "") -> void:
|
||||||
|
var application_commands_data = []
|
||||||
|
for app_cmd in application_commands.values():
|
||||||
|
application_commands_data.append(app_cmd.data)
|
||||||
|
|
||||||
|
bot.register_commands(application_commands_data, guild_id)
|
||||||
|
|
||||||
|
#func _handle_command(bot: DiscordBot, message: Message, channel: Dictionary, cmd_or_alias: String, args: Array):
|
||||||
|
#var cmd = null
|
||||||
|
#if command_aliases.has(cmd_or_alias):
|
||||||
|
#cmd = commands[command_aliases[cmd_or_alias]]
|
||||||
|
#elif commands.has(cmd_or_alias):
|
||||||
|
#cmd = commands[cmd_or_alias]
|
||||||
|
#
|
||||||
|
#if cmd == null:
|
||||||
|
#return
|
||||||
|
#
|
||||||
|
#print("CMD: " + cmd.help.name + " by " + message.author.username + "#" + message.author.discriminator + " (" + message.author.id + ")")
|
||||||
|
#
|
||||||
|
#cmd.on_message(self, bot, message, channel, args)
|
||||||
|
|
||||||
|
func remove_components_from_interaction(interaction: DiscordInteraction, msg = ":robot: Components have timed out!") -> void:
|
||||||
|
var embed = Embed.new().set_description(msg)
|
||||||
|
var new_embeds = interaction.message.embeds + [embed]
|
||||||
|
interaction.update({
|
||||||
|
"content": interaction.message.content,
|
||||||
|
"embeds": new_embeds,
|
||||||
|
"components": []
|
||||||
|
})
|
||||||
|
|
||||||
|
func _on_interaction_create(bot: DiscordBot, interaction: DiscordInteraction):
|
||||||
|
# Handle ApplicationCommand
|
||||||
|
if interaction.is_command() or interaction.is_autocomplete():
|
||||||
|
var cmd_name = interaction.data.name
|
||||||
|
if application_commands.has(cmd_name):
|
||||||
|
# The application command was found, so execute it
|
||||||
|
var app_cmd = application_commands[cmd_name]
|
||||||
|
var options = interaction.data.options if interaction.data.has("options") else []
|
||||||
|
|
||||||
|
if interaction.is_autocomplete():
|
||||||
|
# It is an autocomplete command
|
||||||
|
app_cmd.on_autocomplete(self, bot, interaction, options)
|
||||||
|
return
|
||||||
|
|
||||||
|
print("APP_CMD: " + app_cmd.data.name + " by " + interaction.member.user.username + "#" + interaction.member.user.discriminator + " (" + interaction.member.user.id + ")")
|
||||||
|
app_cmd.execute(self, bot, interaction, options)
|
||||||
|
else:
|
||||||
|
# The application command was not found
|
||||||
|
interaction.reply({
|
||||||
|
"ephemeral": true,
|
||||||
|
"content": ":electric_plug: The requested command was not found."
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
var msg_id = interaction.message.id
|
||||||
|
# Emit the interaction to the normal (text) commands
|
||||||
|
if interactions.has(msg_id):
|
||||||
|
emit_signal("interaction_create", self, bot, interaction, interactions[msg_id])
|
||||||
|
else:
|
||||||
|
remove_components_from_interaction(interaction)
|
||||||
1
main.gd.uid
Normal file
1
main.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cus8nh0g3yyj2
|
||||||
29
project.godot
Normal file
29
project.godot
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="FabsocBot"
|
||||||
|
run/main_scene="uid://dyob0p2l7g5fj"
|
||||||
|
config/features=PackedStringArray("4.5", "GL Compatibility")
|
||||||
|
config/icon="uid://dta0nr1cvl70v"
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
Library="*res://Library.tscn"
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PackedStringArray("res://addons/discord_gd/plugin.cfg")
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
|
||||||
|
renderer/rendering_method="gl_compatibility"
|
||||||
|
renderer/rendering_method.mobile="gl_compatibility"
|
||||||
12
templates/application_command.gd
Normal file
12
templates/application_command.gd
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
#func on_ready(main, bot: DiscordBot) -> void:
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
#func on_autocomplete(main, bot: DiscordBot, interaction: DiscordInteraction, options: Array) -> void:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
func execute(main, bot: DiscordBot, interaction: DiscordInteraction, options: Array) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
var data = ApplicationCommand.new().set_name("test_name").set_description("test_description")
|
||||||
1
templates/application_command.gd.uid
Normal file
1
templates/application_command.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://db2x4erxyi6sw
|
||||||
38
test.gd
Normal file
38
test.gd
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var discord_bot = $DiscordBot
|
||||||
|
discord_bot.TOKEN = "MTQzMzAyNTMwMzYzODQ0MjAzNQ.G3r4My.IdnvCw6xTBfoitEzvhPgxeErSDgcMCsznmLnvI"
|
||||||
|
discord_bot.login()
|
||||||
|
discord_bot.bot_ready.connect(_on_DiscordBot_bot_ready)
|
||||||
|
discord_bot.message_create.connect(_on_DiscordBot_message_create)
|
||||||
|
|
||||||
|
func _on_DiscordBot_bot_ready(bot: DiscordBot):
|
||||||
|
print("Logged in as %s#%s" % [bot.user.username, bot.user.discriminator])
|
||||||
|
print("Listening on %d channels and %d guilds." % [bot.channels.size(), bot.guilds.size()])
|
||||||
|
|
||||||
|
func _on_DiscordBot_message_create(bot: DiscordBot, msg: Message, channel: Dictionary):
|
||||||
|
print("New message from %s: %s" % [msg.author.username, msg.content])
|
||||||
|
|
||||||
|
if msg.author.bot:
|
||||||
|
return
|
||||||
|
|
||||||
|
send_screenshot(bot, msg)
|
||||||
|
|
||||||
|
await bot.reply(msg, "meow!")
|
||||||
|
|
||||||
|
func send_screenshot(bot: DiscordBot, msg: Message):
|
||||||
|
var image : Image = get_viewport().get_texture().get_image()
|
||||||
|
#image.flip_y()
|
||||||
|
var bytes = image.save_png_to_buffer()
|
||||||
|
|
||||||
|
#send
|
||||||
|
bot.send(msg, {
|
||||||
|
"files":[
|
||||||
|
{
|
||||||
|
"name" : "screenshot.png",
|
||||||
|
"media_type" : "image/png",
|
||||||
|
"data" : bytes
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
1
test.gd.uid
Normal file
1
test.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bv8cjh12rwg4t
|
||||||
Loading…
Add table
Add a link
Reference in a new issue