mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-01 20:46:08 +00:00
nuke everything
This commit is contained in:
parent
c279972c1b
commit
0b5176dace
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="56.732mm" height="42.957mm" version="1.1" viewBox="0 0 56.732 42.957" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="translate(-.25763 -31.04)">
|
|
||||||
<g transform="translate(199.65 -45.565)">
|
|
||||||
<path d="m-180.19 96.364c-0.8107 4.3665 1.4732 9.142 3.9319 12.623 0.82935 1.1742 1.9941 3.0788 3.5735 3.2559 1.3127 0.14726 3.1459-1.5186 4.0939-2.2848 0.40063-0.32376 1.0207-1.148 1.592-0.80445 1.552 0.93352 2.521 3.0339 3.8666 4.2491 1.8006 1.6264 3.9896 2.7322 6.1408 3.8002 2.1452 1.065 4.516 2.1686 6.9368 2.3599 0.7634-3.6887 3.9286-7.5421 7.3917-8.9838-0.46636-0.99163-1.5962-1.3829-2.6155-1.5421-2.1819-0.34082-4.4544 0.32489-6.5957-0.3622-2.7237-0.87393-4.8078-3.2504-6.9373-5.049-0.78921-0.66662-1.6434-1.5409-2.6151-1.9167 0.71063-2.4679 1.5178-4.7954 1.6956-7.3917 0.28918-4.2227-1.5661-8.6631-4.8797-11.313-2.0742-1.6591-4.2405-2.5374-6.5957-3.6563-2.0195-0.95939-3.6779-2.8068-6.1408-2.7409-2.0594 0.05508-3.0196 1.7783-4.6625 2.6219-1.1379 0.58429-2.4687 0.82761-3.639 1.3638-3.2889 1.5069-6.562 3.5092-9.0958 6.1057-3.4965 3.5829-5.2855 8.2083-4.4458 13.191 0.32969 1.9563 1.2831 4.736 3.648 4.866 1.8026 0.0992 3.6642-1.4413 5.1174-2.3132 1.1201-0.67208 2.4698-1.0787 3.5253-1.8293 1.2074-0.85846 2.1443-2.1037 3.4116-2.8849 1.006-0.62011 2.2624-0.74702 3.2978-1.3639" fill="#e0823d"/>
|
|
||||||
<path d="m-144.03 110.24v0.22744c-2.7282 1.6701-5.9454 4.6376-6.8231 7.8466-1.5719-0.0676-3.0034-0.65604-4.435-1.2495-2.6279-1.0897-5.3405-2.4136-7.3913-4.4381-1.8298-1.8063-3.0556-4.1361-5.2315-5.5991-1.2451-0.8372-2.6051-0.98412-3.9802-1.4498 0.4973-1.4993 2.1839-2.3758 3.5253-2.9918 0.91146-0.41849 2.0821-1.4225 3.0704-1.5212 1.69-0.16876 3.6827 1.1904 4.8899 2.2074 2.2666 1.9094 4.6371 4.6633 7.3917 5.8454 2.1201 0.90986 4.2994 0.18388 6.482 0.33694 0.92408 0.0647 1.6702 0.42929 2.5018 0.7858" fill="#fff7cb"/>
|
|
||||||
<path d="m-162.51 100.48-1.5921-0.34116c0.40029-1.3896 1.3721-2.5928 1.832-3.9802 1.161-3.5017 0.6076-7.6284-1.8526-10.462-0.86404-0.99538-1.8718-1.9182-2.9361-2.6964-1.28-0.93579-2.1824-1.1784-2.9567-2.6484 1.4694 0.22009 2.913 1.2301 4.0939 2.0804 1.0354 0.74554 2.0579 1.5891 2.8653 2.5821 3.9719 4.8848 2.7276 10.149 0.54631 15.466" fill="#fafabb"/>
|
|
||||||
<path d="m-168.25 87.722h0.11372c0.34002-1.4447-0.58918-2.7442-0.90975-4.0939 1.037 0.344 1.9701 1.2876 2.7291 2.0471 3.7926 3.7938 3.7908 10.187 0.38562 14.317-1.0725 1.3008-3.2815 1.696-4.7068 2.4997-2.4138 1.3609-3.1641 3.2561-3.639 5.8138h-0.11372c-0.91476-1.469-2.0128-2.77-2.8136-4.3213-1.7373-3.366-2.2933-7.3775-1.1274-11.031 0.46067-1.4437 1.1242-2.7825 1.877-4.0939 0.41451-0.722 1.0359-1.4495 1.224-2.2744 0.31852-1.3961 0.22118-3.0822-0.24291-4.435-0.17706-0.51617-0.63784-1.3375-1.3034-1.2253-1.0661 0.17985-1.2338 2.563-1.4698 3.3859-0.63205 2.2046-2.1309 5.9077-4.3316 6.9368v0.11372c1.7907-0.18343 2.7431-2.0052 4.0939-2.9567l-1.5163 4.3213-0.37174 2.23-3.5704 1.463-3.1841 2.777-3.4116 1.1896-3.0704 1.2104c-3.2e-4 -2.9258-2.5897-4.826-2.7174-7.7329-0.0734-1.6706 0.90405-3.5191 1.7595-4.8899 2.5966-4.161 7.3447-6.6032 11.761-8.4152-1.0618 1.8657-3.1206 4.3181-2.9567 6.5957 0.87086-0.64285 1.1669-1.7201 1.7128-2.6155 1.1022-1.8077 2.2324-3.5976 3.8594-4.9842 0.57053-0.48627 1.234-0.84454 1.9332-1.1082 5.7973-2.1863 8.3016 5.298 10.007 9.2766" fill="#fafac8"/>
|
|
||||||
<path d="m-197.59 96.364c0.81268 1.2238 1.7812 2.8608 2.3683 4.2076 0.14717 0.33763 0.46612 1.1888 0.0649 1.4703-0.52861 0.37084-1.3768-0.53334-1.6252-0.90213-0.70879-1.0521-1.318-3.5603-0.80796-4.7757" fill="#f7f0ba"/>
|
|
||||||
<path d="m-168.82 107.51c-0.62033 1.3192-2.225 1.8234-3.5253 2.1607-0.3954-0.93716-0.29658-2.199-0.11372-3.1841 1.3994 0 2.3744 0.49695 3.639 1.0235" fill="#f7f0b9"/>
|
|
||||||
<g transform="matrix(.41249 .21085 -.21085 .41249 -30.267 131.19)">
|
|
||||||
<path d="m-288.87 86.444c9.6869-3.9165 16.476 2.5176 24.551 4.3583 10.153 0.76619 17.086-0.0014 21.787-1.8313" fill="none"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.13598 .10683 -.10683 .13598 41.458 78.459)" fill="#e0892e" font-family="'Pixelic War'">
|
|
||||||
<text transform="matrix(.99215 .088455 -.11224 .9979 0 0)" x="-116.99051" y="-39.508595" font-size="32.495px" stroke-width=".81239" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-116.99051" y="-39.508595" fill="#e0892e" font-family="'Pixelic War'" font-size="32.495px" stroke-width=".81239" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">b</tspan></text>
|
|
||||||
<g font-size="32.377px" stroke-width=".80942">
|
|
||||||
<text transform="rotate(8.4196)" x="-99.723137" y="-36.342819" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-99.723137" y="-36.342819" fill="#e0892e" font-family="'Pixelic War'" font-size="32.377px" stroke-width=".80942" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">i</tspan></text>
|
|
||||||
<text transform="rotate(8.1822)" x="-82.252098" y="-36.873775" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-82.252098" y="-36.873775" fill="#e0892e" font-family="'Pixelic War'" font-size="32.377px" stroke-width=".80942" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">s</tspan></text>
|
|
||||||
<text transform="matrix(1.0599 .015185 .021079 .94378 0 0)" x="-53.695572" y="-46.387341" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-53.695572" y="-46.387341" fill="#e0892e" font-family="'Pixelic War'" font-size="32.377px" stroke-width=".80942" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">c</tspan></text>
|
|
||||||
<text transform="rotate(-7.6565)" x="-31.499388" y="-56.904526" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-31.499388" y="-56.904526" fill="#e0892e" font-family="'Pixelic War'" font-size="32.377px" stroke-width=".80942" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">u</tspan></text>
|
|
||||||
<text transform="matrix(1.0401 -.13007 .17886 .93907 0 0)" x="-12.110206" y="-57.544811" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-12.110206" y="-57.544811" fill="#e0892e" font-family="'Pixelic War'" font-size="32.377px" stroke-width=".80942" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">i</tspan></text>
|
|
||||||
<text transform="rotate(-12.942)" x="4.3184338" y="-57.670696" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="4.3184338" y="-57.670696" fill="#e0892e" font-family="'Pixelic War'" font-size="32.377px" stroke-width=".80942" style="font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal">t</tspan></text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 7.5 KiB |
70
build.ts
70
build.ts
@ -1,70 +0,0 @@
|
|||||||
import { build } from 'https://deno.land/x/dnt@0.26.0/mod.ts';
|
|
||||||
|
|
||||||
await Deno.remove('npm', { recursive: true }).catch((_) => {});
|
|
||||||
|
|
||||||
await build({
|
|
||||||
compilerOptions: {
|
|
||||||
lib: ['webworker', 'es2020'],
|
|
||||||
},
|
|
||||||
shims: {
|
|
||||||
custom: [
|
|
||||||
{
|
|
||||||
package: {
|
|
||||||
name: 'ws',
|
|
||||||
version: '^8.4.0',
|
|
||||||
},
|
|
||||||
globalNames: [
|
|
||||||
{
|
|
||||||
name: 'WebSocket',
|
|
||||||
exportName: 'default',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
package: {
|
|
||||||
author: 'Yuzuru',
|
|
||||||
name: '@oasisjs/biscuit',
|
|
||||||
version: Deno.args[0],
|
|
||||||
description: ' brand new bleeding edge non bloated Discord library',
|
|
||||||
license: 'Apache License 2.0',
|
|
||||||
repository: {
|
|
||||||
type: 'git',
|
|
||||||
url: 'git+https://github.com/oasisjs/biscuit.git',
|
|
||||||
},
|
|
||||||
bugs: {
|
|
||||||
url: 'https://github.com/oasisjs/biscuit/issues',
|
|
||||||
},
|
|
||||||
typesVersions: {
|
|
||||||
'*': {
|
|
||||||
'*': ['./types/mod.d.ts'],
|
|
||||||
'biscuit': ['./types/packages/biscuit/mod.d.ts'],
|
|
||||||
'discordeno': ['./types/packages/discordeno/mod.d.ts'],
|
|
||||||
'cache': ['./types/packages/cache/mod.d.ts'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
entryPoints: [
|
|
||||||
'./mod.ts',
|
|
||||||
{
|
|
||||||
name: './biscuit',
|
|
||||||
path: 'packages/biscuit/mod.ts',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: './discordeno',
|
|
||||||
path: 'packages/discordeno/mod.ts',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: './cache',
|
|
||||||
path: 'packages/cache/mod.ts',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
outDir: './npm',
|
|
||||||
declaration: true,
|
|
||||||
typeCheck: false,
|
|
||||||
test: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// post build steps
|
|
||||||
Deno.copyFileSync('LICENSE', 'npm/LICENSE');
|
|
||||||
Deno.copyFileSync('README.md', 'npm/README.md');
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"fmt": {
|
|
||||||
"options": {
|
|
||||||
"indentWidth": 4,
|
|
||||||
"lineWidth": 120,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
27
egg.json
27
egg.json
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://x.nest.land/eggs@0.3.10/src/schema.json",
|
|
||||||
"name": "biscuit",
|
|
||||||
"entry": "./mod.ts",
|
|
||||||
"description": "A brand new bleeding edge non bloated Discord library",
|
|
||||||
"homepage": "https://github.com/oasisjs/biscuit",
|
|
||||||
"version": "0.2.3",
|
|
||||||
"releaseType": "patch",
|
|
||||||
"unstable": false,
|
|
||||||
"unlisted": false,
|
|
||||||
"files": [
|
|
||||||
"./packages/**/*",
|
|
||||||
"./mod.ts",
|
|
||||||
"LICENSE",
|
|
||||||
"README.md"
|
|
||||||
],
|
|
||||||
"ignore": [
|
|
||||||
"npm",
|
|
||||||
"build.ts",
|
|
||||||
"scripts.ts",
|
|
||||||
".git"
|
|
||||||
],
|
|
||||||
"checkFormat": true,
|
|
||||||
"checkTests": false,
|
|
||||||
"checkInstallation": false,
|
|
||||||
"check": true
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
TOKEN=YOUR_BOT_TOKEN
|
|
@ -1,55 +0,0 @@
|
|||||||
import 'https://deno.land/std@0.146.0/dotenv/load.ts';
|
|
||||||
import {
|
|
||||||
ActionRowBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
ButtonStyles,
|
|
||||||
GatewayIntents,
|
|
||||||
InteractionResponseTypes,
|
|
||||||
Session,
|
|
||||||
} from 'https://deno.land/x/biscuit/mod.ts';
|
|
||||||
|
|
||||||
const token = Deno.env.get('TOKEN') ?? Deno.args[0];
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error('Please provide a token');
|
|
||||||
}
|
|
||||||
|
|
||||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
|
||||||
const session = new Session({ token, intents });
|
|
||||||
|
|
||||||
const PREFIX = '>';
|
|
||||||
const components = new ButtonBuilder().setCustomId('ping').setLabel('Hello!').setStyle(ButtonStyles.Success);
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(components).toJSON();
|
|
||||||
|
|
||||||
session.on('ready', (payload) => {
|
|
||||||
console.log('Logged in as:', payload.user.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
session.on('messageCreate', (message) => {
|
|
||||||
if (message.author?.bot || !message.content.startsWith(PREFIX)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = message.content.substring(PREFIX.length).trim().split(/\s+/gm);
|
|
||||||
const name = args.shift()?.toLowerCase();
|
|
||||||
|
|
||||||
if (name === 'ping') {
|
|
||||||
message.reply({ components: [row] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Follow interaction event
|
|
||||||
session.on('interactionCreate', (interaction) => {
|
|
||||||
if (!interaction.isComponent()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.customId === 'ping') {
|
|
||||||
interaction.respond({
|
|
||||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
|
||||||
data: { content: 'pong!' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
session.start();
|
|
@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Bun example
|
|
||||||
* this example should work on most systems, but if it doesn't just clone the library and import everything from mod.ts
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { GatewayIntents, Session } = require('../mod.ts');
|
|
||||||
|
|
||||||
// if it didn't worked use:
|
|
||||||
// import { GatewayIntents, Session } from "@oasisjs/biscuit";
|
|
||||||
|
|
||||||
const token = process.env.TOKEN;
|
|
||||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
|
||||||
const session = new Session({ token, intents });
|
|
||||||
|
|
||||||
session.on('ready', (payload) => {
|
|
||||||
console.log('Logged in as:', payload.user.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
session.on('messageCreate', async (message) => {
|
|
||||||
// GET
|
|
||||||
if (message.content.startsWith('whatever')) {
|
|
||||||
const whatever = await message.fetch();
|
|
||||||
console.log(whatever);
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST
|
|
||||||
if (message.content.startsWith('ping')) {
|
|
||||||
message.reply({ content: 'pong!' }).catch((err) => console.error(err));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await session.start();
|
|
@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* Deno example
|
|
||||||
*/
|
|
||||||
|
|
||||||
import 'https://deno.land/std@0.146.0/dotenv/load.ts';
|
|
||||||
|
|
||||||
// Session to create a new bot (and intents)
|
|
||||||
import { GatewayIntents, Session } from 'https://deno.land/x/biscuit/mod.ts';
|
|
||||||
|
|
||||||
const token = Deno.env.get('TOKEN') ?? Deno.args[0];
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error('Please provide a token');
|
|
||||||
}
|
|
||||||
|
|
||||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
|
||||||
const session = new Session({ token, intents });
|
|
||||||
|
|
||||||
session.on('ready', (payload) => {
|
|
||||||
console.log('Logged in as:', payload.user.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
const PREFIX = '>';
|
|
||||||
|
|
||||||
session.on('messageCreate', (message) => {
|
|
||||||
if (message.author?.bot || !message.content.startsWith(PREFIX)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = message.content.substring(PREFIX.length).trim().split(/\s+/gm);
|
|
||||||
const name = args.shift()?.toLowerCase();
|
|
||||||
|
|
||||||
if (name === 'ping') {
|
|
||||||
message.reply({ content: 'pong!' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
session.start();
|
|
@ -1,44 +0,0 @@
|
|||||||
/**
|
|
||||||
* Biscuit node example
|
|
||||||
*/
|
|
||||||
|
|
||||||
// process for get the token
|
|
||||||
/** @type {NodeJS.Process} process */
|
|
||||||
import process from 'node:process';
|
|
||||||
|
|
||||||
// Session for create a new bot and intents
|
|
||||||
import { GatewayIntents, Session } from '@oasisjs/biscuit';
|
|
||||||
|
|
||||||
// Discord bot token
|
|
||||||
/** @type {string} token */
|
|
||||||
const token = process.env.TOKEN || 'YOUR_TOKEN_HERE';
|
|
||||||
|
|
||||||
if (token === '') {
|
|
||||||
console.log(new Error('Please set the TOKEN environment variable'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
|
||||||
const session = new Session({ token, intents });
|
|
||||||
|
|
||||||
// Command prefix
|
|
||||||
const PREFIX = '>';
|
|
||||||
|
|
||||||
session.on('ready', (data) => {
|
|
||||||
console.log('Ready! Let\'s start chatting!');
|
|
||||||
console.log('Connected as: ' + data.user.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
session.on('messageCreate', (message) => {
|
|
||||||
if (message.author?.bot || !message.content.startsWith(PREFIX)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = message.content.substring(PREFIX.length).trim().split(/\s+/gm);
|
|
||||||
const name = args.shift()?.toLowerCase();
|
|
||||||
|
|
||||||
if (name === 'ping') {
|
|
||||||
message.reply({ content: 'pong!' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
session.start();
|
|
@ -1,44 +0,0 @@
|
|||||||
/**
|
|
||||||
* Biscuit node example
|
|
||||||
*/
|
|
||||||
|
|
||||||
// process for get the token
|
|
||||||
/** @type {NodeJS.Process} process */
|
|
||||||
const process = require('node:process');
|
|
||||||
|
|
||||||
// Session for create a new bot and intents
|
|
||||||
const { Session, GatewayIntents } = require('@oasisjs/biscuit');
|
|
||||||
|
|
||||||
// Discord bot token
|
|
||||||
/** @type {string} token */
|
|
||||||
const token = process.env.TOKEN || 'YOUR_TOKEN_HERE';
|
|
||||||
|
|
||||||
if (token === '') {
|
|
||||||
return new Error('Please set the TOKEN environment variable');
|
|
||||||
}
|
|
||||||
|
|
||||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
|
||||||
const session = new Session({ token, intents });
|
|
||||||
|
|
||||||
// Command prefix
|
|
||||||
const PREFIX = '>';
|
|
||||||
|
|
||||||
session.on('ready', (data) => {
|
|
||||||
console.log('Ready! Let\'s start chatting!');
|
|
||||||
console.log('Connected as: ' + data.user.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
session.on('messageCreate', (message) => {
|
|
||||||
if (message.author?.bot || !message.content.startsWith(PREFIX)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = message.content.substring(PREFIX.length).trim().split(/\s+/gm);
|
|
||||||
const name = args.shift()?.toLowerCase();
|
|
||||||
|
|
||||||
if (name === 'ping') {
|
|
||||||
message.reply({ content: 'pong!' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
session.start();
|
|
@ -1,49 +0,0 @@
|
|||||||
import 'https://deno.land/std@0.146.0/dotenv/load.ts';
|
|
||||||
import {
|
|
||||||
CreateApplicationCommand,
|
|
||||||
GatewayIntents,
|
|
||||||
InteractionResponseTypes,
|
|
||||||
Session,
|
|
||||||
} from 'https://deno.land/x/biscuit/mod.ts';
|
|
||||||
|
|
||||||
const token = Deno.env.get('TOKEN') ?? Deno.args[0];
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error('Please provide a token');
|
|
||||||
}
|
|
||||||
|
|
||||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
|
||||||
const session = new Session({ token, intents });
|
|
||||||
|
|
||||||
const command: CreateApplicationCommand = {
|
|
||||||
name: 'ping',
|
|
||||||
description: 'Replies with pong!',
|
|
||||||
};
|
|
||||||
|
|
||||||
session.on('ready', async (payload) => {
|
|
||||||
console.log('Logged in as:', payload.user.username);
|
|
||||||
console.log('Creating the application commands...');
|
|
||||||
// create command
|
|
||||||
try {
|
|
||||||
await session.createApplicationCommand(command);
|
|
||||||
console.log('Done!');
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Follow interaction event
|
|
||||||
session.on('interactionCreate', (interaction) => {
|
|
||||||
if (!interaction.isCommand()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.commandName === 'ping') {
|
|
||||||
interaction.respond({
|
|
||||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
|
||||||
data: { content: 'pong!' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await session.start();
|
|
18
hello.ts
18
hello.ts
@ -1,18 +0,0 @@
|
|||||||
// Biscuit Discord library showcase
|
|
||||||
|
|
||||||
import Biscuit, { GatewayIntents } from 'https://deno.land/x/biscuit/mod.ts';
|
|
||||||
|
|
||||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
|
||||||
const session = new Biscuit({ token: Deno.args[0], intents });
|
|
||||||
|
|
||||||
session.on('ready', ({ user }) => {
|
|
||||||
console.log('Logged in as: %s!\nUse !ping to get a reply', user.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
session.on('messageCreate', (message) => {
|
|
||||||
if (message.content.startsWith('!ping')) {
|
|
||||||
message.reply({ content: 'pong!' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
session.start();
|
|
9
mod.ts
9
mod.ts
@ -1,9 +0,0 @@
|
|||||||
import Session from './packages/biscuit/mod.ts';
|
|
||||||
|
|
||||||
export default Session;
|
|
||||||
|
|
||||||
export * from './packages/biscuit/mod.ts';
|
|
||||||
export * from './packages/discordeno/mod.ts';
|
|
||||||
export * from './packages/cache/mod.ts';
|
|
||||||
export * from './packages/api-types/discord.ts';
|
|
||||||
export * from './packages/api-types/shared.ts';
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,439 +0,0 @@
|
|||||||
import type {
|
|
||||||
DiscordAutoModerationActionExecution,
|
|
||||||
DiscordAutoModerationRule,
|
|
||||||
DiscordChannel,
|
|
||||||
DiscordChannelPinsUpdate,
|
|
||||||
DiscordEmoji,
|
|
||||||
DiscordGuild,
|
|
||||||
DiscordGuildBanAddRemove,
|
|
||||||
DiscordGuildEmojisUpdate,
|
|
||||||
DiscordGuildMemberAdd,
|
|
||||||
DiscordGuildMemberRemove,
|
|
||||||
DiscordGuildMemberUpdate,
|
|
||||||
DiscordGuildRoleCreate,
|
|
||||||
DiscordGuildRoleDelete,
|
|
||||||
DiscordGuildRoleUpdate,
|
|
||||||
DiscordIntegration,
|
|
||||||
DiscordIntegrationDelete,
|
|
||||||
DiscordInteraction,
|
|
||||||
DiscordInviteCreate,
|
|
||||||
DiscordInviteDelete,
|
|
||||||
DiscordMemberWithUser,
|
|
||||||
DiscordMessage,
|
|
||||||
DiscordMessageDelete,
|
|
||||||
DiscordMessageReactionAdd,
|
|
||||||
DiscordMessageReactionRemove,
|
|
||||||
DiscordMessageReactionRemoveAll,
|
|
||||||
DiscordMessageReactionRemoveEmoji,
|
|
||||||
DiscordPresenceUpdate,
|
|
||||||
DiscordReady,
|
|
||||||
DiscordRole,
|
|
||||||
DiscordScheduledEvent,
|
|
||||||
DiscordScheduledEventUserAdd,
|
|
||||||
DiscordScheduledEventUserRemove,
|
|
||||||
DiscordThreadListSync,
|
|
||||||
DiscordThreadMembersUpdate,
|
|
||||||
DiscordThreadMemberUpdate,
|
|
||||||
DiscordTypingStart,
|
|
||||||
DiscordUser,
|
|
||||||
DiscordWebhookUpdate,
|
|
||||||
} from '../discordeno/mod.ts';
|
|
||||||
|
|
||||||
import type { Snowflake } from './Snowflake.ts';
|
|
||||||
import type { Session } from './Session.ts';
|
|
||||||
import type { Interaction } from './structures/interactions/InteractionFactory.ts';
|
|
||||||
|
|
||||||
import { AutoModerationRule } from './structures/AutoModerationRule.ts';
|
|
||||||
import { AutoModerationExecution } from './structures/AutoModerationExecution.ts';
|
|
||||||
import { type Channel, ChannelFactory, GuildChannel, ThreadChannel } from './structures/channels.ts';
|
|
||||||
import { type DiscordStageInstanceB, StageInstance } from './structures/StageInstance.ts';
|
|
||||||
import { ScheduledEvent } from './structures/GuildScheduledEvent.ts';
|
|
||||||
import { Presence } from './structures/Presence.ts';
|
|
||||||
|
|
||||||
import ThreadMember from './structures/ThreadMember.ts';
|
|
||||||
import Member from './structures/Member.ts';
|
|
||||||
import Message from './structures/Message.ts';
|
|
||||||
import User from './structures/User.ts';
|
|
||||||
import Integration from './structures/Integration.ts';
|
|
||||||
import { Guild } from './structures/guilds.ts';
|
|
||||||
import InteractionFactory from './structures/interactions/InteractionFactory.ts';
|
|
||||||
import { InviteCreate, NewInviteCreate } from './structures/Invite.ts';
|
|
||||||
import {
|
|
||||||
MessageReactionAdd,
|
|
||||||
MessageReactionRemove,
|
|
||||||
MessageReactionRemoveAll,
|
|
||||||
MessageReactionRemoveEmoji,
|
|
||||||
NewMessageReactionAdd,
|
|
||||||
} from './structures/MessageReaction.ts';
|
|
||||||
|
|
||||||
export type RawHandler<T> = (...args: [Session, number, T]) => void;
|
|
||||||
export type Handler<T extends [obj?: unknown, ddy?: unknown]> = (...args: T) => unknown;
|
|
||||||
|
|
||||||
export const READY: RawHandler<DiscordReady> = (session, shardId, payload) => {
|
|
||||||
session.applicationId = payload.application.id;
|
|
||||||
session.botId = payload.user.id;
|
|
||||||
session.emit('ready', { ...payload, user: new User(session, payload.user) }, shardId);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MESSAGE_CREATE: RawHandler<DiscordMessage> = (session, _shardId, message) => {
|
|
||||||
session.emit('messageCreate', new Message(session, message));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MESSAGE_UPDATE: RawHandler<DiscordMessage> = (session, _shardId, new_message) => {
|
|
||||||
// message is partial
|
|
||||||
if (!new_message.edited_timestamp) {
|
|
||||||
const message = {
|
|
||||||
// TODO: improve this
|
|
||||||
// ...new_message,
|
|
||||||
session,
|
|
||||||
id: new_message.id,
|
|
||||||
guildId: new_message.guild_id,
|
|
||||||
channelId: new_message.channel_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
// all methods of Message can run on partial messages
|
|
||||||
// we aknowledge people that their callback could be partial but giving them all functions of Message
|
|
||||||
Object.setPrototypeOf(message, Message.prototype);
|
|
||||||
|
|
||||||
session.emit('messageUpdate', message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
session.emit('messageUpdate', new Message(session, new_message));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MESSAGE_DELETE: RawHandler<DiscordMessageDelete> = (session, _shardId, { id, channel_id, guild_id }) => {
|
|
||||||
session.emit('messageDelete', { id, channelId: channel_id, guildId: guild_id });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_CREATE: RawHandler<DiscordGuild> = (session, _shardId, guild) => {
|
|
||||||
session.emit('guildCreate', new Guild(session, guild));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_DELETE: RawHandler<DiscordGuild> = (session, _shardId, guild) => {
|
|
||||||
session.emit('guildDelete', { id: guild.id, unavailable: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_MEMBER_ADD: RawHandler<DiscordGuildMemberAdd> = (session, _shardId, member) => {
|
|
||||||
session.emit('guildMemberAdd', new Member(session, member, member.guild_id));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_MEMBER_UPDATE: RawHandler<DiscordGuildMemberUpdate> = (session, _shardId, member) => {
|
|
||||||
session.emit('guildMemberUpdate', new Member(session, member, member.guild_id));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_MEMBER_REMOVE: RawHandler<DiscordGuildMemberRemove> = (session, _shardId, member) => {
|
|
||||||
session.emit('guildMemberRemove', new User(session, member.user), member.guild_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_BAN_ADD: RawHandler<DiscordGuildBanAddRemove> = (session, _shardId, data) => {
|
|
||||||
session.emit('guildBanAdd', { guildId: data.guild_id, user: data.user });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_BAN_REMOVE: RawHandler<DiscordGuildBanAddRemove> = (session, _shardId, data) => {
|
|
||||||
session.emit('guildBanRemove', { guildId: data.guild_id, user: data.user });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_EMOJIS_UPDATE: RawHandler<DiscordGuildEmojisUpdate> = (session, _shardId, data) => {
|
|
||||||
session.emit('guildEmojisUpdate', { guildId: data.guild_id, emojis: data.emojis });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_ROLE_CREATE: RawHandler<DiscordGuildRoleCreate> = (session, _shardId, data) => {
|
|
||||||
session.emit('guildRoleCreate', { guildId: data.guild_id, role: data.role });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_ROLE_UPDATE: RawHandler<DiscordGuildRoleUpdate> = (session, _shardId, data) => {
|
|
||||||
session.emit('guildRoleUpdate', { guildId: data.guild_id, role: data.role });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_ROLE_DELETE: RawHandler<DiscordGuildRoleDelete> = (session, _shardId, data) => {
|
|
||||||
session.emit('guildRoleDelete', { guildId: data.guild_id, roleId: data.role_id });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TYPING_START: RawHandler<DiscordTypingStart> = (session, _shardId, payload) => {
|
|
||||||
session.emit('typingStart', {
|
|
||||||
channelId: payload.channel_id,
|
|
||||||
guildId: payload.guild_id ? payload.guild_id : undefined,
|
|
||||||
userId: payload.user_id,
|
|
||||||
timestamp: payload.timestamp,
|
|
||||||
member: payload.guild_id
|
|
||||||
? new Member(session, payload.member as DiscordMemberWithUser, payload.guild_id)
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const INTERACTION_CREATE: RawHandler<DiscordInteraction> = (session, _shardId, interaction) => {
|
|
||||||
session.emit('interactionCreate', InteractionFactory.from(session, interaction));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CHANNEL_CREATE: RawHandler<DiscordChannel> = (session, _shardId, channel) => {
|
|
||||||
session.emit('channelCreate', ChannelFactory.from(session, channel));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CHANNEL_UPDATE: RawHandler<DiscordChannel> = (session, _shardId, channel) => {
|
|
||||||
session.emit('channelUpdate', ChannelFactory.from(session, channel));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CHANNEL_DELETE: RawHandler<DiscordChannel> = (session, _shardId, channel) => {
|
|
||||||
if (!channel.guild_id) return;
|
|
||||||
|
|
||||||
session.emit('channelDelete', new GuildChannel(session, channel, channel.guild_id));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const THREAD_CREATE: RawHandler<DiscordChannel> = (session, _shardId, channel) => {
|
|
||||||
if (!channel.guild_id) return;
|
|
||||||
|
|
||||||
session.emit('threadCreate', new ThreadChannel(session, channel, channel.guild_id));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const THREAD_UPDATE: RawHandler<DiscordChannel> = (session, _shardId, channel) => {
|
|
||||||
if (!channel.guild_id) return;
|
|
||||||
|
|
||||||
session.emit('threadUpdate', new ThreadChannel(session, channel, channel.guild_id));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const THREAD_DELETE: RawHandler<DiscordChannel> = (session, _shardId, channel) => {
|
|
||||||
if (!channel.guild_id) return;
|
|
||||||
|
|
||||||
session.emit('threadDelete', new ThreadChannel(session, channel, channel.guild_id));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const THREAD_MEMBER_UPDATE: RawHandler<DiscordThreadMemberUpdate> = (session, _shardId, payload) => {
|
|
||||||
session.emit('threadMemberUpdate', {
|
|
||||||
guildId: payload.guild_id,
|
|
||||||
id: payload.id,
|
|
||||||
userId: payload.user_id,
|
|
||||||
joinedAt: payload.joined_at,
|
|
||||||
flags: payload.flags,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const THREAD_MEMBERS_UPDATE: RawHandler<DiscordThreadMembersUpdate> = (session, _shardId, payload) => {
|
|
||||||
session.emit('threadMembersUpdate', {
|
|
||||||
memberCount: payload.member_count,
|
|
||||||
addedMembers: payload.added_members
|
|
||||||
? payload.added_members.map((tm) => new ThreadMember(session, tm))
|
|
||||||
: undefined,
|
|
||||||
removedMemberIds: payload.removed_member_ids ? payload.removed_member_ids : undefined,
|
|
||||||
guildId: payload.guild_id,
|
|
||||||
id: payload.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const THREAD_LIST_SYNC: RawHandler<DiscordThreadListSync> = (session, _shardId, payload) => {
|
|
||||||
session.emit('threadListSync', {
|
|
||||||
guildId: payload.guild_id,
|
|
||||||
channelIds: payload.channel_ids ?? [],
|
|
||||||
threads: payload.threads.map((channel) => new ThreadChannel(session, channel, payload.guild_id)),
|
|
||||||
members: payload.members.map((member) => new ThreadMember(session, member)),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CHANNEL_PINS_UPDATE: RawHandler<DiscordChannelPinsUpdate> = (session, _shardId, payload) => {
|
|
||||||
session.emit('channelPinsUpdate', {
|
|
||||||
guildId: payload.guild_id,
|
|
||||||
channelId: payload.channel_id,
|
|
||||||
lastPinTimestamp: payload.last_pin_timestamp ? Date.parse(payload.last_pin_timestamp) : undefined,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const USER_UPDATE: RawHandler<DiscordUser> = (session, _shardId, payload) => {
|
|
||||||
session.emit('userUpdate', new User(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PRESENCE_UPDATE: RawHandler<DiscordPresenceUpdate> = (session, _shardId, payload) => {
|
|
||||||
session.emit('presenceUpdate', new Presence(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WEBHOOKS_UPDATE: RawHandler<DiscordWebhookUpdate> = (session, _shardId, webhook) => {
|
|
||||||
session.emit('webhooksUpdate', { guildId: webhook.guild_id, channelId: webhook.channel_id });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const INTEGRATION_CREATE: RawHandler<DiscordIntegration & { guildId?: Snowflake }> = (
|
|
||||||
session,
|
|
||||||
_shardId,
|
|
||||||
payload,
|
|
||||||
) => {
|
|
||||||
session.emit('integrationCreate', new Integration(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const INTEGRATION_UPDATE: RawHandler<DiscordIntegration & { guildId?: Snowflake }> = (
|
|
||||||
session,
|
|
||||||
_shardId,
|
|
||||||
payload,
|
|
||||||
) => {
|
|
||||||
session.emit('integrationCreate', new Integration(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const INTEGRATION_DELETE: RawHandler<DiscordIntegrationDelete> = (session, _shardId, payload) => {
|
|
||||||
session.emit('integrationDelete', {
|
|
||||||
id: payload.id,
|
|
||||||
guildId: payload.guild_id,
|
|
||||||
applicationId: payload.application_id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AUTO_MODERATION_RULE_CREATE: RawHandler<DiscordAutoModerationRule> = (session, _shardId, payload) => {
|
|
||||||
session.emit('autoModerationRuleCreate', new AutoModerationRule(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AUTO_MODERATION_RULE_UPDATE: RawHandler<DiscordAutoModerationRule> = (session, _shardId, payload) => {
|
|
||||||
session.emit('autoModerationRuleUpdate', new AutoModerationRule(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AUTO_MODERATION_RULE_DELETE: RawHandler<DiscordAutoModerationRule> = (session, _shardId, payload) => {
|
|
||||||
session.emit('autoModerationRuleDelete', new AutoModerationRule(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AUTO_MODERATION_ACTION_EXECUTE: RawHandler<DiscordAutoModerationActionExecution> = (
|
|
||||||
session,
|
|
||||||
_shardId,
|
|
||||||
payload,
|
|
||||||
) => {
|
|
||||||
session.emit('autoModerationActionExecution', new AutoModerationExecution(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MESSAGE_REACTION_ADD: RawHandler<DiscordMessageReactionAdd> = (session, _shardId, reaction) => {
|
|
||||||
session.emit('messageReactionAdd', NewMessageReactionAdd(session, reaction));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MESSAGE_REACTION_REMOVE: RawHandler<DiscordMessageReactionRemove> = (session, _shardId, reaction) => {
|
|
||||||
session.emit('messageReactionRemove', NewMessageReactionAdd(session, reaction));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MESSAGE_REACTION_REMOVE_ALL: RawHandler<DiscordMessageReactionRemoveAll> = (
|
|
||||||
session,
|
|
||||||
_shardId,
|
|
||||||
reaction,
|
|
||||||
) => {
|
|
||||||
session.emit('messageReactionRemoveAll', NewMessageReactionAdd(session, reaction as DiscordMessageReactionAdd));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MESSAGE_REACTION_REMOVE_EMOJI: RawHandler<DiscordMessageReactionRemoveEmoji> = (
|
|
||||||
session,
|
|
||||||
_shardId,
|
|
||||||
reaction,
|
|
||||||
) => {
|
|
||||||
session.emit('messageReactionRemoveEmoji', NewMessageReactionAdd(session, reaction as DiscordMessageReactionAdd));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const INVITE_CREATE: RawHandler<DiscordInviteCreate> = (session, _shardId, invite) => {
|
|
||||||
session.emit('inviteCreate', NewInviteCreate(session, invite));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const INVITE_DELETE: RawHandler<DiscordInviteDelete> = (session, _shardId, data) => {
|
|
||||||
session.emit('inviteDelete', { channelId: data.channel_id, guildId: data.guild_id, code: data.code });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const STAGE_INSTANCE_CREATE: RawHandler<DiscordStageInstanceB> = (session, _shardId, payload) => {
|
|
||||||
session.emit('stageInstanceCreate', new StageInstance(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const STAGE_INSTANCE_UPDATE: RawHandler<DiscordStageInstanceB> = (session, _shardId, payload) => {
|
|
||||||
session.emit('stageInstanceUpdate', new StageInstance(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const STAGE_INSTANCE_DELETE: RawHandler<DiscordStageInstanceB> = (session, _shardId, payload) => {
|
|
||||||
session.emit('stageInstanceDelete', new StageInstance(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_SCHEDULED_EVENT_CREATE: RawHandler<DiscordScheduledEvent> = (session, _shardId, payload) => {
|
|
||||||
session.emit('guildScheduledEventCreate', new ScheduledEvent(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_SCHEDULED_EVENT_UPDATE: RawHandler<DiscordScheduledEvent> = (session, _shardId, payload) => {
|
|
||||||
session.emit('guildScheduledEventUpdate', new ScheduledEvent(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_SCHEDULED_EVENT_DELETE: RawHandler<DiscordScheduledEvent> = (session, _shardId, payload) => {
|
|
||||||
session.emit('guildScheduledEventDelete', new ScheduledEvent(session, payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_SCHEDULED_EVENT_USER_ADD: RawHandler<DiscordScheduledEventUserAdd> = (
|
|
||||||
session,
|
|
||||||
_shardId,
|
|
||||||
payload,
|
|
||||||
) => {
|
|
||||||
session.emit('guildScheduledEventUserAdd', {
|
|
||||||
scheduledEventId: payload.guild_scheduled_event_id,
|
|
||||||
userId: payload.user_id,
|
|
||||||
guildId: payload.guild_id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GUILD_SCHEDULED_EVENT_USER_REMOVE: RawHandler<DiscordScheduledEventUserRemove> = (
|
|
||||||
session,
|
|
||||||
_shardId,
|
|
||||||
payload,
|
|
||||||
) => {
|
|
||||||
session.emit('guildScheduledEventUserRemove', {
|
|
||||||
scheduledEventId: payload.guild_scheduled_event_id,
|
|
||||||
userId: payload.user_id,
|
|
||||||
guildId: payload.guild_id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const raw: RawHandler<unknown> = (session, shardId, data) => {
|
|
||||||
session.emit('raw', data as { t: string; d: unknown }, shardId);
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface Ready extends Omit<DiscordReady, 'user'> {
|
|
||||||
user: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-fmt-ignore-file
|
|
||||||
export interface Events {
|
|
||||||
"ready": Handler<[Ready, number]>;
|
|
||||||
"messageCreate": Handler<[Message]>;
|
|
||||||
"messageUpdate": Handler<[Partial<Message>]>;
|
|
||||||
"messageDelete": Handler<[{ id: Snowflake, channelId: Snowflake, guildId?: Snowflake }]>;
|
|
||||||
"messageReactionAdd": Handler<[MessageReactionAdd]>;
|
|
||||||
"messageReactionRemove": Handler<[MessageReactionRemove]>;
|
|
||||||
"messageReactionRemoveAll": Handler<[MessageReactionRemoveAll]>;
|
|
||||||
"messageReactionRemoveEmoji": Handler<[MessageReactionRemoveEmoji]>;
|
|
||||||
"guildCreate": Handler<[Guild]>;
|
|
||||||
"guildDelete": Handler<[{ id: Snowflake, unavailable: boolean }]>;
|
|
||||||
"guildMemberAdd": Handler<[Member]>;
|
|
||||||
"guildMemberUpdate": Handler<[Member]>;
|
|
||||||
"guildMemberRemove": Handler<[User, Snowflake]>;
|
|
||||||
"guildBanAdd": Handler<[{ guildId: Snowflake, user: DiscordUser}]>;
|
|
||||||
"guildBanRemove": Handler<[{ guildId: Snowflake, user: DiscordUser }]>
|
|
||||||
"guildEmojisUpdate": Handler<[{ guildId: Snowflake, emojis: DiscordEmoji[] }]>
|
|
||||||
"guildRoleCreate": Handler<[{ guildId: Snowflake, role: DiscordRole }]>;
|
|
||||||
"guildRoleUpdate": Handler<[{ guildId: Snowflake, role: DiscordRole }]>;
|
|
||||||
"guildRoleDelete": Handler<[{ guildId: Snowflake, roleId: Snowflake }]>;
|
|
||||||
"typingStart": Handler<[{channelId: Snowflake, guildId?: Snowflake, userId: Snowflake, timestamp: number, member?: Member}]>
|
|
||||||
"channelCreate": Handler<[Channel]>;
|
|
||||||
"channelUpdate": Handler<[Channel]>;
|
|
||||||
"channelDelete": Handler<[GuildChannel]>;
|
|
||||||
"channelPinsUpdate": Handler<[{ guildId?: Snowflake, channelId: Snowflake, lastPinTimestamp?: number }]>
|
|
||||||
"threadCreate": Handler<[ThreadChannel]>;
|
|
||||||
"threadUpdate": Handler<[ThreadChannel]>;
|
|
||||||
"threadDelete": Handler<[ThreadChannel]>;
|
|
||||||
"threadListSync": Handler<[{ guildId: Snowflake, channelIds: Snowflake[], threads: ThreadChannel[], members: ThreadMember[] }]>
|
|
||||||
"threadMemberUpdate": Handler<[{id: Snowflake, userId: Snowflake, guildId: Snowflake, joinedAt: string, flags: number }]>
|
|
||||||
"threadMembersUpdate": Handler<[{id: Snowflake, memberCount: number, addedMembers?: ThreadMember[], guildId: Snowflake, removedMemberIds?: Snowflake[]}]>
|
|
||||||
"interactionCreate": Handler<[Interaction]>;
|
|
||||||
"integrationCreate": Handler<[Integration]>;
|
|
||||||
"integrationUpdate": Handler<[Integration]>;
|
|
||||||
"integrationDelete": Handler<[{ id: Snowflake, guildId?: Snowflake, applicationId?: Snowflake }]>;
|
|
||||||
"inviteCreate": Handler<[InviteCreate]>;
|
|
||||||
"inviteDelete": Handler<[{ channelId: string, guildId?: string, code: string }]>;
|
|
||||||
"autoModerationRuleCreate": Handler<[AutoModerationRule]>;
|
|
||||||
"autoModerationRuleUpdate": Handler<[AutoModerationRule]>;
|
|
||||||
"autoModerationRuleDelete": Handler<[AutoModerationRule]>;
|
|
||||||
"autoModerationActionExecution":Handler<[AutoModerationExecution]>
|
|
||||||
"stageInstanceCreate": Handler<[StageInstance]>;
|
|
||||||
"stageInstanceUpdate": Handler<[StageInstance]>;
|
|
||||||
"stageInstanceDelete": Handler<[StageInstance]>;
|
|
||||||
"guildScheduledEventCreate": Handler<[ScheduledEvent]>;
|
|
||||||
"guildScheduledEventUpdate": Handler<[ScheduledEvent]>;
|
|
||||||
"guildScheduledEventDelete": Handler<[ScheduledEvent]>;
|
|
||||||
"guildScheduledEventUserAdd": Handler<[{scheduledEventId: Snowflake, userId: Snowflake, guildId: Snowflake}]>
|
|
||||||
"guildScheduledEventUserRemove": Handler<[{scheduledEventId: Snowflake, userId: Snowflake, guildId: Snowflake}]>
|
|
||||||
"raw": Handler<[{ t: string, d: unknown }, number]>;
|
|
||||||
"webhooksUpdate": Handler<[{ guildId: Snowflake, channelId: Snowflake }]>;
|
|
||||||
"userUpdate": Handler<[User]>;
|
|
||||||
"presenceUpdate": Handler<[Presence]>;
|
|
||||||
"debug": Handler<[string]>;
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
import type { Snowflake } from './Snowflake.ts';
|
|
||||||
import { baseEndpoints as Endpoints } from '../discordeno/mod.ts';
|
|
||||||
|
|
||||||
export function USER_AVATAR(userId: Snowflake, icon: string): string {
|
|
||||||
return `${Endpoints.CDN_URL}/avatars/${userId}/${icon}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function EMOJI_URL(id: Snowflake, animated = false): string {
|
|
||||||
return `https://cdn.discordapp.com/emojis/${id}.${animated ? 'gif' : 'png'}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function USER_DEFAULT_AVATAR(
|
|
||||||
/** user discriminator */
|
|
||||||
altIcon: number,
|
|
||||||
): string {
|
|
||||||
return `${Endpoints.CDN_URL}/embed/avatars/${altIcon}.png`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_BANNER(guildId: Snowflake, icon: string): string {
|
|
||||||
return `${Endpoints.CDN_URL}/banners/${guildId}/${icon}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_SPLASH(guildId: Snowflake, icon: string): string {
|
|
||||||
return `${Endpoints.CDN_URL}/splashes/${guildId}/${icon}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_ICON(guildId: Snowflake, icon: string): string {
|
|
||||||
return `${Endpoints.CDN_URL}/icons/${guildId}/${icon}`;
|
|
||||||
}
|
|
@ -1,473 +0,0 @@
|
|||||||
import type { Snowflake } from './Snowflake.ts';
|
|
||||||
|
|
||||||
// cdn endpoints
|
|
||||||
export * from './Cdn.ts';
|
|
||||||
|
|
||||||
export function USER(userId?: Snowflake): string {
|
|
||||||
if (!userId) return '/users/@me';
|
|
||||||
return `/users/${userId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GATEWAY_BOT(): string {
|
|
||||||
return '/gateway/bot';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetMessagesOptions {
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetMessagesOptions {
|
|
||||||
around?: Snowflake;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetMessagesOptions {
|
|
||||||
before?: Snowflake;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetMessagesOptions {
|
|
||||||
after?: Snowflake;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_INVITES(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/invites`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_TYPING(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/typing`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_CREATE_THREAD(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/threads`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MESSAGE_CREATE_THREAD(channelId: Snowflake, messageId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/messages/${messageId}/threads`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** used to send messages */
|
|
||||||
export function CHANNEL_MESSAGES(channelId: Snowflake, options?: GetMessagesOptions): string {
|
|
||||||
let url = `/channels/${channelId}/messages?`;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.after) url += `after=${options.after}`;
|
|
||||||
if (options.before) url += `&before=${options.before}`;
|
|
||||||
if (options.around) url += `&around=${options.around}`;
|
|
||||||
if (options.limit) url += `&limit=${options.limit}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** used to edit messages */
|
|
||||||
export function CHANNEL_MESSAGE(channelId: Snowflake, messageId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/messages/${messageId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** used to kick members */
|
|
||||||
export function GUILD_MEMBER(guildId: Snowflake, userId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/members/${userId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** used to ban members */
|
|
||||||
export function GUILD_BAN(guildId: Snowflake, userId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/bans/${userId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetBans {
|
|
||||||
limit?: number;
|
|
||||||
before?: Snowflake;
|
|
||||||
after?: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** used to unban members */
|
|
||||||
export function GUILD_BANS(guildId: Snowflake, options?: GetBans): string {
|
|
||||||
let url = `/guilds/${guildId}/bans?`;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.limit) url += `limit=${options.limit}`;
|
|
||||||
if (options.after) url += `&after=${options.after}`;
|
|
||||||
if (options.before) url += `&before=${options.before}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_ROLE(guildId: Snowflake, roleId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/roles/${roleId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_ROLES(guildId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/roles`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function USER_GUILDS(guildId?: Snowflake): string {
|
|
||||||
if (guildId) return `/users/@me/guilds/${guildId}`;
|
|
||||||
return `/users/@me/guilds/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function USER_DM() {
|
|
||||||
return `/users/@me/channels`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_EMOJIS(guildId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/emojis`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_EMOJI(guildId: Snowflake, emojiId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/emojis/${emojiId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetInvite {
|
|
||||||
withCounts?: boolean;
|
|
||||||
withExpiration?: boolean;
|
|
||||||
scheduledEventId?: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILDS(guildId?: Snowflake): string {
|
|
||||||
if (guildId) return `/guilds/${guildId}`;
|
|
||||||
return `/guilds`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AUTO_MODERATION_RULES(guildId: Snowflake, ruleId?: Snowflake): string {
|
|
||||||
if (ruleId) {
|
|
||||||
return `/guilds/${guildId}/auto-moderation/rules/${ruleId}`;
|
|
||||||
}
|
|
||||||
return `/guilds/${guildId}/auto-moderation/rules`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function INVITE(inviteCode: string, options?: GetInvite): string {
|
|
||||||
let url = `/invites/${inviteCode}?`;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.withCounts) url += `with_counts=${options.withCounts}`;
|
|
||||||
if (options.withExpiration) url += `&with_expiration=${options.withExpiration}`;
|
|
||||||
if (options.scheduledEventId) url += `&guild_scheduled_event_id=${options.scheduledEventId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_INVITES(guildId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/invites`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function INTERACTION_ID_TOKEN(interactionId: Snowflake, token: string): string {
|
|
||||||
return `/interactions/${interactionId}/${token}/callback`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WEBHOOK_MESSAGE_ORIGINAL(webhookId: Snowflake, token: string, options?: { threadId?: bigint }): string {
|
|
||||||
let url = `/webhooks/${webhookId}/${token}/messages/@original?`;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.threadId) url += `threadId=${options.threadId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WEBHOOK_MESSAGE(
|
|
||||||
webhookId: Snowflake,
|
|
||||||
token: string,
|
|
||||||
messageId: Snowflake,
|
|
||||||
options?: { threadId?: Snowflake },
|
|
||||||
): string {
|
|
||||||
let url = `/webhooks/${webhookId}/${token}/messages/${messageId}?`;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.threadId) url += `threadId=${options.threadId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WEBHOOK_TOKEN(webhookId: Snowflake, token?: string): string {
|
|
||||||
if (!token) return `/webhooks/${webhookId}`;
|
|
||||||
return `/webhooks/${webhookId}/${token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebhookOptions {
|
|
||||||
wait?: boolean;
|
|
||||||
threadId?: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WEBHOOK(webhookId: Snowflake, token: string, options?: WebhookOptions): string {
|
|
||||||
let url = `/webhooks/${webhookId}/${token}`;
|
|
||||||
|
|
||||||
if (options?.wait) url += `?wait=${options.wait}`;
|
|
||||||
if (options?.threadId) url += `?threadId=${options.threadId}`;
|
|
||||||
if (options?.wait && options.threadId) url += `?wait=${options.wait}&threadId=${options.threadId}`;
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function USER_NICK(guildId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/members/@me`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/guild#get-guild-prune-count
|
|
||||||
*/
|
|
||||||
export interface GetGuildPruneCountQuery {
|
|
||||||
days?: number;
|
|
||||||
includeRoles?: Snowflake | Snowflake[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_PRUNE(guildId: Snowflake, options?: GetGuildPruneCountQuery): string {
|
|
||||||
let url = `/guilds/${guildId}/prune?`;
|
|
||||||
|
|
||||||
if (options?.days) url += `days=${options.days}`;
|
|
||||||
if (options?.includeRoles) url += `&include_roles=${options.includeRoles}`;
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_PIN(channelId: Snowflake, messageId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/pins/${messageId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_PINS(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/pins`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_MESSAGE_REACTION_ME(channelId: Snowflake, messageId: Snowflake, emoji: string): string {
|
|
||||||
return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_MESSAGE_REACTION_USER(
|
|
||||||
channelId: Snowflake,
|
|
||||||
messageId: Snowflake,
|
|
||||||
emoji: string,
|
|
||||||
userId: Snowflake,
|
|
||||||
) {
|
|
||||||
return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/${userId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_MESSAGE_REACTIONS(channelId: Snowflake, messageId: Snowflake) {
|
|
||||||
return `/channels/${channelId}/messages/${messageId}/reactions`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#get-reactions-query-string-params
|
|
||||||
*/
|
|
||||||
export interface GetReactions {
|
|
||||||
after?: string;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_MESSAGE_REACTION(
|
|
||||||
channelId: Snowflake,
|
|
||||||
messageId: Snowflake,
|
|
||||||
emoji: string,
|
|
||||||
options?: GetReactions,
|
|
||||||
): string {
|
|
||||||
let url = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}?`;
|
|
||||||
|
|
||||||
if (options?.after) url += `after=${options.after}`;
|
|
||||||
if (options?.limit) url += `&limit=${options.limit}`;
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_MESSAGE_CROSSPOST(channelId: Snowflake, messageId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/messages/${messageId}/crosspost`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_MEMBER_ROLE(guildId: Snowflake, memberId: Snowflake, roleId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/members/${memberId}/roles/${roleId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CHANNEL_WEBHOOKS(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/webhooks`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_START_PUBLIC(channelId: Snowflake, messageId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/messages/${messageId}/threads`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_START_PRIVATE(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/threads`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_ACTIVE(guildId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/threads/active`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ListArchivedThreads {
|
|
||||||
before?: number;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_ME(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/thread-members/@me`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_MEMBERS(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/thread-members`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_USER(channelId: Snowflake, userId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/thread-members/${userId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_ARCHIVED(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/threads/archived`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_ARCHIVED_PUBLIC(channelId: Snowflake, options?: ListArchivedThreads): string {
|
|
||||||
let url = `/channels/${channelId}/threads/archived/public?`;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.before) url += `before=${new Date(options.before).toISOString()}`;
|
|
||||||
if (options.limit) url += `&limit=${options.limit}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_ARCHIVED_PRIVATE(channelId: Snowflake, options?: ListArchivedThreads): string {
|
|
||||||
let url = `/channels/${channelId}/threads/archived/private?`;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.before) url += `before=${new Date(options.before).toISOString()}`;
|
|
||||||
if (options.limit) url += `&limit=${options.limit}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function THREAD_ARCHIVED_PRIVATE_JOINED(channelId: Snowflake, options?: ListArchivedThreads): string {
|
|
||||||
let url = `/channels/${channelId}/users/@me/threads/archived/private?`;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
if (options.before) url += `before=${new Date(options.before).toISOString()}`;
|
|
||||||
if (options.limit) url += `&limit=${options.limit}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FORUM_START(channelId: Snowflake): string {
|
|
||||||
return `/channels/${channelId}/threads?has_message=true`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function STAGE_INSTANCES(): string {
|
|
||||||
return `/stage-instances`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function STAGE_INSTANCE(channelId: Snowflake): string {
|
|
||||||
return `/stage-instances/${channelId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function APPLICATION_COMMANDS(appId: Snowflake, commandId?: Snowflake): string {
|
|
||||||
if (commandId) return `/applications/${appId}/commands/${commandId}`;
|
|
||||||
return `/applications/${appId}/commands`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_APPLICATION_COMMANDS(appId: Snowflake, guildId: Snowflake, commandId?: Snowflake): string {
|
|
||||||
if (commandId) return `/applications/${appId}/guilds/${guildId}/commands/${commandId}`;
|
|
||||||
return `/applications/${appId}/guilds/${guildId}/commands`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_APPLICATION_COMMANDS_PERMISSIONS(
|
|
||||||
appId: Snowflake,
|
|
||||||
guildId: Snowflake,
|
|
||||||
commandId?: Snowflake,
|
|
||||||
): string {
|
|
||||||
if (commandId) return `/applications/${appId}/guilds/${guildId}/commands/${commandId}/permissions`;
|
|
||||||
return `/applications/${appId}/guilds/${guildId}/commands/permissions`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function APPLICATION_COMMANDS_LOCALIZATIONS(
|
|
||||||
appId: Snowflake,
|
|
||||||
commandId: Snowflake,
|
|
||||||
withLocalizations?: boolean,
|
|
||||||
): string {
|
|
||||||
let url = `/applications/${appId}/commands/${commandId}?`;
|
|
||||||
|
|
||||||
if (withLocalizations !== undefined) {
|
|
||||||
url += `withLocalizations=${withLocalizations}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_APPLICATION_COMMANDS_LOCALIZATIONS(
|
|
||||||
appId: Snowflake,
|
|
||||||
guildId: Snowflake,
|
|
||||||
commandId: Snowflake,
|
|
||||||
withLocalizations?: boolean,
|
|
||||||
): string {
|
|
||||||
let url = `/applications/${appId}/guilds/${guildId}/commands/${commandId}?`;
|
|
||||||
|
|
||||||
if (withLocalizations !== undefined) {
|
|
||||||
url += `with_localizations=${withLocalizations}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function STICKER(id: Snowflake): string {
|
|
||||||
return `stickers/${id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function STICKER_PACKS(): string {
|
|
||||||
return `stickers-packs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GUILD_STICKERS(guildId: Snowflake, stickerId?: Snowflake): string {
|
|
||||||
if (stickerId) return `/guilds/${guildId}/stickers/${stickerId}`;
|
|
||||||
return `/guilds/${guildId}/stickers`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the widget for the guild.
|
|
||||||
* @link https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
|
|
||||||
*/
|
|
||||||
export interface GetWidget {
|
|
||||||
get: 'json' | 'image' | 'settings';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* /guilds/{guildId}/widget
|
|
||||||
* @link https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
|
|
||||||
*/
|
|
||||||
export function GUILD_WIDGET(guildId: Snowflake, options: GetWidget = { get: 'settings' }): string {
|
|
||||||
let url = `/guilds/${guildId}/widget`;
|
|
||||||
if (options.get === 'json') {
|
|
||||||
url += '.json';
|
|
||||||
} else if (options.get === 'image') {
|
|
||||||
url += '.png';
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @link https://discord.com/developers/docs/resources/guild#get-guild-voice-regions */
|
|
||||||
export function GUILD_VOICE_REGIONS(guildId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/regions`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/guild#get-guild-vanity-url
|
|
||||||
* @param guildId The guild
|
|
||||||
* @returns Get vanity URL
|
|
||||||
*/
|
|
||||||
export function GUILD_VANITY(guildId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/vanity-url`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/guild#get-guild-preview
|
|
||||||
* @param guildId The guild
|
|
||||||
* @returns Get guild preview url
|
|
||||||
*/
|
|
||||||
export function GUILD_PREVIEW(guildId: Snowflake): string {
|
|
||||||
return `/guilds/${guildId}/preview`;
|
|
||||||
}
|
|
@ -1,465 +0,0 @@
|
|||||||
import type {
|
|
||||||
ApplicationCommandPermissionTypes,
|
|
||||||
AtLeastOne,
|
|
||||||
DiscordApplicationCommand,
|
|
||||||
DiscordApplicationCommandOption,
|
|
||||||
DiscordGetGatewayBot,
|
|
||||||
DiscordGuildApplicationCommandPermissions,
|
|
||||||
DiscordUser,
|
|
||||||
GatewayBot,
|
|
||||||
GatewayIntents,
|
|
||||||
Localization,
|
|
||||||
} from '../discordeno/mod.ts';
|
|
||||||
|
|
||||||
import type { DiscordGatewayPayload, Shard } from '../discordeno/mod.ts';
|
|
||||||
import type { Events } from './Actions.ts';
|
|
||||||
import type { PermissionResolvable } from './structures/Permissions.ts';
|
|
||||||
import type { Activities, StatusTypes } from './structures/Presence.ts';
|
|
||||||
|
|
||||||
import { Permissions } from './structures/Permissions.ts';
|
|
||||||
import { Snowflake } from './Snowflake.ts';
|
|
||||||
import { EventEmitter } from './util/EventEmmiter.ts';
|
|
||||||
import {
|
|
||||||
ApplicationCommandTypes,
|
|
||||||
createGatewayManager,
|
|
||||||
createRestManager,
|
|
||||||
GatewayOpcodes,
|
|
||||||
getBotIdFromToken,
|
|
||||||
} from '../discordeno/mod.ts';
|
|
||||||
|
|
||||||
import User from './structures/User.ts';
|
|
||||||
import { urlToBase64 } from './util/urlToBase64.ts';
|
|
||||||
|
|
||||||
import * as Routes from './Routes.ts';
|
|
||||||
import * as Actions from './Actions.ts';
|
|
||||||
|
|
||||||
export type DiscordRawEventHandler = (shard: Shard, data: DiscordGatewayPayload) => unknown;
|
|
||||||
|
|
||||||
// INTERACTIONS
|
|
||||||
|
|
||||||
export type CreateApplicationCommands = CreateApplicationCommand | CreateContextApplicationCommand;
|
|
||||||
export type UpsertDataApplicationCommands =
|
|
||||||
| AtLeastOne<CreateApplicationCommand>
|
|
||||||
| AtLeastOne<CreateContextApplicationCommand>;
|
|
||||||
export type LastCreateApplicationCommands =
|
|
||||||
| AtLeastOne<CreateContextApplicationCommand>
|
|
||||||
| AtLeastOne<CreateApplicationCommand>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params
|
|
||||||
*/
|
|
||||||
export interface CreateApplicationCommand {
|
|
||||||
name: string;
|
|
||||||
nameLocalizations?: Localization;
|
|
||||||
description: string;
|
|
||||||
descriptionLocalizations?: Localization;
|
|
||||||
type?: ApplicationCommandTypes;
|
|
||||||
options?: DiscordApplicationCommandOption[];
|
|
||||||
defaultMemberPermissions?: PermissionResolvable;
|
|
||||||
dmPermission?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params
|
|
||||||
*/
|
|
||||||
export interface CreateContextApplicationCommand extends Omit<CreateApplicationCommand, 'options'> {
|
|
||||||
type: ApplicationCommandTypes.Message | ApplicationCommandTypes.User;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/application-commands#endpoints-query-string-params
|
|
||||||
*/
|
|
||||||
export interface GetApplicationCommand {
|
|
||||||
guildId?: Snowflake;
|
|
||||||
withLocalizations?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpsertApplicationCommands extends CreateApplicationCommand {
|
|
||||||
id?: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions
|
|
||||||
*/
|
|
||||||
export interface ApplicationCommandPermissions {
|
|
||||||
id: Snowflake;
|
|
||||||
type: ApplicationCommandPermissionTypes;
|
|
||||||
permission: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions
|
|
||||||
*/
|
|
||||||
export interface ApplicationCommandPermissions {
|
|
||||||
id: Snowflake;
|
|
||||||
type: ApplicationCommandPermissionTypes;
|
|
||||||
permission: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// END INTERACTIONS
|
|
||||||
|
|
||||||
export interface RestOptions {
|
|
||||||
secretKey?: string;
|
|
||||||
applicationId?: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GatewayOptions {
|
|
||||||
botId?: Snowflake;
|
|
||||||
data?: GatewayBot;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionOptions {
|
|
||||||
token: string;
|
|
||||||
rawHandler?: DiscordRawEventHandler;
|
|
||||||
intents?: GatewayIntents;
|
|
||||||
rest?: RestOptions;
|
|
||||||
gateway?: GatewayOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/topics/gateway#update-status
|
|
||||||
*/
|
|
||||||
export interface StatusUpdate {
|
|
||||||
activities: Activities[];
|
|
||||||
status: StatusTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives a Token, connects
|
|
||||||
* Most of the command implementations were adapted from Discordeno (https://github.com/discordeno/discordeno)
|
|
||||||
*/
|
|
||||||
export class Session extends EventEmitter {
|
|
||||||
options: SessionOptions;
|
|
||||||
|
|
||||||
rest: ReturnType<typeof createRestManager>;
|
|
||||||
gateway: ReturnType<typeof createGatewayManager>;
|
|
||||||
|
|
||||||
#botId: Snowflake;
|
|
||||||
#applicationId?: Snowflake;
|
|
||||||
|
|
||||||
set applicationId(id: Snowflake) {
|
|
||||||
this.#applicationId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get applicationId(): Snowflake {
|
|
||||||
return this.#applicationId!;
|
|
||||||
}
|
|
||||||
|
|
||||||
set botId(id: Snowflake) {
|
|
||||||
this.#botId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get botId(): Snowflake {
|
|
||||||
return this.#botId;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(options: SessionOptions) {
|
|
||||||
super();
|
|
||||||
this.options = options;
|
|
||||||
|
|
||||||
const defHandler: DiscordRawEventHandler = (shard, data) => {
|
|
||||||
Actions.raw(this, shard.id, data);
|
|
||||||
|
|
||||||
if (!data.t || !data.d) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
Actions[data.t as keyof typeof Actions]?.(this, shard.id, data.d as any);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.rest = createRestManager({
|
|
||||||
token: this.options.token,
|
|
||||||
debug: (text) => {
|
|
||||||
// TODO: set this using the event emitter
|
|
||||||
super.rawListeners('debug')?.forEach((fn) => fn(text));
|
|
||||||
},
|
|
||||||
secretKey: this.options.rest?.secretKey ?? undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.gateway = createGatewayManager({
|
|
||||||
gatewayBot: this.options.gateway?.data ?? {} as GatewayBot, // TODO
|
|
||||||
gatewayConfig: {
|
|
||||||
token: this.options.token,
|
|
||||||
intents: this.options.intents,
|
|
||||||
},
|
|
||||||
handleDiscordPayload: this.options.rawHandler ?? defHandler,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.#botId = getBotIdFromToken(options.token).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
override on<K extends keyof Events>(event: K, func: Events[K]): this;
|
|
||||||
override on<K extends string>(event: K, func: (...args: unknown[]) => unknown): this {
|
|
||||||
return super.on(event, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
override off<K extends keyof Events>(event: K, func: Events[K]): this;
|
|
||||||
override off<K extends string>(event: K, func: (...args: unknown[]) => unknown): this {
|
|
||||||
return super.off(event, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
override once<K extends keyof Events>(event: K, func: Events[K]): this;
|
|
||||||
override once<K extends string>(event: K, func: (...args: unknown[]) => unknown): this {
|
|
||||||
return super.once(event, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
override emit<K extends keyof Events>(event: K, ...params: Parameters<Events[K]>): boolean;
|
|
||||||
override emit<K extends string>(event: K, ...params: unknown[]): boolean {
|
|
||||||
return super.emit(event, ...params);
|
|
||||||
}
|
|
||||||
|
|
||||||
async editProfile(nick?: string, avatar?: string): Promise<User> {
|
|
||||||
const user = await this.rest.runMethod<DiscordUser>(this.rest, 'PATCH', Routes.USER(), {
|
|
||||||
username: nick ?? null,
|
|
||||||
avatar: avatar ?? null,
|
|
||||||
});
|
|
||||||
return new User(this, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit bot's status
|
|
||||||
* tip: execute this on the ready event if possible
|
|
||||||
* @example
|
|
||||||
* for (const { id } of session.gateway.manager.shards) {
|
|
||||||
* session.editStatus(id, data);
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
editStatus(shardId: number, status: StatusUpdate): void {
|
|
||||||
const shard = this.gateway.manager.shards.get(shardId);
|
|
||||||
|
|
||||||
if (!shard) {
|
|
||||||
throw new Error(`Unknown shard ${shardId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
shard.send({
|
|
||||||
op: GatewayOpcodes.PresenceUpdate,
|
|
||||||
d: {
|
|
||||||
status: status.status,
|
|
||||||
since: null,
|
|
||||||
afk: false,
|
|
||||||
activities: status.activities.map((activity) => {
|
|
||||||
return {
|
|
||||||
name: activity.name,
|
|
||||||
type: activity.type,
|
|
||||||
url: activity.url,
|
|
||||||
created_at: activity.createdAt,
|
|
||||||
timestamps: activity.timestamps,
|
|
||||||
application_id: this.applicationId,
|
|
||||||
details: activity.details,
|
|
||||||
state: activity.state,
|
|
||||||
emoji: activity.emoji && {
|
|
||||||
name: activity.emoji.name,
|
|
||||||
id: activity.emoji.id,
|
|
||||||
animated: activity.emoji.animated,
|
|
||||||
},
|
|
||||||
party: activity.party,
|
|
||||||
assets: activity.assets &&
|
|
||||||
{
|
|
||||||
large_image: activity.assets.largeImage,
|
|
||||||
large_text: activity.assets.largeText,
|
|
||||||
small_image: activity.assets.smallImage,
|
|
||||||
small_text: activity.assets.smallText,
|
|
||||||
},
|
|
||||||
secrets: activity.secrets,
|
|
||||||
instance: activity.instance,
|
|
||||||
flags: activity.flags,
|
|
||||||
buttons: activity.buttons,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchUser(id: Snowflake): Promise<User | undefined> {
|
|
||||||
const user: DiscordUser = await this.rest.runMethod<DiscordUser>(this.rest, 'GET', Routes.USER(id));
|
|
||||||
|
|
||||||
if (!user.id) return;
|
|
||||||
|
|
||||||
return new User(this, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
createApplicationCommand(
|
|
||||||
options: CreateApplicationCommands,
|
|
||||||
guildId?: Snowflake,
|
|
||||||
): Promise<DiscordApplicationCommand> {
|
|
||||||
return this.rest.runMethod<DiscordApplicationCommand>(
|
|
||||||
this.rest,
|
|
||||||
'POST',
|
|
||||||
guildId
|
|
||||||
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
|
|
||||||
: Routes.APPLICATION_COMMANDS(this.applicationId),
|
|
||||||
this.isContextApplicationCommand(options)
|
|
||||||
? {
|
|
||||||
name: options.name,
|
|
||||||
name_localizations: options.nameLocalizations,
|
|
||||||
type: options.type,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: options.name,
|
|
||||||
name_localizations: options.nameLocalizations,
|
|
||||||
description: options.description,
|
|
||||||
description_localizations: options.descriptionLocalizations,
|
|
||||||
type: options.type,
|
|
||||||
options: options.options,
|
|
||||||
default_member_permissions: options.defaultMemberPermissions
|
|
||||||
? new Permissions(options.defaultMemberPermissions).bitfield.toString()
|
|
||||||
: undefined,
|
|
||||||
dm_permission: options.dmPermission,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteApplicationCommand(id: Snowflake, guildId?: Snowflake): Promise<undefined> {
|
|
||||||
return this.rest.runMethod<undefined>(
|
|
||||||
this.rest,
|
|
||||||
'DELETE',
|
|
||||||
guildId
|
|
||||||
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId, id)
|
|
||||||
: Routes.APPLICATION_COMMANDS(this.applicationId, id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateApplicationCommandPermissions(
|
|
||||||
guildId: Snowflake,
|
|
||||||
id: Snowflake,
|
|
||||||
bearerToken: string,
|
|
||||||
options: ApplicationCommandPermissions[],
|
|
||||||
): Promise<DiscordGuildApplicationCommandPermissions> {
|
|
||||||
return this.rest.runMethod<DiscordGuildApplicationCommandPermissions>(
|
|
||||||
this.rest,
|
|
||||||
'PUT',
|
|
||||||
Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id),
|
|
||||||
{
|
|
||||||
permissions: options,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: { authorization: `Bearer ${bearerToken}` },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchApplicationCommand(id: Snowflake, options?: GetApplicationCommand): Promise<DiscordApplicationCommand> {
|
|
||||||
return this.rest.runMethod<DiscordApplicationCommand>(
|
|
||||||
this.rest,
|
|
||||||
'GET',
|
|
||||||
options?.guildId
|
|
||||||
? Routes.GUILD_APPLICATION_COMMANDS_LOCALIZATIONS(
|
|
||||||
this.applicationId,
|
|
||||||
options.guildId,
|
|
||||||
id,
|
|
||||||
options?.withLocalizations,
|
|
||||||
)
|
|
||||||
: Routes.APPLICATION_COMMANDS(this.applicationId, id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchApplicationCommandPermissions(guildId: Snowflake): Promise<DiscordGuildApplicationCommandPermissions[]> {
|
|
||||||
return this.rest.runMethod<DiscordGuildApplicationCommandPermissions[]>(
|
|
||||||
this.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchApplicationCommandPermission(
|
|
||||||
guildId: Snowflake,
|
|
||||||
id: Snowflake,
|
|
||||||
): Promise<DiscordGuildApplicationCommandPermissions> {
|
|
||||||
return this.rest.runMethod<DiscordGuildApplicationCommandPermissions>(
|
|
||||||
this.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
upsertApplicationCommand(
|
|
||||||
id: Snowflake,
|
|
||||||
options: UpsertDataApplicationCommands,
|
|
||||||
guildId?: Snowflake,
|
|
||||||
): Promise<DiscordApplicationCommand> {
|
|
||||||
return this.rest.runMethod<DiscordApplicationCommand>(
|
|
||||||
this.rest,
|
|
||||||
'PATCH',
|
|
||||||
guildId
|
|
||||||
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
|
|
||||||
: Routes.APPLICATION_COMMANDS(this.applicationId, id),
|
|
||||||
this.isContextApplicationCommand(options)
|
|
||||||
? {
|
|
||||||
name: options.name,
|
|
||||||
type: options.type,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: options.name,
|
|
||||||
description: options.description,
|
|
||||||
type: options.type,
|
|
||||||
options: options.options,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
upsertApplicationCommands(
|
|
||||||
options: UpsertDataApplicationCommands[],
|
|
||||||
guildId?: Snowflake,
|
|
||||||
): Promise<DiscordApplicationCommand[]> {
|
|
||||||
return this.rest.runMethod<DiscordApplicationCommand[]>(
|
|
||||||
this.rest,
|
|
||||||
'PUT',
|
|
||||||
guildId
|
|
||||||
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
|
|
||||||
: Routes.APPLICATION_COMMANDS(this.applicationId),
|
|
||||||
options.map((o) =>
|
|
||||||
this.isContextApplicationCommand(o)
|
|
||||||
? {
|
|
||||||
name: o.name,
|
|
||||||
type: o.type,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: o.name,
|
|
||||||
description: o.description,
|
|
||||||
type: o.type,
|
|
||||||
options: o.options,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchCommands(guildId?: Snowflake): Promise<DiscordApplicationCommand[]> {
|
|
||||||
return this.rest.runMethod<DiscordApplicationCommand[]>(
|
|
||||||
this.rest,
|
|
||||||
'GET',
|
|
||||||
guildId
|
|
||||||
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
|
|
||||||
: Routes.APPLICATION_COMMANDS(this.applicationId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-fmt-ignore
|
|
||||||
isContextApplicationCommand(cmd: LastCreateApplicationCommands): cmd is AtLeastOne<CreateContextApplicationCommand> {
|
|
||||||
return cmd.type === ApplicationCommandTypes.Message || cmd.type === ApplicationCommandTypes.User;
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(): Promise<void> {
|
|
||||||
const getGatewayBot = () => this.rest.runMethod<DiscordGetGatewayBot>(this.rest, 'GET', Routes.GATEWAY_BOT());
|
|
||||||
|
|
||||||
// check if is empty
|
|
||||||
if (!Object.keys(this.options.gateway?.data ?? {}).length) {
|
|
||||||
const nonParsed = await getGatewayBot();
|
|
||||||
|
|
||||||
this.gateway.gatewayBot = {
|
|
||||||
url: nonParsed.url,
|
|
||||||
shards: nonParsed.shards,
|
|
||||||
sessionStartLimit: {
|
|
||||||
total: nonParsed.session_start_limit.total,
|
|
||||||
remaining: nonParsed.session_start_limit.remaining,
|
|
||||||
resetAfter: nonParsed.session_start_limit.reset_after,
|
|
||||||
maxConcurrency: nonParsed.session_start_limit.max_concurrency,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this.gateway.lastShardId = this.gateway.gatewayBot.shards - 1;
|
|
||||||
this.gateway.manager.totalShards = this.gateway.gatewayBot.shards;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.gateway.spawnShards();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
/** snowflake type */
|
|
||||||
export type Snowflake = string;
|
|
||||||
|
|
||||||
/** Discord epoch */
|
|
||||||
export const DiscordEpoch = 14200704e5;
|
|
||||||
|
|
||||||
/** utilities for Snowflakes */
|
|
||||||
export const Snowflake = {
|
|
||||||
snowflakeToTimestamp(id: Snowflake): number {
|
|
||||||
return (Number(id) >> 22) + DiscordEpoch;
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,89 +0,0 @@
|
|||||||
import type { ButtonBuilder, InputTextBuilder, SelectMenuBuilder } from './mod.ts';
|
|
||||||
import type { Permissions } from './structures/Permissions.ts';
|
|
||||||
import type { Snowflake } from './Snowflake.ts';
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Represents a session's cache
|
|
||||||
* */
|
|
||||||
export interface SymCache {
|
|
||||||
readonly cache: symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#message-object-message-flags
|
|
||||||
*/
|
|
||||||
export enum MessageFlags {
|
|
||||||
/** this message has been published to subscribed channels (via Channel Following) */
|
|
||||||
CrossPosted = 1 << 0,
|
|
||||||
/** this message originated from a message in another channel (via Channel Following) */
|
|
||||||
IsCrosspost = 1 << 1,
|
|
||||||
/** do not include any embeds when serializing this message */
|
|
||||||
SupressEmbeds = 1 << 2,
|
|
||||||
/** the source message for this crosspost has been deleted (via Channel Following) */
|
|
||||||
SourceMessageDeleted = 1 << 3,
|
|
||||||
/** this message came from the urgent message system */
|
|
||||||
Urgent = 1 << 4,
|
|
||||||
/** this message has an associated thread, with the same id as the message */
|
|
||||||
HasThread = 1 << 5,
|
|
||||||
/** this message is only visible to the user who invoked the Interaction */
|
|
||||||
Ephemeral = 1 << 6,
|
|
||||||
/** this message is an Interaction Response and the bot is "thinking" */
|
|
||||||
Loading = 1 << 7,
|
|
||||||
/** this message failed to mention some roles and add their members to the thread */
|
|
||||||
FailedToMentionSomeRolesInThread = 1 << 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ComponentBuilder =
|
|
||||||
| InputTextBuilder
|
|
||||||
| SelectMenuBuilder
|
|
||||||
| ButtonBuilder;
|
|
||||||
|
|
||||||
/** *
|
|
||||||
* Utility type
|
|
||||||
*/
|
|
||||||
export type ComponentEmoji = {
|
|
||||||
id: Snowflake;
|
|
||||||
name: string;
|
|
||||||
animated?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility type
|
|
||||||
*/
|
|
||||||
export interface PermissionsOverwrites {
|
|
||||||
id: Snowflake;
|
|
||||||
type: 0 | 1;
|
|
||||||
allow: Permissions;
|
|
||||||
deny: Permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/reference#image-formatting
|
|
||||||
*/
|
|
||||||
export type ImageFormat = 'jpg' | 'jpeg' | 'png' | 'webp' | 'gif' | 'json';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/reference#image-formatting
|
|
||||||
*/
|
|
||||||
export type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility functions
|
|
||||||
*/
|
|
||||||
export class Util {
|
|
||||||
static formatImageURL(url: string, size: ImageSize = 128, format?: ImageFormat): string {
|
|
||||||
return `${url}.${format || (url.includes('/a_') ? 'gif' : 'jpg')}?size=${size}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static iconHashToBigInt(hash: string): bigint {
|
|
||||||
return BigInt('0x' + (hash.startsWith('a_') ? `a${hash.substring(2)}` : `b${hash}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
static iconBigintToHash(icon: bigint): string {
|
|
||||||
const hash: string = icon.toString(16);
|
|
||||||
|
|
||||||
return hash.startsWith('a') ? `a_${hash.substring(1)}` : hash.substring(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Util;
|
|
@ -1,77 +0,0 @@
|
|||||||
// structures
|
|
||||||
import { Session } from './Session.ts';
|
|
||||||
export default Session;
|
|
||||||
|
|
||||||
export * from './structures/Application.ts';
|
|
||||||
export * from './structures/Attachment.ts';
|
|
||||||
export * from './structures/AutoModerationExecution.ts';
|
|
||||||
export * from './structures/AutoModerationRule.ts';
|
|
||||||
export * from './structures/Base.ts';
|
|
||||||
export * from './structures/Embed.ts';
|
|
||||||
export * from './structures/Emoji.ts';
|
|
||||||
export * from './structures/GuildEmoji.ts';
|
|
||||||
export * from './structures/GuildScheduledEvent.ts';
|
|
||||||
export * from './structures/Integration.ts';
|
|
||||||
export * from './structures/Invite.ts';
|
|
||||||
export * from './structures/Member.ts';
|
|
||||||
export * from './structures/Message.ts';
|
|
||||||
export * from './structures/MessageReaction.ts';
|
|
||||||
export * from './structures/Permissions.ts';
|
|
||||||
export * from './structures/Presence.ts';
|
|
||||||
export * from './structures/Role.ts';
|
|
||||||
export * from './structures/StageInstance.ts';
|
|
||||||
export * from './structures/Sticker.ts';
|
|
||||||
export * from './structures/ThreadMember.ts';
|
|
||||||
export * from './structures/User.ts';
|
|
||||||
export * from './structures/Webhook.ts';
|
|
||||||
export * from './structures/WelcomeChannel.ts';
|
|
||||||
export * from './structures/WelcomeScreen.ts';
|
|
||||||
|
|
||||||
// channels
|
|
||||||
export * from './structures/channels.ts';
|
|
||||||
|
|
||||||
// components
|
|
||||||
export * from './structures/components/ActionRowComponent.ts';
|
|
||||||
export * from './structures/components/ButtonComponent.ts';
|
|
||||||
export * from './structures/components/Component.ts';
|
|
||||||
export * from './structures/components/LinkButtonComponent.ts';
|
|
||||||
export * from './structures/components/SelectMenuComponent.ts';
|
|
||||||
export * from './structures/components/TextInputComponent.ts';
|
|
||||||
|
|
||||||
// guilds
|
|
||||||
export * from './structures/guilds.ts';
|
|
||||||
|
|
||||||
// builders
|
|
||||||
export * from './structures/builders/EmbedBuilder.ts';
|
|
||||||
export * from './structures/builders/components/InputTextComponentBuilder.ts';
|
|
||||||
export * from './structures/builders/components/MessageActionRow.ts';
|
|
||||||
export * from './structures/builders/components/MessageButton.ts';
|
|
||||||
export * from './structures/builders/components/MessageSelectMenu.ts';
|
|
||||||
export * from './structures/builders/components/SelectMenuOptionBuilder.ts';
|
|
||||||
export * from './structures/builders/slash/ApplicationCommand.ts';
|
|
||||||
export * from './structures/builders/slash/ApplicationCommandOption.ts';
|
|
||||||
|
|
||||||
// interactions
|
|
||||||
export * from './structures/interactions/AutoCompleteInteraction.ts';
|
|
||||||
export * from './structures/interactions/BaseInteraction.ts';
|
|
||||||
export * from './structures/interactions/CommandInteraction.ts';
|
|
||||||
export * from './structures/interactions/CommandInteractionOptionResolver.ts';
|
|
||||||
export * from './structures/interactions/ComponentInteraction.ts';
|
|
||||||
export * from './structures/interactions/InteractionFactory.ts';
|
|
||||||
export * from './structures/interactions/ModalSubmitInteraction.ts';
|
|
||||||
export * from './structures/interactions/PingInteraction.ts';
|
|
||||||
|
|
||||||
// etc
|
|
||||||
export * from './Snowflake.ts';
|
|
||||||
|
|
||||||
// session
|
|
||||||
export * from './Session.ts';
|
|
||||||
|
|
||||||
// util
|
|
||||||
export * from './Util.ts';
|
|
||||||
export * from './util/urlToBase64.ts';
|
|
||||||
export * from './util/EventEmmiter.ts';
|
|
||||||
|
|
||||||
// routes
|
|
||||||
export * as Routes from './Routes.ts';
|
|
||||||
export * as Cdn from './Cdn.ts';
|
|
@ -1,106 +0,0 @@
|
|||||||
import { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import {
|
|
||||||
DiscordApplication,
|
|
||||||
DiscordInstallParams,
|
|
||||||
DiscordTeam,
|
|
||||||
DiscordUser,
|
|
||||||
TeamMembershipStates,
|
|
||||||
} from '../../discordeno/mod.ts';
|
|
||||||
import User from './User.ts';
|
|
||||||
|
|
||||||
type SummaryDeprecated = '';
|
|
||||||
|
|
||||||
export interface Team {
|
|
||||||
/** a hash of the image of the team's icon */
|
|
||||||
icon?: string;
|
|
||||||
/** the unique id of the team */
|
|
||||||
id: string;
|
|
||||||
/** the members of the team */
|
|
||||||
members: TeamMember[];
|
|
||||||
/** user id of the current team owner */
|
|
||||||
ownerUserId: string;
|
|
||||||
/** team name */
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TeamMember {
|
|
||||||
/** the user's membership state on the team */
|
|
||||||
membershipState: TeamMembershipStates;
|
|
||||||
permissions: '*'[];
|
|
||||||
|
|
||||||
teamId: string;
|
|
||||||
|
|
||||||
user: Partial<User> & Pick<User, 'avatarHash' | 'discriminator' | 'id' | 'username'>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTeam create a new Team object for discord applications
|
|
||||||
export function NewTeam(session: Session, data: DiscordTeam): Team {
|
|
||||||
return {
|
|
||||||
icon: data.icon ? data.icon : undefined,
|
|
||||||
id: data.id,
|
|
||||||
members: data.members.map((member) => {
|
|
||||||
return {
|
|
||||||
membershipState: member.membership_state,
|
|
||||||
permissions: member.permissions,
|
|
||||||
teamId: member.team_id,
|
|
||||||
user: new User(session, member.user),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
ownerUserId: data.owner_user_id,
|
|
||||||
name: data.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/application#application-object
|
|
||||||
*/
|
|
||||||
export class Application implements Model {
|
|
||||||
constructor(session: Session, data: DiscordApplication) {
|
|
||||||
this.id = data.id;
|
|
||||||
this.session = session;
|
|
||||||
|
|
||||||
this.name = data.name;
|
|
||||||
this.icon = data.icon || undefined;
|
|
||||||
this.description = data.description;
|
|
||||||
this.rpcOrigins = data.rpc_origins;
|
|
||||||
this.botPublic = data.bot_public;
|
|
||||||
this.botRequireCodeGrant = data.bot_require_code_grant;
|
|
||||||
this.termsOfServiceURL = data.terms_of_service_url;
|
|
||||||
this.privacyPolicyURL = data.privacy_policy_url;
|
|
||||||
this.owner = data.owner ? new User(session, data.owner as DiscordUser) : undefined;
|
|
||||||
this.summary = '';
|
|
||||||
this.verifyKey = data.verify_key;
|
|
||||||
this.team = data.team ? NewTeam(session, data.team) : undefined;
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.coverImage = data.cover_image;
|
|
||||||
this.tags = data.tags;
|
|
||||||
this.installParams = data.install_params;
|
|
||||||
this.customInstallURL = data.custom_install_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
id: Snowflake;
|
|
||||||
name: string;
|
|
||||||
icon?: string;
|
|
||||||
description: string;
|
|
||||||
rpcOrigins?: string[];
|
|
||||||
botPublic: boolean;
|
|
||||||
botRequireCodeGrant: boolean;
|
|
||||||
termsOfServiceURL?: string;
|
|
||||||
privacyPolicyURL?: string;
|
|
||||||
owner?: Partial<User>;
|
|
||||||
summary: SummaryDeprecated;
|
|
||||||
verifyKey: string;
|
|
||||||
team?: Team;
|
|
||||||
guildId?: Snowflake;
|
|
||||||
primarySkuId?: Snowflake;
|
|
||||||
slug?: string;
|
|
||||||
coverImage?: string;
|
|
||||||
flags?: number;
|
|
||||||
tags?: string[];
|
|
||||||
installParams?: DiscordInstallParams;
|
|
||||||
customInstallURL?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Application;
|
|
@ -1,38 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { DiscordAttachment } from '../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an attachment
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#attachment-object
|
|
||||||
*/
|
|
||||||
export class Attachment implements Model {
|
|
||||||
constructor(session: Session, data: DiscordAttachment) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
|
|
||||||
this.contentType = data.content_type ? data.content_type : undefined;
|
|
||||||
this.attachment = data.url;
|
|
||||||
this.proxyUrl = data.proxy_url;
|
|
||||||
this.name = data.filename;
|
|
||||||
this.size = data.size;
|
|
||||||
this.height = data.height ? data.height : undefined;
|
|
||||||
this.width = data.width ? data.width : undefined;
|
|
||||||
this.ephemeral = !!data.ephemeral;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
readonly id: Snowflake;
|
|
||||||
|
|
||||||
contentType?: string;
|
|
||||||
attachment: string;
|
|
||||||
proxyUrl: string;
|
|
||||||
name: string;
|
|
||||||
size: number;
|
|
||||||
height?: number;
|
|
||||||
width?: number;
|
|
||||||
ephemeral: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Attachment;
|
|
@ -1,51 +0,0 @@
|
|||||||
import { AutoModerationTriggerTypes, DiscordAutoModerationActionExecution } from '../../discordeno/mod.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import { AutoModerationAction } from './AutoModerationRule.ts';
|
|
||||||
|
|
||||||
export class AutoModerationExecution {
|
|
||||||
constructor(session: Session, data: DiscordAutoModerationActionExecution) {
|
|
||||||
this.session = session;
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.action = Object.create({
|
|
||||||
type: data.action.type,
|
|
||||||
metadata: {
|
|
||||||
channelId: data.action.metadata.channel_id,
|
|
||||||
durationSeconds: data.action.metadata.duration_seconds,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.ruleId = data.rule_id;
|
|
||||||
this.ruleTriggerType = data.rule_trigger_type;
|
|
||||||
this.userId = data.user_id;
|
|
||||||
this.content = data.content;
|
|
||||||
if (data.channel_id) {
|
|
||||||
this.channelId = data.channel_id;
|
|
||||||
}
|
|
||||||
if (data.message_id) {
|
|
||||||
this.messageId = data.message_id;
|
|
||||||
}
|
|
||||||
if (data.alert_system_message_id) {
|
|
||||||
this.alertSystemMessageId = data.alert_system_message_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.matched_keyword) {
|
|
||||||
this.matchedKeyword = data.matched_keyword;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.matched_content) {
|
|
||||||
this.matched_content = data.matched_content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
session: Session;
|
|
||||||
guildId: Snowflake;
|
|
||||||
action: AutoModerationAction;
|
|
||||||
ruleId: Snowflake;
|
|
||||||
ruleTriggerType: AutoModerationTriggerTypes;
|
|
||||||
userId: Snowflake;
|
|
||||||
channelId?: Snowflake;
|
|
||||||
messageId?: Snowflake;
|
|
||||||
alertSystemMessageId?: Snowflake;
|
|
||||||
content?: string;
|
|
||||||
matchedKeyword?: string;
|
|
||||||
matched_content?: string;
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
import {
|
|
||||||
AutoModerationActionType,
|
|
||||||
AutoModerationEventTypes,
|
|
||||||
AutoModerationTriggerTypes,
|
|
||||||
DiscordAutoModerationRule,
|
|
||||||
DiscordAutoModerationRuleTriggerMetadataPresets,
|
|
||||||
} from '../../discordeno/mod.ts';
|
|
||||||
import { Model } from './Base.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
|
|
||||||
export interface AutoModerationRuleTriggerMetadata {
|
|
||||||
keywordFilter?: string[];
|
|
||||||
presets?: DiscordAutoModerationRuleTriggerMetadataPresets[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ActionMetadata {
|
|
||||||
channelId: Snowflake;
|
|
||||||
durationSeconds: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AutoModerationAction {
|
|
||||||
type: AutoModerationActionType;
|
|
||||||
metadata: ActionMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AutoModerationRule implements Model {
|
|
||||||
constructor(session: Session, data: DiscordAutoModerationRule) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.name = data.name;
|
|
||||||
this.creatorId = data.creator_id;
|
|
||||||
this.eventType = data.event_type;
|
|
||||||
this.triggerType = data.trigger_type;
|
|
||||||
this.triggerMetadata = {
|
|
||||||
keywordFilter: data.trigger_metadata.keyword_filter,
|
|
||||||
presets: data.trigger_metadata.presets,
|
|
||||||
};
|
|
||||||
this.actions = data.actions.map((action) =>
|
|
||||||
Object.create({
|
|
||||||
type: action.type,
|
|
||||||
metadata: {
|
|
||||||
channelId: action.metadata.channel_id,
|
|
||||||
durationSeconds: action.metadata.duration_seconds,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.enabled = !!data.enabled;
|
|
||||||
this.exemptRoles = data.exempt_roles;
|
|
||||||
this.exemptChannels = data.exempt_channels;
|
|
||||||
}
|
|
||||||
session: Session;
|
|
||||||
id: Snowflake;
|
|
||||||
guildId: Snowflake;
|
|
||||||
name: string;
|
|
||||||
creatorId: Snowflake;
|
|
||||||
eventType: AutoModerationEventTypes;
|
|
||||||
triggerType: AutoModerationTriggerTypes;
|
|
||||||
triggerMetadata: AutoModerationRuleTriggerMetadata;
|
|
||||||
actions: AutoModerationAction[];
|
|
||||||
enabled: boolean;
|
|
||||||
exemptRoles: Snowflake[];
|
|
||||||
exemptChannels: Snowflake[];
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a Discord data model
|
|
||||||
*/
|
|
||||||
export interface Model {
|
|
||||||
/** id of the model */
|
|
||||||
id: Snowflake;
|
|
||||||
/** reference to the client that instantiated the model */
|
|
||||||
session: Session;
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
import type { DiscordEmbed, EmbedTypes } from '../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
export interface Embed {
|
|
||||||
title?: string;
|
|
||||||
timestamp?: string;
|
|
||||||
type?: EmbedTypes;
|
|
||||||
url?: string;
|
|
||||||
color?: number;
|
|
||||||
description?: string;
|
|
||||||
author?: {
|
|
||||||
name: string;
|
|
||||||
iconURL?: string;
|
|
||||||
proxyIconURL?: string;
|
|
||||||
url?: string;
|
|
||||||
};
|
|
||||||
footer?: {
|
|
||||||
text: string;
|
|
||||||
iconURL?: string;
|
|
||||||
proxyIconURL?: string;
|
|
||||||
};
|
|
||||||
fields?: Array<{
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
inline?: boolean;
|
|
||||||
}>;
|
|
||||||
thumbnail?: {
|
|
||||||
url: string;
|
|
||||||
proxyURL?: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
};
|
|
||||||
video?: {
|
|
||||||
url?: string;
|
|
||||||
proxyURL?: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
};
|
|
||||||
image?: {
|
|
||||||
url: string;
|
|
||||||
proxyURL?: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
};
|
|
||||||
provider?: {
|
|
||||||
url?: string;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function embed(data: Embed): DiscordEmbed {
|
|
||||||
return {
|
|
||||||
title: data.title,
|
|
||||||
timestamp: data.timestamp,
|
|
||||||
type: data.type,
|
|
||||||
url: data.url,
|
|
||||||
color: data.color,
|
|
||||||
description: data.description,
|
|
||||||
author: data.author && {
|
|
||||||
name: data.author.name,
|
|
||||||
url: data.author.url,
|
|
||||||
icon_url: data.author.iconURL,
|
|
||||||
proxy_icon_url: data.author.proxyIconURL,
|
|
||||||
},
|
|
||||||
footer: data.footer && {
|
|
||||||
text: data.footer.text,
|
|
||||||
icon_url: data.footer.iconURL,
|
|
||||||
proxy_icon_url: data.footer.proxyIconURL,
|
|
||||||
},
|
|
||||||
fields: data.fields?.map((f) => {
|
|
||||||
return {
|
|
||||||
name: f.name,
|
|
||||||
value: f.value,
|
|
||||||
inline: f.inline,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
thumbnail: data.thumbnail && {
|
|
||||||
url: data.thumbnail.url,
|
|
||||||
proxy_url: data.thumbnail.proxyURL,
|
|
||||||
width: data.thumbnail.width,
|
|
||||||
height: data.thumbnail.height,
|
|
||||||
},
|
|
||||||
video: {
|
|
||||||
url: data.video?.url,
|
|
||||||
proxy_url: data.video?.proxyURL,
|
|
||||||
width: data.video?.width,
|
|
||||||
height: data.video?.height,
|
|
||||||
},
|
|
||||||
image: data.image && {
|
|
||||||
url: data.image.url,
|
|
||||||
proxy_url: data.image.proxyURL,
|
|
||||||
width: data.image.width,
|
|
||||||
height: data.image.height,
|
|
||||||
},
|
|
||||||
provider: {
|
|
||||||
url: data.provider?.url,
|
|
||||||
name: data.provider?.name,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Embed;
|
|
@ -1,23 +0,0 @@
|
|||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { DiscordEmoji } from '../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
export class Emoji {
|
|
||||||
constructor(session: Session, data: DiscordEmoji) {
|
|
||||||
this.id = data.id;
|
|
||||||
this.name = data.name;
|
|
||||||
this.animated = !!data.animated;
|
|
||||||
this.available = !!data.available;
|
|
||||||
this.requireColons = !!data.require_colons;
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
readonly id?: Snowflake;
|
|
||||||
readonly session: Session;
|
|
||||||
|
|
||||||
name?: string;
|
|
||||||
animated: boolean;
|
|
||||||
available: boolean;
|
|
||||||
requireColons: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Emoji;
|
|
@ -1,49 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { DiscordEmoji } from '../../discordeno/mod.ts';
|
|
||||||
import type { ModifyGuildEmoji } from './guilds.ts';
|
|
||||||
import Guild from './guilds.ts';
|
|
||||||
import Emoji from './Emoji.ts';
|
|
||||||
import User from './User.ts';
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
|
|
||||||
export class GuildEmoji extends Emoji implements Model {
|
|
||||||
constructor(session: Session, data: DiscordEmoji, guildId: Snowflake) {
|
|
||||||
super(session, data);
|
|
||||||
this.guildId = guildId;
|
|
||||||
this.roles = data.roles;
|
|
||||||
this.user = data.user ? new User(this.session, data.user) : undefined;
|
|
||||||
this.managed = !!data.managed;
|
|
||||||
this.id = super.id!;
|
|
||||||
}
|
|
||||||
guildId: Snowflake;
|
|
||||||
roles?: Snowflake[];
|
|
||||||
user?: User;
|
|
||||||
managed?: boolean;
|
|
||||||
|
|
||||||
// id cannot be null in a GuildEmoji
|
|
||||||
override id: Snowflake;
|
|
||||||
|
|
||||||
async edit(options: ModifyGuildEmoji): Promise<GuildEmoji> {
|
|
||||||
const emoji = await Guild.prototype.editEmoji.call(
|
|
||||||
{ id: this.guildId, session: this.session },
|
|
||||||
this.id,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
return emoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(reason?: string): Promise<GuildEmoji> {
|
|
||||||
await Guild.prototype.deleteEmoji.call({ id: this.guildId, session: this.session }, this.id, reason);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
get url(): string {
|
|
||||||
return Routes.EMOJI_URL(this.id, this.animated);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GuildEmoji;
|
|
@ -1,48 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import { PrivacyLevels } from './StageInstance.ts';
|
|
||||||
import type {
|
|
||||||
DiscordScheduledEvent,
|
|
||||||
DiscordScheduledEventEntityMetadata,
|
|
||||||
ScheduledEventEntityType,
|
|
||||||
ScheduledEventStatus,
|
|
||||||
} from '../../discordeno/mod.ts';
|
|
||||||
import User from './User.ts';
|
|
||||||
|
|
||||||
export class ScheduledEvent implements Model {
|
|
||||||
constructor(session: Session, data: DiscordScheduledEvent) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.channelId = data.channel_id;
|
|
||||||
this.creatorId = data.creator_id ? data.creator_id : undefined;
|
|
||||||
this.name = data.name;
|
|
||||||
this.description = data.description;
|
|
||||||
this.scheduledStartTime = data.scheduled_start_time;
|
|
||||||
this.scheduledEndTime = data.scheduled_end_time;
|
|
||||||
this.privacyLevel = PrivacyLevels.GuildOnly;
|
|
||||||
this.status = data.status;
|
|
||||||
this.entityType = data.entity_type;
|
|
||||||
this.entityMetadata = data.entity_metadata ? data.entity_metadata : undefined;
|
|
||||||
this.creator = data.creator ? new User(session, data.creator) : undefined;
|
|
||||||
this.userCount = data.user_count;
|
|
||||||
this.image = data.image ? data.image : undefined;
|
|
||||||
}
|
|
||||||
session: Session;
|
|
||||||
id: Snowflake;
|
|
||||||
guildId: Snowflake;
|
|
||||||
channelId: Snowflake | null;
|
|
||||||
creatorId?: Snowflake;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
scheduledStartTime: string;
|
|
||||||
scheduledEndTime: string | null;
|
|
||||||
privacyLevel: PrivacyLevels;
|
|
||||||
status: ScheduledEventStatus;
|
|
||||||
entityType: ScheduledEventEntityType;
|
|
||||||
entityMetadata?: DiscordScheduledEventEntityMetadata;
|
|
||||||
creator?: User;
|
|
||||||
userCount?: number;
|
|
||||||
image?: string;
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { DiscordIntegration, IntegrationExpireBehaviors } from '../../discordeno/mod.ts';
|
|
||||||
import User from './User.ts';
|
|
||||||
|
|
||||||
export type IntegrationTypes = 'twitch' | 'youtube' | 'discord';
|
|
||||||
|
|
||||||
export interface IntegrationAccount {
|
|
||||||
id: Snowflake;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IntegrationApplication {
|
|
||||||
id: Snowflake;
|
|
||||||
name: string;
|
|
||||||
icon?: string;
|
|
||||||
description: string;
|
|
||||||
bot?: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Integration implements Model {
|
|
||||||
constructor(session: Session, data: DiscordIntegration & { guild_id?: Snowflake }) {
|
|
||||||
this.id = data.id;
|
|
||||||
this.session = session;
|
|
||||||
|
|
||||||
data.guild_id ? this.guildId = data.guild_id : null;
|
|
||||||
|
|
||||||
this.name = data.name;
|
|
||||||
this.type = data.type;
|
|
||||||
this.enabled = !!data.enabled;
|
|
||||||
this.syncing = !!data.syncing;
|
|
||||||
this.roleId = data.role_id;
|
|
||||||
this.enableEmoticons = !!data.enable_emoticons;
|
|
||||||
this.expireBehavior = data.expire_behavior;
|
|
||||||
this.expireGracePeriod = data.expire_grace_period;
|
|
||||||
this.syncedAt = data.synced_at;
|
|
||||||
this.subscriberCount = data.subscriber_count;
|
|
||||||
this.revoked = !!data.revoked;
|
|
||||||
|
|
||||||
this.user = data.user ? new User(session, data.user) : undefined;
|
|
||||||
this.account = {
|
|
||||||
id: data.account.id,
|
|
||||||
name: data.account.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.application) {
|
|
||||||
this.application = {
|
|
||||||
id: data.application.id,
|
|
||||||
name: data.application.name,
|
|
||||||
icon: data.application.icon ? data.application.icon : undefined,
|
|
||||||
description: data.application.description,
|
|
||||||
bot: data.application.bot ? new User(session, data.application.bot) : undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
id: Snowflake;
|
|
||||||
guildId?: Snowflake;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
type: IntegrationTypes;
|
|
||||||
enabled?: boolean;
|
|
||||||
syncing?: boolean;
|
|
||||||
roleId?: string;
|
|
||||||
enableEmoticons?: boolean;
|
|
||||||
expireBehavior?: IntegrationExpireBehaviors;
|
|
||||||
expireGracePeriod?: number;
|
|
||||||
syncedAt?: string;
|
|
||||||
subscriberCount?: number;
|
|
||||||
revoked?: boolean;
|
|
||||||
|
|
||||||
user?: User;
|
|
||||||
account: IntegrationAccount;
|
|
||||||
application?: IntegrationApplication;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Integration;
|
|
@ -1,170 +0,0 @@
|
|||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type {
|
|
||||||
DiscordApplication,
|
|
||||||
DiscordChannel,
|
|
||||||
DiscordInvite,
|
|
||||||
DiscordInviteCreate,
|
|
||||||
DiscordMemberWithUser,
|
|
||||||
DiscordScheduledEventEntityMetadata,
|
|
||||||
ScheduledEventEntityType,
|
|
||||||
ScheduledEventPrivacyLevel,
|
|
||||||
ScheduledEventStatus,
|
|
||||||
} from '../../discordeno/mod.ts';
|
|
||||||
import { TargetTypes } from '../../discordeno/mod.ts';
|
|
||||||
import { GuildChannel } from './channels.ts';
|
|
||||||
import { Member } from './Member.ts';
|
|
||||||
import { Guild, InviteGuild } from './guilds.ts';
|
|
||||||
import User from './User.ts';
|
|
||||||
import Application from './Application.ts';
|
|
||||||
|
|
||||||
export interface InviteStageInstance {
|
|
||||||
/** The members speaking in the Stage */
|
|
||||||
members: Partial<Member>[];
|
|
||||||
/** The number of users in the Stage */
|
|
||||||
participantCount: number;
|
|
||||||
/** The number of users speaking in the Stage */
|
|
||||||
speakerCount: number;
|
|
||||||
/** The topic of the Stage instance (1-120 characters) */
|
|
||||||
topic: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InviteScheduledEvent {
|
|
||||||
id: Snowflake;
|
|
||||||
guildId: string;
|
|
||||||
channelId?: string;
|
|
||||||
creatorId?: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
scheduledStartTime: string;
|
|
||||||
scheduledEndTime?: string;
|
|
||||||
privacyLevel: ScheduledEventPrivacyLevel;
|
|
||||||
status: ScheduledEventStatus;
|
|
||||||
entityType: ScheduledEventEntityType;
|
|
||||||
entityId?: string;
|
|
||||||
entityMetadata?: DiscordScheduledEventEntityMetadata;
|
|
||||||
creator?: User;
|
|
||||||
userCount?: number;
|
|
||||||
image?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InviteCreate {
|
|
||||||
channelId: string;
|
|
||||||
code: string;
|
|
||||||
createdAt: string;
|
|
||||||
guildId?: string;
|
|
||||||
inviter?: User;
|
|
||||||
maxAge: number;
|
|
||||||
maxUses: number;
|
|
||||||
targetType: TargetTypes;
|
|
||||||
targetUser?: User;
|
|
||||||
targetApplication?: Partial<Application>;
|
|
||||||
temporary: boolean;
|
|
||||||
uses: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NewInviteCreate(session: Session, invite: DiscordInviteCreate): InviteCreate {
|
|
||||||
return {
|
|
||||||
channelId: invite.channel_id,
|
|
||||||
code: invite.code,
|
|
||||||
createdAt: invite.created_at,
|
|
||||||
guildId: invite.guild_id,
|
|
||||||
inviter: invite.inviter ? new User(session, invite.inviter) : undefined,
|
|
||||||
maxAge: invite.max_age,
|
|
||||||
maxUses: invite.max_uses,
|
|
||||||
targetType: invite.target_type,
|
|
||||||
targetUser: invite.target_user ? new User(session, invite.target_user) : undefined,
|
|
||||||
targetApplication: invite.target_application &&
|
|
||||||
new Application(session, invite.target_application as DiscordApplication),
|
|
||||||
temporary: invite.temporary,
|
|
||||||
uses: invite.uses,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/invite#invite-object
|
|
||||||
*/
|
|
||||||
export class Invite {
|
|
||||||
constructor(session: Session, data: DiscordInvite) {
|
|
||||||
this.session = session;
|
|
||||||
|
|
||||||
this.guild = data.guild ? new InviteGuild(session, data.guild) : undefined;
|
|
||||||
this.approximateMemberCount = data.approximate_member_count ? data.approximate_member_count : undefined;
|
|
||||||
this.approximatePresenceCount = data.approximate_presence_count ? data.approximate_presence_count : undefined;
|
|
||||||
this.code = data.code;
|
|
||||||
this.expiresAt = data.expires_at ? Number.parseInt(data.expires_at) : undefined;
|
|
||||||
this.inviter = data.inviter ? new User(session, data.inviter) : undefined;
|
|
||||||
this.targetUser = data.target_user ? new User(session, data.target_user) : undefined;
|
|
||||||
this.targetApplication = data.target_application
|
|
||||||
? new Application(session, data.target_application as DiscordApplication)
|
|
||||||
: undefined;
|
|
||||||
this.targetType = data.target_type;
|
|
||||||
|
|
||||||
if (data.channel) {
|
|
||||||
const guildId = (data.guild && data.guild?.id) ? data.guild.id : '';
|
|
||||||
this.channel = new GuildChannel(session, data.channel as DiscordChannel, guildId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.guild_scheduled_event) {
|
|
||||||
this.guildScheduledEvent = {
|
|
||||||
id: data.guild_scheduled_event.id,
|
|
||||||
guildId: data.guild_scheduled_event.guild_id,
|
|
||||||
channelId: data.guild_scheduled_event.channel_id ? data.guild_scheduled_event.channel_id : undefined,
|
|
||||||
creatorId: data.guild_scheduled_event.creator_id ? data.guild_scheduled_event.creator_id : undefined,
|
|
||||||
name: data.guild_scheduled_event.name,
|
|
||||||
description: data.guild_scheduled_event.description
|
|
||||||
? data.guild_scheduled_event.description
|
|
||||||
: undefined,
|
|
||||||
scheduledStartTime: data.guild_scheduled_event.scheduled_start_time,
|
|
||||||
scheduledEndTime: data.guild_scheduled_event.scheduled_end_time
|
|
||||||
? data.guild_scheduled_event.scheduled_end_time
|
|
||||||
: undefined,
|
|
||||||
privacyLevel: data.guild_scheduled_event.privacy_level,
|
|
||||||
status: data.guild_scheduled_event.status,
|
|
||||||
entityType: data.guild_scheduled_event.entity_type,
|
|
||||||
entityId: data.guild ? data.guild.id : undefined,
|
|
||||||
entityMetadata: data.guild_scheduled_event.entity_metadata
|
|
||||||
? data.guild_scheduled_event.entity_metadata
|
|
||||||
: undefined,
|
|
||||||
creator: data.guild_scheduled_event.creator
|
|
||||||
? new User(session, data.guild_scheduled_event.creator)
|
|
||||||
: undefined,
|
|
||||||
userCount: data.guild_scheduled_event.user_count ? data.guild_scheduled_event.user_count : undefined,
|
|
||||||
image: data.guild_scheduled_event.image ? data.guild_scheduled_event.image : undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.stage_instance) {
|
|
||||||
const guildId = (data.guild && data.guild?.id) ? data.guild.id : '';
|
|
||||||
this.stageInstance = {
|
|
||||||
members: data.stage_instance.members.map((m) =>
|
|
||||||
new Member(session, m as DiscordMemberWithUser, guildId)
|
|
||||||
),
|
|
||||||
participantCount: data.stage_instance.participant_count,
|
|
||||||
speakerCount: data.stage_instance.speaker_count,
|
|
||||||
topic: data.stage_instance.topic,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
guild?: InviteGuild;
|
|
||||||
approximateMemberCount?: number;
|
|
||||||
approximatePresenceCount?: number;
|
|
||||||
code: string;
|
|
||||||
expiresAt?: number;
|
|
||||||
inviter?: User;
|
|
||||||
targetUser?: User;
|
|
||||||
targetType?: TargetTypes;
|
|
||||||
channel?: Partial<GuildChannel>;
|
|
||||||
stageInstance?: InviteStageInstance;
|
|
||||||
guildScheduledEvent?: InviteScheduledEvent;
|
|
||||||
targetApplication?: Partial<Application>;
|
|
||||||
|
|
||||||
async delete(): Promise<Invite> {
|
|
||||||
await Guild.prototype.deleteInvite.call(this.guild, this.code);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Invite;
|
|
@ -1,155 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { DiscordMemberWithUser } from '../../discordeno/mod.ts';
|
|
||||||
import type { CreateGuildBan, ModifyGuildMember } from './guilds.ts';
|
|
||||||
import { Guild } from './guilds.ts';
|
|
||||||
import Util from '../Util.ts';
|
|
||||||
import { avatarOptions, User } from './User.ts';
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/guild#guild-member-object
|
|
||||||
* Represents a guild member
|
|
||||||
*/
|
|
||||||
export class Member implements Model {
|
|
||||||
constructor(session: Session, data: DiscordMemberWithUser, guildId: Snowflake) {
|
|
||||||
this.session = session;
|
|
||||||
this.user = new User(session, data.user);
|
|
||||||
this.guildId = guildId;
|
|
||||||
this.avatarHash = data.avatar ? Util.iconHashToBigInt(data.avatar) : undefined;
|
|
||||||
this.nickname = data.nick ? data.nick : undefined;
|
|
||||||
this.premiumSince = data.premium_since ? Number.parseInt(data.premium_since) : undefined;
|
|
||||||
this.joinedTimestamp = Number.parseInt(data.joined_at);
|
|
||||||
this.roles = data.roles;
|
|
||||||
this.deaf = !!data.deaf;
|
|
||||||
this.mute = !!data.mute;
|
|
||||||
this.pending = !!data.pending;
|
|
||||||
this.communicationDisabledUntilTimestamp = data.communication_disabled_until
|
|
||||||
? Number.parseInt(data.communication_disabled_until)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** the session that instantiated this member */
|
|
||||||
readonly session: Session;
|
|
||||||
|
|
||||||
/** the user this guild member represents */
|
|
||||||
user: User;
|
|
||||||
|
|
||||||
/** the choosen guild id */
|
|
||||||
guildId: Snowflake;
|
|
||||||
|
|
||||||
/** the member's guild avatar hash optimized as a bigint */
|
|
||||||
avatarHash?: bigint;
|
|
||||||
|
|
||||||
/** this user's guild nickname */
|
|
||||||
nickname?: string;
|
|
||||||
|
|
||||||
/** when the user started boosting the guild */
|
|
||||||
premiumSince?: number;
|
|
||||||
|
|
||||||
/** when the user joined the guild */
|
|
||||||
joinedTimestamp: number;
|
|
||||||
|
|
||||||
/** array of role object ids */
|
|
||||||
roles: Snowflake[];
|
|
||||||
|
|
||||||
/** whether the user is deafened in voice channels */
|
|
||||||
deaf: boolean;
|
|
||||||
|
|
||||||
/** whether the user is muted in voice channels */
|
|
||||||
mute: boolean;
|
|
||||||
|
|
||||||
/** whether the user has not yet passed the guild's Membership Screening requirements */
|
|
||||||
pending: boolean;
|
|
||||||
|
|
||||||
/** when the user's timeout will expire and the user will be able to communicate in the guild again, null or a time in the past if the user is not timed out */
|
|
||||||
communicationDisabledUntilTimestamp?: number;
|
|
||||||
|
|
||||||
/** shorthand to User.id */
|
|
||||||
get id(): Snowflake {
|
|
||||||
return this.user.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** gets the nickname or the username */
|
|
||||||
get nicknameOrUsername(): string {
|
|
||||||
return this.nickname ?? this.user.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** gets the joinedAt timestamp as a Date */
|
|
||||||
get joinedAt(): Date {
|
|
||||||
return new Date(this.joinedTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** bans a member from this guild and delete previous messages sent by the member */
|
|
||||||
async ban(options: CreateGuildBan): Promise<Member> {
|
|
||||||
await Guild.prototype.banMember.call({ id: this.guildId, session: this.session }, this.user.id, options);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** kicks a member from this guild */
|
|
||||||
async kick(options: { reason?: string }): Promise<Member> {
|
|
||||||
await Guild.prototype.kickMember.call(
|
|
||||||
{ id: this.guildId, session: this.session },
|
|
||||||
this.user.id,
|
|
||||||
options.reason,
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** unbans a member from this guild */
|
|
||||||
async unban(): Promise<void> {
|
|
||||||
await Guild.prototype.unbanMember.call({ id: this.guildId, session: this.session }, this.user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** edits member's nickname, roles, etc */
|
|
||||||
async edit(options: ModifyGuildMember): Promise<Member> {
|
|
||||||
const member = await Guild.prototype.editMember.call(
|
|
||||||
{ id: this.guildId, session: this.session },
|
|
||||||
this.user.id,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
return member;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** adds a role to this member */
|
|
||||||
async addRole(roleId: Snowflake, reason?: string): Promise<void> {
|
|
||||||
await Guild.prototype.addRole.call({ id: this.guildId, session: this.session }, this.user.id, roleId, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** removes a role from this member */
|
|
||||||
async removeRole(roleId: Snowflake, options: { reason?: string } = {}): Promise<void> {
|
|
||||||
await Guild.prototype.removeRole.call(
|
|
||||||
{ id: this.guildId, session: this.session },
|
|
||||||
this.user.id,
|
|
||||||
roleId,
|
|
||||||
options.reason,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** gets the members's guild avatar, not to be confused with Member.user.avatarURL() */
|
|
||||||
avatarURL(options: avatarOptions): string {
|
|
||||||
let url: string;
|
|
||||||
|
|
||||||
if (this.user.bot) {
|
|
||||||
return this.user.avatarURL(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.avatarHash) {
|
|
||||||
url = Routes.USER_DEFAULT_AVATAR(Number(this.user.discriminator) % 5);
|
|
||||||
} else {
|
|
||||||
url = Routes.USER_AVATAR(this.user.id, Util.iconBigintToHash(this.avatarHash));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Util.formatImageURL(url, options.size ?? 128, options.format);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return `<@!${this.user.id}>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Member;
|
|
@ -1,559 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type {
|
|
||||||
AllowedMentionsTypes,
|
|
||||||
DiscordEmbed,
|
|
||||||
DiscordMessage,
|
|
||||||
DiscordMessageComponents,
|
|
||||||
DiscordUser,
|
|
||||||
FileContent,
|
|
||||||
MessageActivityTypes,
|
|
||||||
MessageTypes,
|
|
||||||
} from '../../discordeno/mod.ts';
|
|
||||||
import type { Channel } from './channels.ts';
|
|
||||||
import type { Component } from './components/Component.ts';
|
|
||||||
import type { GetReactions } from '../Routes.ts';
|
|
||||||
import type { MessageInteraction } from './interactions/InteractionFactory.ts';
|
|
||||||
import { MessageFlags } from '../Util.ts';
|
|
||||||
import { Snowflake } from '../Snowflake.ts';
|
|
||||||
import { ChannelFactory, ThreadChannel } from './channels.ts';
|
|
||||||
import Util from '../Util.ts';
|
|
||||||
import User from './User.ts';
|
|
||||||
import Member from './Member.ts';
|
|
||||||
import Attachment from './Attachment.ts';
|
|
||||||
import ComponentFactory from './components/ComponentFactory.ts';
|
|
||||||
import MessageReaction from './MessageReaction.ts';
|
|
||||||
import Application, { NewTeam } from './Application.ts';
|
|
||||||
import InteractionFactory from './interactions/InteractionFactory.ts';
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
import { StickerItem } from './Sticker.ts';
|
|
||||||
|
|
||||||
export type GuildMessage = Message & { guildId: Snowflake };
|
|
||||||
export type WebhookMessage = Message & {
|
|
||||||
author: Partial<User>;
|
|
||||||
webhook: WebhookAuthor;
|
|
||||||
member: undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface MessageActivity {
|
|
||||||
partyId?: Snowflake;
|
|
||||||
type: MessageActivityTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#allowed-mentions-object
|
|
||||||
*/
|
|
||||||
export interface AllowedMentions {
|
|
||||||
parse?: AllowedMentionsTypes[];
|
|
||||||
repliedUser?: boolean;
|
|
||||||
roles?: Snowflake[];
|
|
||||||
users?: Snowflake[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://github.com/denoland/deno_doc/blob/main/lib/types.d.ts
|
|
||||||
* channelId is optional when creating a reply, but will always be present when receiving an event/response that includes this data model.
|
|
||||||
*/
|
|
||||||
export interface CreateMessageReference {
|
|
||||||
messageId: Snowflake;
|
|
||||||
channelId?: Snowflake;
|
|
||||||
guildId?: Snowflake;
|
|
||||||
failIfNotExists?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#create-message-json-params
|
|
||||||
* Posts a message to a guild text or DM channel. Returns a message object. Fires a Message Create Gateway event.
|
|
||||||
*/
|
|
||||||
export interface CreateMessage {
|
|
||||||
embeds?: DiscordEmbed[];
|
|
||||||
content?: string;
|
|
||||||
allowedMentions?: AllowedMentions;
|
|
||||||
files?: FileContent[];
|
|
||||||
messageReference?: CreateMessageReference;
|
|
||||||
tts?: boolean;
|
|
||||||
components?: DiscordMessageComponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#edit-message-json-params
|
|
||||||
* Edit a previously sent message.
|
|
||||||
* Returns a {@link Message} object. Fires a Message Update Gateway event.
|
|
||||||
*/
|
|
||||||
export interface EditMessage extends Partial<CreateMessage> {
|
|
||||||
flags?: MessageFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a guild or unicode {@link Emoji}
|
|
||||||
*/
|
|
||||||
export type EmojiResolvable = string | {
|
|
||||||
name: string;
|
|
||||||
id: Snowflake;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A partial {@link User} to represent the author of a message sent by a webhook
|
|
||||||
*/
|
|
||||||
export interface WebhookAuthor {
|
|
||||||
id: string;
|
|
||||||
username: string;
|
|
||||||
discriminator: string;
|
|
||||||
avatar?: bigint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#message-object
|
|
||||||
* Represents a message
|
|
||||||
*/
|
|
||||||
export class Message implements Model {
|
|
||||||
constructor(session: Session, data: DiscordMessage) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
|
|
||||||
this.type = data.type;
|
|
||||||
this.channelId = data.channel_id;
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.applicationId = data.application_id;
|
|
||||||
|
|
||||||
this.mentions = {
|
|
||||||
users: data.mentions?.map((user) => new User(session, user)) ?? [],
|
|
||||||
roleIds: data.mention_roles ?? [],
|
|
||||||
channels: data.mention_channels?.map((channel) => ChannelFactory.from(session, channel)) ?? [],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!data.webhook_id) {
|
|
||||||
this.author = new User(session, data.author);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.flags = data.flags;
|
|
||||||
this.pinned = !!data.pinned;
|
|
||||||
this.tts = !!data.tts;
|
|
||||||
this.content = data.content!;
|
|
||||||
this.nonce = data.nonce;
|
|
||||||
this.mentionEveryone = data.mention_everyone;
|
|
||||||
|
|
||||||
this.timestamp = Date.parse(data.timestamp);
|
|
||||||
this.editedTimestamp = data.edited_timestamp ? Date.parse(data.edited_timestamp) : undefined;
|
|
||||||
|
|
||||||
this.reactions = data.reactions?.map((react) => new MessageReaction(session, react)) ?? [];
|
|
||||||
this.attachments = data.attachments.map((attachment) => new Attachment(session, attachment));
|
|
||||||
this.embeds = data.embeds;
|
|
||||||
|
|
||||||
if (data.interaction) {
|
|
||||||
this.interaction = InteractionFactory.fromMessage(session, data.interaction, data.guild_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.thread && data.guild_id) {
|
|
||||||
this.thread = new ThreadChannel(session, data.thread, data.guild_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// webhook handling
|
|
||||||
if (data.webhook_id && data.author.discriminator === '0000') {
|
|
||||||
this.webhook = {
|
|
||||||
id: data.webhook_id!,
|
|
||||||
username: data.author.username,
|
|
||||||
discriminator: data.author.discriminator,
|
|
||||||
avatar: data.author.avatar ? Util.iconHashToBigInt(data.author.avatar) : undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// user is always null on MessageCreate and its replaced with author
|
|
||||||
if (data.guild_id && data.member && !this.isWebhookMessage()) {
|
|
||||||
this.member = new Member(session, { ...data.member, user: data.author }, data.guild_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.components = data.components?.map((component) => ComponentFactory.from(session, component)) ?? [];
|
|
||||||
|
|
||||||
if (data.activity) {
|
|
||||||
this.activity = {
|
|
||||||
partyId: data.activity.party_id,
|
|
||||||
type: data.activity.type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.sticker_items) {
|
|
||||||
this.stickers = data.sticker_items.map((si) => {
|
|
||||||
return {
|
|
||||||
id: si.id,
|
|
||||||
name: si.name,
|
|
||||||
formatType: si.format_type,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.application) {
|
|
||||||
const application: Partial<Application> = {
|
|
||||||
id: data.application.id,
|
|
||||||
icon: data.application.icon ? data.application.icon : undefined,
|
|
||||||
name: data.application.name,
|
|
||||||
guildId: data.application.guild_id,
|
|
||||||
flags: data.application.flags,
|
|
||||||
botPublic: data.application.bot_public,
|
|
||||||
owner: data.application.owner ? new User(session, data.application.owner as DiscordUser) : undefined,
|
|
||||||
botRequireCodeGrant: data.application.bot_require_code_grant,
|
|
||||||
coverImage: data.application.cover_image,
|
|
||||||
customInstallURL: data.application.custom_install_url,
|
|
||||||
description: data.application.description,
|
|
||||||
installParams: data.application.install_params,
|
|
||||||
tags: data.application.tags,
|
|
||||||
verifyKey: data.application.verify_key,
|
|
||||||
team: data.application.team ? NewTeam(session, data.application.team) : undefined,
|
|
||||||
primarySkuId: data.application.primary_sku_id,
|
|
||||||
privacyPolicyURL: data.application.privacy_policy_url,
|
|
||||||
rpcOrigins: data.application.rpc_origins,
|
|
||||||
slug: data.application.slug,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.setPrototypeOf(application, Application.prototype);
|
|
||||||
|
|
||||||
this.application = application;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Reference to the client that instantiated this Message */
|
|
||||||
readonly session: Session;
|
|
||||||
|
|
||||||
/** id of the message */
|
|
||||||
readonly id: Snowflake;
|
|
||||||
|
|
||||||
/** type of message */
|
|
||||||
type: MessageTypes;
|
|
||||||
|
|
||||||
/** id of the channel the message was sent in */
|
|
||||||
channelId: Snowflake;
|
|
||||||
|
|
||||||
/** id of the guild the message was sent in, this should exist on MESSAGE_CREATE and MESSAGE_UPDATE events */
|
|
||||||
guildId?: Snowflake;
|
|
||||||
|
|
||||||
/** if the message is an Interaction or application-owned webhook, this is the id of the application */
|
|
||||||
applicationId?: Snowflake;
|
|
||||||
|
|
||||||
/** mentions if any */
|
|
||||||
mentions: {
|
|
||||||
/** users specifically mentioned in the message */
|
|
||||||
users: User[];
|
|
||||||
|
|
||||||
/** roles specifically mentioned in this message */
|
|
||||||
roleIds: Snowflake[];
|
|
||||||
|
|
||||||
/** channels specifically mentioned in the message */
|
|
||||||
channels: Channel[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/** sent if the message is a response to an Interaction */
|
|
||||||
interaction?: MessageInteraction;
|
|
||||||
|
|
||||||
/** the author of this message, this field is **not** sent on webhook messages */
|
|
||||||
author!: User;
|
|
||||||
|
|
||||||
/** message flags combined as a bitfield */
|
|
||||||
flags?: MessageFlags;
|
|
||||||
|
|
||||||
/** whether this message is pinned */
|
|
||||||
pinned: boolean;
|
|
||||||
|
|
||||||
/** whether this was a TTS message */
|
|
||||||
tts: boolean;
|
|
||||||
|
|
||||||
/** contents of the message */
|
|
||||||
content: string;
|
|
||||||
|
|
||||||
/** used for validating a message was sent */
|
|
||||||
nonce?: string | number;
|
|
||||||
|
|
||||||
/** whether this message mentions everyone */
|
|
||||||
mentionEveryone: boolean;
|
|
||||||
|
|
||||||
/** when this message was sent */
|
|
||||||
timestamp: number;
|
|
||||||
|
|
||||||
/** when this message was edited */
|
|
||||||
editedTimestamp?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sent if the message contains stickers
|
|
||||||
* **this contains sticker items not stickers**
|
|
||||||
*/
|
|
||||||
stickers?: StickerItem[];
|
|
||||||
|
|
||||||
/** reactions to the message */
|
|
||||||
reactions: MessageReaction[];
|
|
||||||
|
|
||||||
/** any attached files */
|
|
||||||
attachments: Attachment[];
|
|
||||||
|
|
||||||
/** any embedded content */
|
|
||||||
embeds: DiscordEmbed[];
|
|
||||||
|
|
||||||
/** member properties for this message's author */
|
|
||||||
member?: Member;
|
|
||||||
|
|
||||||
/** the thread that was started from this message, includes {@link ThreadMember} */
|
|
||||||
thread?: ThreadChannel;
|
|
||||||
|
|
||||||
/** sent if the message contains components like buttons, action rows, or other interactive components */
|
|
||||||
components: Component[];
|
|
||||||
|
|
||||||
/** if the message is generated by a webhook, this is the webhook's author data */
|
|
||||||
webhook?: WebhookAuthor;
|
|
||||||
|
|
||||||
/** sent with Rich Presence-related chat embeds */
|
|
||||||
application?: Partial<Application>;
|
|
||||||
|
|
||||||
/** sent with Rich Presence-related chat embeds */
|
|
||||||
activity?: MessageActivity;
|
|
||||||
|
|
||||||
/** gets the timestamp of this message, this does not requires the timestamp field */
|
|
||||||
get createdTimestamp(): number {
|
|
||||||
return Snowflake.snowflakeToTimestamp(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** gets the timestamp of this message as a Date */
|
|
||||||
get createdAt(): Date {
|
|
||||||
return new Date(this.createdTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** gets the timestamp of this message (sent by the API) */
|
|
||||||
get sentAt(): Date {
|
|
||||||
return new Date(this.timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** gets the edited timestamp as a Date */
|
|
||||||
get editedAt(): Date | undefined {
|
|
||||||
return this.editedTimestamp ? new Date(this.editedTimestamp) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** whether this message was edited */
|
|
||||||
get edited(): number | undefined {
|
|
||||||
return this.editedTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** gets the url of the message that points to the message */
|
|
||||||
get url(): string {
|
|
||||||
return `https://discord.com/channels/${this.guildId ?? '@me'}/${this.channelId}/${this.id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compatibility with Discordeno
|
|
||||||
* same as Message.author.bot
|
|
||||||
*/
|
|
||||||
get isBot(): boolean {
|
|
||||||
return this.author.bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pins this message
|
|
||||||
*/
|
|
||||||
async pin(): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'PUT',
|
|
||||||
Routes.CHANNEL_PIN(this.channelId, this.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unpins this message
|
|
||||||
*/
|
|
||||||
async unpin(): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
Routes.CHANNEL_PIN(this.channelId, this.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Edits the current message */
|
|
||||||
async edit(options: EditMessage): Promise<Message> {
|
|
||||||
const message = await this.session.rest.runMethod(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
Routes.CHANNEL_MESSAGE(this.id, this.channelId),
|
|
||||||
{
|
|
||||||
content: options.content,
|
|
||||||
allowed_mentions: {
|
|
||||||
parse: options.allowedMentions?.parse,
|
|
||||||
roles: options.allowedMentions?.roles,
|
|
||||||
users: options.allowedMentions?.users,
|
|
||||||
replied_user: options.allowedMentions?.repliedUser,
|
|
||||||
},
|
|
||||||
flags: options.flags,
|
|
||||||
embeds: options.embeds,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** edits the current message flags to supress its embeds */
|
|
||||||
async suppressEmbeds(suppress: true): Promise<Message>;
|
|
||||||
async suppressEmbeds(suppress: false): Promise<Message | undefined>;
|
|
||||||
async suppressEmbeds(suppress = true) {
|
|
||||||
if (this.flags === MessageFlags.SupressEmbeds && suppress === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = await this.edit({ flags: MessageFlags.SupressEmbeds });
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** deletes this message */
|
|
||||||
async delete(reason?: string): Promise<Message> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
Routes.CHANNEL_MESSAGE(this.channelId, this.id),
|
|
||||||
{ reason },
|
|
||||||
);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Replies directly in the channel where the message was sent */
|
|
||||||
async reply(options: CreateMessage): Promise<Message> {
|
|
||||||
const message = await this.session.rest.runMethod<DiscordMessage>(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
Routes.CHANNEL_MESSAGES(this.channelId),
|
|
||||||
{
|
|
||||||
content: options.content,
|
|
||||||
file: options.files,
|
|
||||||
allowed_mentions: {
|
|
||||||
parse: options.allowedMentions?.parse,
|
|
||||||
roles: options.allowedMentions?.roles,
|
|
||||||
users: options.allowedMentions?.users,
|
|
||||||
replied_user: options.allowedMentions?.repliedUser,
|
|
||||||
},
|
|
||||||
message_reference: options.messageReference
|
|
||||||
? {
|
|
||||||
message_id: options.messageReference.messageId,
|
|
||||||
channel_id: options.messageReference.channelId,
|
|
||||||
guild_id: options.messageReference.guildId,
|
|
||||||
fail_if_not_exists: options.messageReference.failIfNotExists ?? true,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
embeds: options.embeds,
|
|
||||||
tts: options.tts,
|
|
||||||
components: options.components,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Message(this.session, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** alias for Message.addReaction */
|
|
||||||
get react() {
|
|
||||||
return this.addReaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** adds a Reaction */
|
|
||||||
async addReaction(reaction: EmojiResolvable): Promise<void> {
|
|
||||||
const r = typeof reaction === 'string' ? reaction : `${reaction.name}:${reaction.id}`;
|
|
||||||
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'PUT',
|
|
||||||
Routes.CHANNEL_MESSAGE_REACTION_ME(this.channelId, this.id, r),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** removes a reaction from someone */
|
|
||||||
async removeReaction(reaction: EmojiResolvable, options?: { userId: Snowflake }): Promise<void> {
|
|
||||||
const r = typeof reaction === 'string' ? reaction : `${reaction.name}:${reaction.id}`;
|
|
||||||
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
options?.userId
|
|
||||||
? Routes.CHANNEL_MESSAGE_REACTION_USER(
|
|
||||||
this.channelId,
|
|
||||||
this.id,
|
|
||||||
r,
|
|
||||||
options.userId,
|
|
||||||
)
|
|
||||||
: Routes.CHANNEL_MESSAGE_REACTION_ME(this.channelId, this.id, r),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get users who reacted with this emoji
|
|
||||||
* not recommended since the cache handles reactions already
|
|
||||||
*/
|
|
||||||
async fetchReactions(reaction: EmojiResolvable, options?: GetReactions): Promise<User[]> {
|
|
||||||
const r = typeof reaction === 'string' ? reaction : `${reaction.name}:${reaction.id}`;
|
|
||||||
|
|
||||||
const users = await this.session.rest.runMethod<DiscordUser[]>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.CHANNEL_MESSAGE_REACTION(this.channelId, this.id, encodeURIComponent(r), options),
|
|
||||||
);
|
|
||||||
|
|
||||||
return users.map((user) => new User(this.session, user));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* same as Message.removeReaction but removes using a unicode emoji
|
|
||||||
*/
|
|
||||||
async removeReactionEmoji(reaction: EmojiResolvable): Promise<void> {
|
|
||||||
const r = typeof reaction === 'string' ? reaction : `${reaction.name}:${reaction.id}`;
|
|
||||||
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
Routes.CHANNEL_MESSAGE_REACTION(this.channelId, this.id, r),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** nukes every reaction on the message */
|
|
||||||
async nukeReactions(): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
Routes.CHANNEL_MESSAGE_REACTIONS(this.channelId, this.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** publishes/crossposts a message to all followers */
|
|
||||||
async crosspost(): Promise<Message> {
|
|
||||||
const message = await this.session.rest.runMethod<DiscordMessage>(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
Routes.CHANNEL_MESSAGE_CROSSPOST(this.channelId, this.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Message(this.session, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** fetches this message, meant to be used with Function.call since its redundant */
|
|
||||||
async fetch(): Promise<(Message | undefined)> {
|
|
||||||
const message = await this.session.rest.runMethod<DiscordMessage>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.CHANNEL_MESSAGE(this.channelId, this.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!message?.id) return;
|
|
||||||
|
|
||||||
return new Message(this.session, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** alias of Message.crosspost */
|
|
||||||
get publish() {
|
|
||||||
return this.crosspost;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** wheter the message comes from a guild **/
|
|
||||||
inGuild(): this is GuildMessage {
|
|
||||||
return !!this.guildId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** wheter the messages comes from a Webhook */
|
|
||||||
isWebhookMessage(): this is WebhookMessage {
|
|
||||||
return !!this.webhook;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Message;
|
|
@ -1,83 +0,0 @@
|
|||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { DiscordMemberWithUser, DiscordMessageReactionAdd, DiscordReaction } from '../../discordeno/mod.ts';
|
|
||||||
import Emoji from './Emoji.ts';
|
|
||||||
import Member from './Member.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents when a new reaction was added to a message.
|
|
||||||
* @link https://discord.com/developers/docs/topics/gateway#message-reaction-add
|
|
||||||
*/
|
|
||||||
export interface MessageReactionAdd {
|
|
||||||
userId: string;
|
|
||||||
channelId: string;
|
|
||||||
messageId: string;
|
|
||||||
guildId?: string;
|
|
||||||
member?: Member;
|
|
||||||
emoji: Partial<Emoji>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents when a reaction was removed from a message.
|
|
||||||
* Equal to MessageReactionAdd but without 'member' property.
|
|
||||||
* @see {@link MessageReactionAdd}
|
|
||||||
* @link https://discord.com/developers/docs/topics/gateway#message-reaction-remove-message-reaction-remove-event-fields
|
|
||||||
*/
|
|
||||||
export type MessageReactionRemove = Omit<MessageReactionAdd, 'member'>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents when all reactions were removed from a message.
|
|
||||||
* Equals to MessageReactionAdd but with 'channelId', 'messageId' and 'guildId' properties guaranteed.
|
|
||||||
* @see {@link MessageReactionAdd}
|
|
||||||
* @link https://discord.com/developers/docs/topics/gateway#message-reaction-remove-all
|
|
||||||
*/
|
|
||||||
export type MessageReactionRemoveAll = Pick<MessageReactionAdd, 'channelId' | 'messageId' | 'guildId'>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents when a reaction-emoji was removed from a message.
|
|
||||||
* Equals to MessageReactionAdd but with 'channelId', 'messageId', 'emoji' and 'guildId' properties guaranteed.
|
|
||||||
* @see {@link MessageReactionRemove}
|
|
||||||
* @see {@link Emoji}
|
|
||||||
* @link https://discord.com/developers/docs/topics/gateway#message-reaction-remove-emoji
|
|
||||||
*/
|
|
||||||
export type MessageReactionRemoveEmoji = Pick<
|
|
||||||
MessageReactionAdd,
|
|
||||||
'channelId' | 'guildId' | 'messageId' | 'emoji'
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new MessageReactionAdd object.
|
|
||||||
* @param session - Current application session.
|
|
||||||
* @param data - Discord message reaction to parse.
|
|
||||||
*/
|
|
||||||
export function NewMessageReactionAdd(session: Session, data: DiscordMessageReactionAdd): MessageReactionAdd {
|
|
||||||
return {
|
|
||||||
userId: data.user_id,
|
|
||||||
channelId: data.channel_id,
|
|
||||||
messageId: data.message_id,
|
|
||||||
guildId: data.guild_id,
|
|
||||||
member: data.member
|
|
||||||
? new Member(session, data.member as DiscordMemberWithUser, data.guild_id || '')
|
|
||||||
: undefined,
|
|
||||||
emoji: new Emoji(session, data.emoji),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a reaction
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#reaction-object
|
|
||||||
*/
|
|
||||||
export class MessageReaction {
|
|
||||||
constructor(session: Session, data: DiscordReaction) {
|
|
||||||
this.session = session;
|
|
||||||
this.me = data.me;
|
|
||||||
this.count = data.count;
|
|
||||||
this.emoji = new Emoji(session, data.emoji);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
me: boolean;
|
|
||||||
count: number;
|
|
||||||
emoji: Emoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MessageReaction;
|
|
@ -1,44 +0,0 @@
|
|||||||
import { BitwisePermissionFlags } from '../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
export type PermissionString = keyof typeof BitwisePermissionFlags;
|
|
||||||
export type PermissionResolvable =
|
|
||||||
| bigint
|
|
||||||
| PermissionString
|
|
||||||
| PermissionString[]
|
|
||||||
| BitwisePermissionFlags;
|
|
||||||
|
|
||||||
export class Permissions {
|
|
||||||
static Flags = BitwisePermissionFlags;
|
|
||||||
bitfield: bigint;
|
|
||||||
|
|
||||||
constructor(bitfield: PermissionResolvable) {
|
|
||||||
this.bitfield = Permissions.resolve(bitfield);
|
|
||||||
}
|
|
||||||
|
|
||||||
has(bit: PermissionResolvable): boolean {
|
|
||||||
if (this.bitfield & BigInt(Permissions.Flags.ADMINISTRATOR)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!(this.bitfield & Permissions.resolve(bit));
|
|
||||||
}
|
|
||||||
|
|
||||||
static resolve(bit: PermissionResolvable): bigint {
|
|
||||||
switch (typeof bit) {
|
|
||||||
case 'bigint':
|
|
||||||
return bit;
|
|
||||||
case 'number':
|
|
||||||
return BigInt(bit);
|
|
||||||
case 'string':
|
|
||||||
return BigInt(Permissions.Flags[bit]);
|
|
||||||
case 'object':
|
|
||||||
return Permissions.resolve(
|
|
||||||
bit.map((p) => BigInt(Permissions.Flags[p])).reduce((acc, cur) => acc | cur, 0n),
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw new TypeError(`Cannot resolve permission: ${bit}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Permissions;
|
|
@ -1,92 +0,0 @@
|
|||||||
import type {
|
|
||||||
ActivityTypes,
|
|
||||||
DiscordActivityButton,
|
|
||||||
DiscordActivitySecrets,
|
|
||||||
DiscordClientStatus,
|
|
||||||
DiscordPresenceUpdate,
|
|
||||||
} from '../../discordeno/mod.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import { User } from './User.ts';
|
|
||||||
import { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { ComponentEmoji } from '../Util.ts';
|
|
||||||
|
|
||||||
export interface ActivityAssets {
|
|
||||||
largeImage?: string;
|
|
||||||
largeText?: string;
|
|
||||||
smallImage?: string;
|
|
||||||
smallText?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Activities {
|
|
||||||
name: string;
|
|
||||||
type: ActivityTypes;
|
|
||||||
url?: string;
|
|
||||||
createdAt: number;
|
|
||||||
timestamps?: {
|
|
||||||
start?: number;
|
|
||||||
end?: number;
|
|
||||||
};
|
|
||||||
applicationId?: Snowflake;
|
|
||||||
details?: string;
|
|
||||||
state?: string;
|
|
||||||
emoji?: ComponentEmoji;
|
|
||||||
party?: {
|
|
||||||
id?: string;
|
|
||||||
size?: number[];
|
|
||||||
};
|
|
||||||
assets?: ActivityAssets;
|
|
||||||
secrets?: DiscordActivitySecrets;
|
|
||||||
instance?: boolean;
|
|
||||||
flags?: number;
|
|
||||||
buttons?: DiscordActivityButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum StatusTypes {
|
|
||||||
online = 0,
|
|
||||||
dnd = 1,
|
|
||||||
idle = 2,
|
|
||||||
invisible = 3,
|
|
||||||
offline = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Presence {
|
|
||||||
constructor(session: Session, data: DiscordPresenceUpdate) {
|
|
||||||
this.session = session;
|
|
||||||
this.user = new User(this.session, data.user);
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.status = StatusTypes[data.status];
|
|
||||||
this.activities = data.activities.map<Activities>((activity) =>
|
|
||||||
Object.create({
|
|
||||||
name: activity.name,
|
|
||||||
type: activity.type,
|
|
||||||
url: activity.url ? activity.url : undefined,
|
|
||||||
createdAt: activity.created_at,
|
|
||||||
timestamps: activity.timestamps,
|
|
||||||
applicationId: activity.application_id,
|
|
||||||
details: activity.details ? activity.details : undefined,
|
|
||||||
state: activity.state,
|
|
||||||
emoji: activity.emoji ? activity.emoji : undefined,
|
|
||||||
party: activity.party ? activity.party : undefined,
|
|
||||||
assets: activity.assets
|
|
||||||
? {
|
|
||||||
largeImage: activity.assets.large_image,
|
|
||||||
largeText: activity.assets.large_text,
|
|
||||||
smallImage: activity.assets.small_image,
|
|
||||||
smallText: activity.assets.small_text,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
secrets: activity.secrets ? activity.secrets : undefined,
|
|
||||||
instance: !!activity.instance,
|
|
||||||
flags: activity.flags,
|
|
||||||
buttons: activity.buttons,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.clientStatus = data.client_status;
|
|
||||||
}
|
|
||||||
session: Session;
|
|
||||||
user: User;
|
|
||||||
guildId: Snowflake;
|
|
||||||
status: StatusTypes;
|
|
||||||
activities: Activities[];
|
|
||||||
clientStatus: DiscordClientStatus;
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { DiscordRole } from '../../discordeno/mod.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import { Snowflake } from '../Snowflake.ts';
|
|
||||||
import { Guild, type ModifyGuildRole } from './guilds.ts';
|
|
||||||
import Permissions from './Permissions.ts';
|
|
||||||
import Util from '../Util.ts';
|
|
||||||
|
|
||||||
export class Role implements Model {
|
|
||||||
constructor(session: Session, data: DiscordRole, guildId: Snowflake) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.guildId = guildId;
|
|
||||||
this.hoist = data.hoist;
|
|
||||||
this.iconHash = data.icon ? Util.iconHashToBigInt(data.icon) : undefined;
|
|
||||||
this.color = data.color;
|
|
||||||
this.name = data.name;
|
|
||||||
this.unicodeEmoji = data.unicode_emoji;
|
|
||||||
this.mentionable = data.mentionable;
|
|
||||||
this.managed = data.managed;
|
|
||||||
this.permissions = new Permissions(BigInt(data.permissions));
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
readonly id: Snowflake;
|
|
||||||
|
|
||||||
readonly guildId: Snowflake;
|
|
||||||
|
|
||||||
hoist: boolean;
|
|
||||||
iconHash?: bigint;
|
|
||||||
color: number;
|
|
||||||
name: string;
|
|
||||||
unicodeEmoji?: string;
|
|
||||||
mentionable: boolean;
|
|
||||||
managed: boolean;
|
|
||||||
permissions: Permissions;
|
|
||||||
|
|
||||||
get createdTimestamp(): number {
|
|
||||||
return Snowflake.snowflakeToTimestamp(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
get createdAt(): Date {
|
|
||||||
return new Date(this.createdTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
get hexColor(): string {
|
|
||||||
return `#${this.color.toString(16).padStart(6, '0')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(): Promise<void> {
|
|
||||||
await Guild.prototype.deleteRole.call({ id: this.guildId, session: this.session }, this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
async edit(options: ModifyGuildRole): Promise<Role> {
|
|
||||||
const role = await Guild.prototype.editRole.call({ id: this.guildId, session: this.session }, this.id, options);
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
|
|
||||||
async add(memberId: Snowflake, reason?: string): Promise<void> {
|
|
||||||
await Guild.prototype.addRole.call({ id: this.guildId, session: this.session }, memberId, this.id, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(memberId: Snowflake, reason?: string): Promise<void> {
|
|
||||||
await Guild.prototype.removeRole.call({ id: this.guildId, session: this.session }, memberId, this.id, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
switch (this.id) {
|
|
||||||
case this.guildId:
|
|
||||||
return '@everyone';
|
|
||||||
default:
|
|
||||||
return `<@&${this.id}>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Role;
|
|
@ -1,66 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { DiscordStageInstance as DiscordAutoClosingStageInstance } from '../../discordeno/mod.ts';
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
|
|
||||||
export interface DiscordStageInstanceB extends DiscordAutoClosingStageInstance {
|
|
||||||
privacy_level: PrivacyLevels;
|
|
||||||
discoverable_disabled: boolean;
|
|
||||||
guild_scheduled_event_id: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PrivacyLevels {
|
|
||||||
Public = 1,
|
|
||||||
GuildOnly = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type stageEditOptions = {
|
|
||||||
topic?: string;
|
|
||||||
privacy?: PrivacyLevels;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class StageInstance implements Model {
|
|
||||||
constructor(session: Session, data: DiscordStageInstanceB) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.channelId = data.channel_id;
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.topic = data.topic;
|
|
||||||
this.privacyLevel = data.privacy_level;
|
|
||||||
this.discoverableDisabled = data.discoverable_disabled;
|
|
||||||
this.guildScheduledEventId = data.guild_scheduled_event_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
readonly id: Snowflake;
|
|
||||||
|
|
||||||
channelId: Snowflake;
|
|
||||||
guildId: Snowflake;
|
|
||||||
topic: string;
|
|
||||||
|
|
||||||
// TODO: see if this works
|
|
||||||
privacyLevel: PrivacyLevels;
|
|
||||||
discoverableDisabled: boolean;
|
|
||||||
guildScheduledEventId: Snowflake;
|
|
||||||
|
|
||||||
async edit(options: stageEditOptions): Promise<StageInstance> {
|
|
||||||
const stageInstance = await this.session.rest.runMethod<DiscordStageInstanceB>(
|
|
||||||
this.session.rest,
|
|
||||||
'PATCH',
|
|
||||||
Routes.STAGE_INSTANCE(this.id),
|
|
||||||
{
|
|
||||||
topic: options.topic,
|
|
||||||
privacy_level: options.privacy,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return new StageInstance(this.session, stageInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(this.session.rest, 'DELETE', Routes.STAGE_INSTANCE(this.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StageInstance;
|
|
@ -1,70 +0,0 @@
|
|||||||
import type { DiscordSticker, DiscordStickerPack, StickerFormatTypes, StickerTypes } from '../../discordeno/mod.ts';
|
|
||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import { User } from './User.ts';
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
|
|
||||||
export interface StickerItem {
|
|
||||||
id: Snowflake;
|
|
||||||
name: string;
|
|
||||||
formatType: StickerFormatTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StickerPack {
|
|
||||||
id: Snowflake;
|
|
||||||
stickers: Sticker[];
|
|
||||||
name: string;
|
|
||||||
skuId: Snowflake;
|
|
||||||
coverStickerId?: Snowflake;
|
|
||||||
description: string;
|
|
||||||
bannerAssetId?: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Sticker implements Model {
|
|
||||||
constructor(session: Session, data: DiscordSticker) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.packId = data.pack_id;
|
|
||||||
this.name = data.name;
|
|
||||||
this.description = data.description;
|
|
||||||
this.tags = data.tags.split(',');
|
|
||||||
this.type = data.type;
|
|
||||||
this.formatType = data.format_type;
|
|
||||||
this.available = !!data.available;
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.user = data.user ? new User(this.session, data.user) : undefined;
|
|
||||||
this.sortValue = data.sort_value;
|
|
||||||
}
|
|
||||||
session: Session;
|
|
||||||
id: Snowflake;
|
|
||||||
packId?: Snowflake;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
tags: string[];
|
|
||||||
type: StickerTypes;
|
|
||||||
formatType: StickerFormatTypes;
|
|
||||||
available?: boolean;
|
|
||||||
guildId?: Snowflake;
|
|
||||||
user?: User;
|
|
||||||
sortValue?: number;
|
|
||||||
|
|
||||||
async fetchPremiumPack(): Promise<StickerPack> {
|
|
||||||
const data = await this.session.rest.runMethod<DiscordStickerPack>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.STICKER_PACKS(),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
id: data.id,
|
|
||||||
stickers: data.stickers.map((st) => new Sticker(this.session, st)),
|
|
||||||
name: data.name,
|
|
||||||
skuId: data.sku_id,
|
|
||||||
coverStickerId: data.cover_sticker_id,
|
|
||||||
description: data.description,
|
|
||||||
bannerAssetId: data.banner_asset_id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Sticker;
|
|
@ -1,47 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { DiscordThreadMember } from '../../discordeno/mod.ts';
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A member that comes from a thread
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#thread-member-object
|
|
||||||
* **/
|
|
||||||
export class ThreadMember implements Model {
|
|
||||||
constructor(session: Session, data: DiscordThreadMember) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.flags = data.flags;
|
|
||||||
this.timestamp = Date.parse(data.join_timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
readonly id: Snowflake;
|
|
||||||
flags: number;
|
|
||||||
timestamp: number;
|
|
||||||
|
|
||||||
get threadId(): Snowflake {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
async quitThread(memberId?: Snowflake): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
Routes.THREAD_USER(this.id, memberId ?? this.session.botId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchMember(memberId?: Snowflake): Promise<ThreadMember> {
|
|
||||||
const member = await this.session.rest.runMethod<DiscordThreadMember>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.THREAD_USER(this.id, memberId ?? this.session.botId),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new ThreadMember(this.session, member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ThreadMember;
|
|
@ -1,113 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { DiscordUser, PremiumTypes, UserFlags } from '../../discordeno/mod.ts';
|
|
||||||
import type { ImageFormat, ImageSize } from '../Util.ts';
|
|
||||||
import Util from '../Util.ts';
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
|
|
||||||
export type avatarOptions = {
|
|
||||||
format?: ImageFormat;
|
|
||||||
size?: ImageSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/user#user-object
|
|
||||||
* Represents a user
|
|
||||||
*/
|
|
||||||
export class User implements Model {
|
|
||||||
constructor(session: Session, data: DiscordUser) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
|
|
||||||
this.username = data.username;
|
|
||||||
this.discriminator = data.discriminator;
|
|
||||||
this.avatarHash = data.avatar ? Util.iconHashToBigInt(data.avatar) : undefined;
|
|
||||||
this.accentColor = data.accent_color;
|
|
||||||
this.bot = !!data.bot;
|
|
||||||
this.system = !!data.system;
|
|
||||||
this.banner = data.banner ? Util.iconHashToBigInt(data.banner) : undefined;
|
|
||||||
this.mfaEnabled = !!data.mfa_enabled;
|
|
||||||
this.locale = data.locale;
|
|
||||||
this.email = data.email ? data.email : undefined;
|
|
||||||
this.verified = data.verified;
|
|
||||||
this.flags = data.flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** the session that instantiated this User */
|
|
||||||
readonly session: Session;
|
|
||||||
|
|
||||||
/** the user's id */
|
|
||||||
readonly id: Snowflake;
|
|
||||||
|
|
||||||
/** the user's username, not unique across the platform */
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
/** the user's 4-digit discord-tag */
|
|
||||||
discriminator: string;
|
|
||||||
|
|
||||||
/** the user's avatar hash optimized as a bigint */
|
|
||||||
avatarHash?: bigint;
|
|
||||||
|
|
||||||
/** the user's banner color encoded as an integer representation of hexadecimal color code */
|
|
||||||
accentColor?: number;
|
|
||||||
|
|
||||||
/** whether the user belongs to an OAuth2 application */
|
|
||||||
bot: boolean;
|
|
||||||
|
|
||||||
/** whether the user is an Official Discord System user (part of the urgent message system) */
|
|
||||||
system: boolean;
|
|
||||||
|
|
||||||
/** the user's banner hash optimized as a bigint */
|
|
||||||
banner?: bigint;
|
|
||||||
|
|
||||||
/** whether the user has two factor enabled on their account */
|
|
||||||
mfaEnabled: boolean;
|
|
||||||
|
|
||||||
/** the user's chosen language option */
|
|
||||||
locale?: string;
|
|
||||||
|
|
||||||
/** the user's email */
|
|
||||||
email?: string;
|
|
||||||
|
|
||||||
/** the flags on a user's account */
|
|
||||||
flags?: UserFlags;
|
|
||||||
|
|
||||||
/** whether the email on this account has been verified */
|
|
||||||
verified?: boolean;
|
|
||||||
|
|
||||||
/** the type of Nitro subscription on a user's account */
|
|
||||||
premiumType?: PremiumTypes;
|
|
||||||
|
|
||||||
/** the public flags on a user's account */
|
|
||||||
publicFlags?: UserFlags;
|
|
||||||
|
|
||||||
/** gets the user's username#discriminator */
|
|
||||||
get tag(): string {
|
|
||||||
return `${this.username}#${this.discriminator}}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** fetches this user */
|
|
||||||
fetch() {
|
|
||||||
return this.session.fetchUser(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** gets the user's avatar */
|
|
||||||
avatarURL(options: avatarOptions): string {
|
|
||||||
let url: string;
|
|
||||||
|
|
||||||
if (!this.avatarHash) {
|
|
||||||
url = Routes.USER_DEFAULT_AVATAR(Number(this.discriminator) % 5);
|
|
||||||
} else {
|
|
||||||
url = Routes.USER_AVATAR(this.id, Util.iconBigintToHash(this.avatarHash));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Util.formatImageURL(url, options.size ?? 128, options.format);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return `<@${this.id}>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default User;
|
|
@ -1,188 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type {
|
|
||||||
DiscordEmbed,
|
|
||||||
DiscordMessage,
|
|
||||||
DiscordMessageComponents,
|
|
||||||
DiscordWebhook,
|
|
||||||
FileContent,
|
|
||||||
WebhookTypes,
|
|
||||||
} from '../../discordeno/mod.ts';
|
|
||||||
import type { WebhookOptions } from '../Routes.ts';
|
|
||||||
import type { Attachment } from './Attachment.ts';
|
|
||||||
import type { AllowedMentions, CreateMessage } from './Message.ts';
|
|
||||||
import Util from '../Util.ts';
|
|
||||||
import User from './User.ts';
|
|
||||||
import Message from './Message.ts';
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
|
|
||||||
export type executeWebhookOptions = WebhookOptions & CreateMessage & { avatarUrl?: string; username?: string };
|
|
||||||
|
|
||||||
export type editMessageWithThread = EditWebhookMessage & { threadId?: Snowflake };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/webhook#edit-webhook-message-jsonform-params
|
|
||||||
*/
|
|
||||||
export interface EditWebhookMessage {
|
|
||||||
content?: string;
|
|
||||||
embeds?: DiscordEmbed[];
|
|
||||||
files?: FileContent[];
|
|
||||||
allowedMentions?: AllowedMentions;
|
|
||||||
attachments?: Attachment[];
|
|
||||||
components?: DiscordMessageComponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Webhook implements Model {
|
|
||||||
constructor(session: Session, data: DiscordWebhook) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.type = data.type;
|
|
||||||
this.token = data.token;
|
|
||||||
|
|
||||||
if (data.avatar) {
|
|
||||||
this.avatar = Util.iconHashToBigInt(data.avatar);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.user) {
|
|
||||||
this.user = new User(session, data.user);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.guild_id) {
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.channel_id) {
|
|
||||||
this.channelId = data.channel_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.application_id) {
|
|
||||||
this.applicationId = data.application_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
readonly id: Snowflake;
|
|
||||||
type: WebhookTypes;
|
|
||||||
token?: string;
|
|
||||||
avatar?: bigint;
|
|
||||||
applicationId?: Snowflake;
|
|
||||||
channelId?: Snowflake;
|
|
||||||
guildId?: Snowflake;
|
|
||||||
user?: User;
|
|
||||||
|
|
||||||
async execute(
|
|
||||||
options?: executeWebhookOptions,
|
|
||||||
): Promise<(Message | undefined)> {
|
|
||||||
if (!this.token) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
content: options?.content,
|
|
||||||
embeds: options?.embeds,
|
|
||||||
tts: options?.tts,
|
|
||||||
allowed_mentions: options?.allowedMentions,
|
|
||||||
components: options?.components,
|
|
||||||
file: options?.files,
|
|
||||||
};
|
|
||||||
|
|
||||||
const message = this.session.rest.sendRequest<DiscordMessage>(this.session.rest, {
|
|
||||||
url: Routes.WEBHOOK(this.id, this.token!, {
|
|
||||||
wait: options?.wait,
|
|
||||||
threadId: options?.threadId,
|
|
||||||
}),
|
|
||||||
method: 'POST',
|
|
||||||
payload: this.session.rest.createRequestBody(this.session.rest, {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
...data,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (options?.wait ?? true) ? new Message(this.session, await (message)) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch(): Promise<Webhook> {
|
|
||||||
const message = await this.session.rest.runMethod<DiscordWebhook>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.WEBHOOK_TOKEN(this.id, this.token),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Webhook(this.session, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchMessage(messageId: Snowflake, threadId?: Snowflake): Promise<Message | undefined> {
|
|
||||||
if (!this.token) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = await this.session.rest.runMethod<DiscordMessage>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.WEBHOOK_MESSAGE(this.id, this.token, messageId, { threadId }),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Message(this.session, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteMessage(messageId: Snowflake, threadId?: Snowflake): Promise<void> {
|
|
||||||
if (!this.token) {
|
|
||||||
throw new Error('No token found');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
Routes.WEBHOOK_MESSAGE(this.id, this.token, messageId, { threadId }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async editMessage(
|
|
||||||
messageId?: Snowflake,
|
|
||||||
options?: editMessageWithThread,
|
|
||||||
): Promise<Message> {
|
|
||||||
if (!this.token) {
|
|
||||||
throw new Error('No token found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = await this.session.rest.runMethod<DiscordMessage>(
|
|
||||||
this.session.rest,
|
|
||||||
'PATCH',
|
|
||||||
messageId
|
|
||||||
? Routes.WEBHOOK_MESSAGE(this.id, this.token, messageId)
|
|
||||||
: Routes.WEBHOOK_MESSAGE_ORIGINAL(this.id, this.token),
|
|
||||||
{
|
|
||||||
content: options?.content,
|
|
||||||
embeds: options?.embeds,
|
|
||||||
file: options?.files,
|
|
||||||
components: options?.components,
|
|
||||||
allowed_mentions: options?.allowedMentions && {
|
|
||||||
parse: options?.allowedMentions.parse,
|
|
||||||
replied_user: options?.allowedMentions.repliedUser,
|
|
||||||
users: options?.allowedMentions.users,
|
|
||||||
roles: options?.allowedMentions.roles,
|
|
||||||
},
|
|
||||||
attachments: options?.attachments?.map((attachment) => {
|
|
||||||
return {
|
|
||||||
id: attachment.id,
|
|
||||||
filename: attachment.name,
|
|
||||||
content_type: attachment.contentType,
|
|
||||||
size: attachment.size,
|
|
||||||
url: attachment.attachment,
|
|
||||||
proxy_url: attachment.proxyUrl,
|
|
||||||
height: attachment.height,
|
|
||||||
width: attachment.width,
|
|
||||||
ephemeral: attachment.ephemeral,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Message(this.session, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Webhook;
|
|
@ -1,33 +0,0 @@
|
|||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { DiscordWelcomeScreenChannel } from '../../discordeno/mod.ts';
|
|
||||||
import Emoji from './Emoji.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not a channel
|
|
||||||
* @link https://discord.com/developers/docs/resources/guild#welcome-screen-object-welcome-screen-channel-structure
|
|
||||||
*/
|
|
||||||
export class WelcomeChannel implements Model {
|
|
||||||
constructor(session: Session, data: DiscordWelcomeScreenChannel) {
|
|
||||||
this.session = session;
|
|
||||||
this.channelId = data.channel_id;
|
|
||||||
this.description = data.description;
|
|
||||||
this.emoji = new Emoji(session, {
|
|
||||||
name: data.emoji_name ? data.emoji_name : undefined,
|
|
||||||
id: data.emoji_id ? data.emoji_id : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
session: Session;
|
|
||||||
channelId: Snowflake;
|
|
||||||
description: string;
|
|
||||||
emoji: Emoji;
|
|
||||||
|
|
||||||
/** alias for WelcomeScreenChannel.channelId */
|
|
||||||
get id(): Snowflake {
|
|
||||||
return this.channelId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WelcomeChannel;
|
|
@ -1,26 +0,0 @@
|
|||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { DiscordWelcomeScreen } from '../../discordeno/mod.ts';
|
|
||||||
import WelcomeChannel from './WelcomeChannel.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/guild#welcome-screen-object
|
|
||||||
*/
|
|
||||||
export class WelcomeScreen {
|
|
||||||
constructor(session: Session, data: DiscordWelcomeScreen) {
|
|
||||||
this.session = session;
|
|
||||||
this.welcomeChannels = data.welcome_channels.map((welcomeChannel) =>
|
|
||||||
new WelcomeChannel(session, welcomeChannel)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (data.description) {
|
|
||||||
this.description = data.description;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
|
|
||||||
description?: string;
|
|
||||||
welcomeChannels: WelcomeChannel[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WelcomeScreen;
|
|
@ -1,40 +0,0 @@
|
|||||||
import { DiscordGuildWidget } from '../../api-types/discord.ts';
|
|
||||||
import { Session } from '../Session.ts';
|
|
||||||
import { Snowflake } from '../Snowflake.ts';
|
|
||||||
import { Model } from './Base.ts';
|
|
||||||
import { PartialChannel } from './channels.ts';
|
|
||||||
|
|
||||||
export interface WidgetMember {
|
|
||||||
id?: string;
|
|
||||||
username: string;
|
|
||||||
avatar?: string | null;
|
|
||||||
status: string;
|
|
||||||
avatarURL: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Widget implements Model {
|
|
||||||
constructor(session: Session, data: DiscordGuildWidget) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.name = data.name;
|
|
||||||
this.instantInvite = data.instant_invite;
|
|
||||||
this.channels = data.channels;
|
|
||||||
this.members = data.members.map((x) => {
|
|
||||||
return {
|
|
||||||
id: x.id,
|
|
||||||
username: x.username,
|
|
||||||
avatar: x.avatar,
|
|
||||||
status: x.status,
|
|
||||||
avatarURL: x.avatar_url,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.presenceCount = data.presence_count;
|
|
||||||
}
|
|
||||||
session: Session;
|
|
||||||
id: Snowflake;
|
|
||||||
name: string;
|
|
||||||
instantInvite?: string;
|
|
||||||
channels: PartialChannel[];
|
|
||||||
members: WidgetMember[];
|
|
||||||
presenceCount: number;
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
import type { DiscordEmbed, DiscordEmbedField, DiscordEmbedProvider } from '../../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
export interface EmbedFooter {
|
|
||||||
text: string;
|
|
||||||
iconUrl?: string;
|
|
||||||
proxyIconUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmbedAuthor {
|
|
||||||
name: string;
|
|
||||||
text?: string;
|
|
||||||
url?: string;
|
|
||||||
iconUrl?: string;
|
|
||||||
proxyIconUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmbedVideo {
|
|
||||||
height?: number;
|
|
||||||
proxyUrl?: string;
|
|
||||||
url?: string;
|
|
||||||
width?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EmbedBuilder {
|
|
||||||
#data: DiscordEmbed;
|
|
||||||
constructor(data: DiscordEmbed = {}) {
|
|
||||||
this.#data = data;
|
|
||||||
if (!this.#data.fields) this.#data.fields = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
setAuthor(author: EmbedAuthor): EmbedBuilder {
|
|
||||||
this.#data.author = {
|
|
||||||
name: author.name,
|
|
||||||
icon_url: author.iconUrl,
|
|
||||||
proxy_icon_url: author.proxyIconUrl,
|
|
||||||
url: author.url,
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setColor(color: number): EmbedBuilder {
|
|
||||||
this.#data.color = color;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDescription(description: string): EmbedBuilder {
|
|
||||||
this.#data.description = description;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addField(field: DiscordEmbedField): EmbedBuilder {
|
|
||||||
this.#data.fields!.push(field);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFooter(footer: EmbedFooter): EmbedBuilder {
|
|
||||||
this.#data.footer = {
|
|
||||||
text: footer.text,
|
|
||||||
icon_url: footer.iconUrl,
|
|
||||||
proxy_icon_url: footer.proxyIconUrl,
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setImage(image: string): EmbedBuilder {
|
|
||||||
this.#data.image = { url: image };
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setProvider(provider: DiscordEmbedProvider): EmbedBuilder {
|
|
||||||
this.#data.provider = provider;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setThumbnail(thumbnail: string): EmbedBuilder {
|
|
||||||
this.#data.thumbnail = { url: thumbnail };
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimestamp(timestamp: string | Date): EmbedBuilder {
|
|
||||||
this.#data.timestamp = timestamp instanceof Date ? timestamp.toISOString() : timestamp;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTitle(title: string, url?: string): EmbedBuilder {
|
|
||||||
this.#data.title = title;
|
|
||||||
if (url) this.setUrl(url);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUrl(url: string): EmbedBuilder {
|
|
||||||
this.#data.url = url;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setVideo(video: EmbedVideo): EmbedBuilder {
|
|
||||||
this.#data.video = {
|
|
||||||
height: video.height,
|
|
||||||
proxy_url: video.proxyUrl,
|
|
||||||
url: video.url,
|
|
||||||
width: video.width,
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): DiscordEmbed {
|
|
||||||
return this.#data;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
import type { DiscordInputTextComponent, MessageComponentTypes, TextStyles } from '../../../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
export class InputTextBuilder {
|
|
||||||
constructor() {
|
|
||||||
this.#data = {} as DiscordInputTextComponent;
|
|
||||||
this.type = 4;
|
|
||||||
}
|
|
||||||
#data: DiscordInputTextComponent;
|
|
||||||
type: MessageComponentTypes.InputText;
|
|
||||||
|
|
||||||
setStyle(style: TextStyles): this {
|
|
||||||
this.#data.style = style;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLabel(label: string): this {
|
|
||||||
this.#data.label = label;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlaceholder(placeholder: string): this {
|
|
||||||
this.#data.placeholder = placeholder;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLength(max?: number, min?: number): this {
|
|
||||||
this.#data.max_length = max;
|
|
||||||
this.#data.min_length = min;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCustomId(id: string): this {
|
|
||||||
this.#data.custom_id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(value: string): this {
|
|
||||||
this.#data.value = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setRequired(required = true): this {
|
|
||||||
this.#data.required = required;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
toJSON(): DiscordInputTextComponent {
|
|
||||||
return { ...this.#data, type: this.type };
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
import type { DiscordActionRow, MessageComponentTypes } from '../../../../discordeno/mod.ts';
|
|
||||||
import type { ComponentBuilder } from '../../../Util.ts';
|
|
||||||
|
|
||||||
export class ActionRowBuilder<T extends ComponentBuilder> {
|
|
||||||
constructor() {
|
|
||||||
this.components = [] as T[];
|
|
||||||
this.type = 1;
|
|
||||||
}
|
|
||||||
components: T[];
|
|
||||||
type: MessageComponentTypes.ActionRow;
|
|
||||||
|
|
||||||
addComponents(...components: T[]): this {
|
|
||||||
this.components.push(...components);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setComponents(...components: T[]): this {
|
|
||||||
this.components.splice(
|
|
||||||
0,
|
|
||||||
this.components.length,
|
|
||||||
...components,
|
|
||||||
);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): DiscordActionRow {
|
|
||||||
return {
|
|
||||||
type: this.type,
|
|
||||||
components: this.components.map((c) => c.toJSON()) as DiscordActionRow['components'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import { type ButtonStyles, type DiscordButtonComponent, MessageComponentTypes } from '../../../../discordeno/mod.ts';
|
|
||||||
import type { ComponentEmoji } from '../../../Util.ts';
|
|
||||||
|
|
||||||
export class ButtonBuilder {
|
|
||||||
constructor() {
|
|
||||||
this.#data = {} as DiscordButtonComponent;
|
|
||||||
this.type = MessageComponentTypes.Button;
|
|
||||||
}
|
|
||||||
#data: DiscordButtonComponent;
|
|
||||||
type: MessageComponentTypes.Button;
|
|
||||||
|
|
||||||
setStyle(style: ButtonStyles): this {
|
|
||||||
this.#data.style = style;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLabel(label: string): this {
|
|
||||||
this.#data.label = label;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCustomId(id: string): this {
|
|
||||||
this.#data.custom_id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setEmoji(emoji: ComponentEmoji): this {
|
|
||||||
this.#data.emoji = emoji;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDisabled(disabled = true): this {
|
|
||||||
this.#data.disabled = disabled;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setURL(url: string): this {
|
|
||||||
this.#data.url = url;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): DiscordButtonComponent {
|
|
||||||
return { ...this.#data, type: this.type };
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
import { type DiscordSelectMenuComponent, MessageComponentTypes } from '../../../../discordeno/mod.ts';
|
|
||||||
import type { SelectMenuOptionBuilder } from './SelectMenuOptionBuilder.ts';
|
|
||||||
|
|
||||||
export class SelectMenuBuilder {
|
|
||||||
constructor() {
|
|
||||||
this.#data = {} as DiscordSelectMenuComponent;
|
|
||||||
this.type = MessageComponentTypes.SelectMenu;
|
|
||||||
this.options = [];
|
|
||||||
}
|
|
||||||
#data: DiscordSelectMenuComponent;
|
|
||||||
type: MessageComponentTypes.SelectMenu;
|
|
||||||
options: SelectMenuOptionBuilder[];
|
|
||||||
|
|
||||||
setPlaceholder(placeholder: string): this {
|
|
||||||
this.#data.placeholder = placeholder;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setValues(max?: number, min?: number): this {
|
|
||||||
this.#data.max_values = max;
|
|
||||||
this.#data.min_values = min;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDisabled(disabled = true): this {
|
|
||||||
this.#data.disabled = disabled;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCustomId(id: string): this {
|
|
||||||
this.#data.custom_id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOptions(...options: SelectMenuOptionBuilder[]): this {
|
|
||||||
this.options.splice(
|
|
||||||
0,
|
|
||||||
this.options.length,
|
|
||||||
...options,
|
|
||||||
);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addOptions(...options: SelectMenuOptionBuilder[]): this {
|
|
||||||
this.options.push(
|
|
||||||
...options,
|
|
||||||
);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): DiscordSelectMenuComponent {
|
|
||||||
return { ...this.#data, type: this.type, options: this.options.map((option) => option.toJSON()) };
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
import type { DiscordSelectOption } from '../../../../discordeno/mod.ts';
|
|
||||||
import type { ComponentEmoji } from '../../../Util.ts';
|
|
||||||
|
|
||||||
export class SelectMenuOptionBuilder {
|
|
||||||
constructor() {
|
|
||||||
this.#data = {} as DiscordSelectOption;
|
|
||||||
}
|
|
||||||
#data: DiscordSelectOption;
|
|
||||||
|
|
||||||
setLabel(label: string): SelectMenuOptionBuilder {
|
|
||||||
this.#data.label = label;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(value: string): SelectMenuOptionBuilder {
|
|
||||||
this.#data.value = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDescription(description: string): SelectMenuOptionBuilder {
|
|
||||||
this.#data.description = description;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefault(Default = true): SelectMenuOptionBuilder {
|
|
||||||
this.#data.default = Default;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setEmoji(emoji: ComponentEmoji): SelectMenuOptionBuilder {
|
|
||||||
this.#data.emoji = emoji;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): DiscordSelectOption {
|
|
||||||
return { ...this.#data };
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
import type { Localization, PermissionStrings } from '../../../../discordeno/mod.ts';
|
|
||||||
import { ApplicationCommandTypes } from '../../../../discordeno/mod.ts';
|
|
||||||
import { OptionBased } from './ApplicationCommandOption.ts';
|
|
||||||
import { CreateApplicationCommand } from '../../../Session.ts';
|
|
||||||
|
|
||||||
export abstract class ApplicationCommandBuilder implements CreateApplicationCommand {
|
|
||||||
constructor(
|
|
||||||
type: ApplicationCommandTypes = ApplicationCommandTypes.ChatInput,
|
|
||||||
name: string = '',
|
|
||||||
description: string = '',
|
|
||||||
defaultMemberPermissions?: PermissionStrings[],
|
|
||||||
nameLocalizations?: Localization,
|
|
||||||
descriptionLocalizations?: Localization,
|
|
||||||
dmPermission: boolean = true,
|
|
||||||
) {
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.description = description;
|
|
||||||
this.defaultMemberPermissions = defaultMemberPermissions;
|
|
||||||
this.nameLocalizations = nameLocalizations;
|
|
||||||
this.descriptionLocalizations = descriptionLocalizations;
|
|
||||||
this.dmPermission = dmPermission;
|
|
||||||
}
|
|
||||||
type: ApplicationCommandTypes;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
defaultMemberPermissions?: PermissionStrings[];
|
|
||||||
nameLocalizations?: Localization;
|
|
||||||
descriptionLocalizations?: Localization;
|
|
||||||
dmPermission: boolean;
|
|
||||||
|
|
||||||
setType(type: ApplicationCommandTypes): this {
|
|
||||||
return (this.type = type), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setName(name: string): this {
|
|
||||||
return (this.name = name), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDescription(description: string): this {
|
|
||||||
return (this.description = description), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefaultMemberPermission(perm: PermissionStrings[]): this {
|
|
||||||
return (this.defaultMemberPermissions = perm), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setNameLocalizations(l: Localization): this {
|
|
||||||
return (this.nameLocalizations = l), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDescriptionLocalizations(l: Localization): this {
|
|
||||||
return (this.descriptionLocalizations = l), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDmPermission(perm: boolean): this {
|
|
||||||
return (this.dmPermission = perm), this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MessageApplicationCommandBuilderJSON = { name: string; type: ApplicationCommandTypes.Message };
|
|
||||||
|
|
||||||
export class MessageApplicationCommandBuilder {
|
|
||||||
type: ApplicationCommandTypes;
|
|
||||||
name?: string;
|
|
||||||
constructor(
|
|
||||||
type?: ApplicationCommandTypes,
|
|
||||||
name?: string,
|
|
||||||
) {
|
|
||||||
this.type = type ?? ApplicationCommandTypes.Message;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
setName(name: string): this {
|
|
||||||
return (this.name = name), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): MessageApplicationCommandBuilderJSON {
|
|
||||||
if (!this.name) throw new TypeError('Propety \'name\' is required');
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: ApplicationCommandTypes.Message,
|
|
||||||
name: this.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChatInputApplicationCommandBuilder extends ApplicationCommandBuilder {
|
|
||||||
type: ApplicationCommandTypes.ChatInput = ApplicationCommandTypes.ChatInput;
|
|
||||||
|
|
||||||
toJSON(): CreateApplicationCommand {
|
|
||||||
if (!this.type) throw new TypeError('Propety \'type\' is required');
|
|
||||||
if (!this.name) throw new TypeError('Propety \'name\' is required');
|
|
||||||
if (!this.description) {
|
|
||||||
throw new TypeError('Propety \'description\' is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: ApplicationCommandTypes.ChatInput,
|
|
||||||
name: this.name,
|
|
||||||
description: this.description,
|
|
||||||
options: this.options?.map((o) => o.toJSON()) ?? [],
|
|
||||||
defaultMemberPermissions: this.defaultMemberPermissions,
|
|
||||||
nameLocalizations: this.nameLocalizations,
|
|
||||||
descriptionLocalizations: this.descriptionLocalizations,
|
|
||||||
dmPermission: this.dmPermission,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OptionBased.applyTo(ChatInputApplicationCommandBuilder);
|
|
||||||
|
|
||||||
export interface ChatInputApplicationCommandBuilder extends ApplicationCommandBuilder, OptionBased {
|
|
||||||
// pass
|
|
||||||
}
|
|
@ -1,346 +0,0 @@
|
|||||||
import { ApplicationCommandOptionTypes, type ChannelTypes, type Localization } from '../../../../discordeno/mod.ts';
|
|
||||||
import { ApplicationCommandOptionChoice } from '../../interactions/BaseInteraction.ts';
|
|
||||||
|
|
||||||
export class ChoiceBuilder {
|
|
||||||
name?: string;
|
|
||||||
value?: string;
|
|
||||||
|
|
||||||
setName(name: string): ChoiceBuilder {
|
|
||||||
this.name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(value: string): this {
|
|
||||||
this.value = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): ApplicationCommandOptionChoice {
|
|
||||||
if (!this.name) throw new TypeError('Property \'name\' is required');
|
|
||||||
if (!this.value) throw new TypeError('Property \'value\' is required');
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: this.name,
|
|
||||||
value: this.value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OptionBuilder {
|
|
||||||
required?: boolean;
|
|
||||||
autocomplete?: boolean;
|
|
||||||
type?: ApplicationCommandOptionTypes;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
constructor(type?: ApplicationCommandOptionTypes, name?: string, description?: string) {
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
setType(type: ApplicationCommandOptionTypes): this {
|
|
||||||
return (this.type = type), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setName(name: string): this {
|
|
||||||
return (this.name = name), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDescription(description: string): this {
|
|
||||||
return (this.description = description), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setRequired(required: boolean): this {
|
|
||||||
return (this.required = required), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): ApplicationCommandOption {
|
|
||||||
if (!this.type) throw new TypeError('Property \'type\' is required');
|
|
||||||
if (!this.name) throw new TypeError('Property \'name\' is required');
|
|
||||||
if (!this.description) {
|
|
||||||
throw new TypeError('Property \'description\' is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const applicationCommandOption: ApplicationCommandOption = {
|
|
||||||
type: this.type,
|
|
||||||
name: this.name,
|
|
||||||
description: this.description,
|
|
||||||
required: this.required ? true : false,
|
|
||||||
};
|
|
||||||
|
|
||||||
return applicationCommandOption;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OptionBuilderLimitedValues extends OptionBuilder {
|
|
||||||
choices?: ChoiceBuilder[];
|
|
||||||
minValue?: number;
|
|
||||||
maxValue?: number;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
type?: ApplicationCommandOptionTypes.Integer | ApplicationCommandOptionTypes.Number,
|
|
||||||
name?: string,
|
|
||||||
description?: string,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMinValue(n: number): this {
|
|
||||||
return (this.minValue = n), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMaxValue(n: number): this {
|
|
||||||
return (this.maxValue = n), this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addChoice(fn: (choice: ChoiceBuilder) => ChoiceBuilder): this {
|
|
||||||
const choice = fn(new ChoiceBuilder());
|
|
||||||
this.choices ??= [];
|
|
||||||
this.choices.push(choice);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
override toJSON(): ApplicationCommandOption {
|
|
||||||
return {
|
|
||||||
...super.toJSON(),
|
|
||||||
choices: this.choices?.map((c) => c.toJSON()) ?? [],
|
|
||||||
minValue: this.minValue,
|
|
||||||
maxValue: this.maxValue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OptionBuilderString extends OptionBuilder {
|
|
||||||
choices?: ChoiceBuilder[];
|
|
||||||
constructor(
|
|
||||||
type?: ApplicationCommandOptionTypes.String,
|
|
||||||
name?: string,
|
|
||||||
description?: string,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.description = description;
|
|
||||||
this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addChoice(fn: (choice: ChoiceBuilder) => ChoiceBuilder): this {
|
|
||||||
const choice = fn(new ChoiceBuilder());
|
|
||||||
this.choices ??= [];
|
|
||||||
this.choices.push(choice);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
override toJSON(): ApplicationCommandOption {
|
|
||||||
return {
|
|
||||||
...super.toJSON(),
|
|
||||||
choices: this.choices?.map((c) => c.toJSON()) ?? [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OptionBuilderChannel extends OptionBuilder {
|
|
||||||
channelTypes?: ChannelTypes[];
|
|
||||||
constructor(
|
|
||||||
type?: ApplicationCommandOptionTypes.Channel,
|
|
||||||
name?: string,
|
|
||||||
description?: string,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.description = description;
|
|
||||||
this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addChannelTypes(...channels: ChannelTypes[]): this {
|
|
||||||
this.channelTypes ??= [];
|
|
||||||
this.channelTypes.push(...channels);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
override toJSON(): ApplicationCommandOption {
|
|
||||||
return {
|
|
||||||
...super.toJSON(),
|
|
||||||
channelTypes: this.channelTypes ?? [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OptionBuilderLike {
|
|
||||||
toJSON(): ApplicationCommandOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OptionBased {
|
|
||||||
options?:
|
|
||||||
& (
|
|
||||||
| OptionBuilder[]
|
|
||||||
| OptionBuilderString[]
|
|
||||||
| OptionBuilderLimitedValues[]
|
|
||||||
| OptionBuilderNested[]
|
|
||||||
| OptionBuilderChannel[]
|
|
||||||
)
|
|
||||||
& OptionBuilderLike[];
|
|
||||||
|
|
||||||
addOption(fn: (option: OptionBuilder) => OptionBuilder, type?: ApplicationCommandOptionTypes): this {
|
|
||||||
const option = fn(new OptionBuilder(type));
|
|
||||||
this.options ??= [];
|
|
||||||
this.options.push(option);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addNestedOption(fn: (option: OptionBuilder) => OptionBuilder): this {
|
|
||||||
const option = fn(new OptionBuilder(ApplicationCommandOptionTypes.SubCommand));
|
|
||||||
this.options ??= [];
|
|
||||||
this.options.push(option);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addStringOption(fn: (option: OptionBuilderString) => OptionBuilderString): this {
|
|
||||||
const option = fn(new OptionBuilderString(ApplicationCommandOptionTypes.String));
|
|
||||||
this.options ??= [];
|
|
||||||
this.options.push(option);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addIntegerOption(fn: (option: OptionBuilderLimitedValues) => OptionBuilderLimitedValues): this {
|
|
||||||
const option = fn(new OptionBuilderLimitedValues(ApplicationCommandOptionTypes.Integer));
|
|
||||||
this.options ??= [];
|
|
||||||
this.options.push(option);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addNumberOption(fn: (option: OptionBuilderLimitedValues) => OptionBuilderLimitedValues): this {
|
|
||||||
const option = fn(new OptionBuilderLimitedValues(ApplicationCommandOptionTypes.Number));
|
|
||||||
this.options ??= [];
|
|
||||||
this.options.push(option);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addBooleanOption(fn: (option: OptionBuilder) => OptionBuilder): this {
|
|
||||||
return this.addOption(fn, ApplicationCommandOptionTypes.Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
addSubCommand(fn: (option: OptionBuilderNested) => OptionBuilderNested): this {
|
|
||||||
const option = fn(new OptionBuilderNested(ApplicationCommandOptionTypes.SubCommand));
|
|
||||||
this.options ??= [];
|
|
||||||
this.options.push(option);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addSubCommandGroup(fn: (option: OptionBuilderNested) => OptionBuilderNested): this {
|
|
||||||
const option = fn(new OptionBuilderNested(ApplicationCommandOptionTypes.SubCommandGroup));
|
|
||||||
this.options ??= [];
|
|
||||||
this.options.push(option);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addUserOption(fn: (option: OptionBuilder) => OptionBuilder): this {
|
|
||||||
return this.addOption(fn, ApplicationCommandOptionTypes.User);
|
|
||||||
}
|
|
||||||
|
|
||||||
addChannelOption(fn: (option: OptionBuilderChannel) => OptionBuilderChannel): this {
|
|
||||||
const option = fn(new OptionBuilderChannel(ApplicationCommandOptionTypes.Channel));
|
|
||||||
this.options ??= [];
|
|
||||||
this.options.push(option);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
addRoleOption(fn: (option: OptionBuilder) => OptionBuilder): this {
|
|
||||||
return this.addOption(fn, ApplicationCommandOptionTypes.Role);
|
|
||||||
}
|
|
||||||
|
|
||||||
addMentionableOption(fn: (option: OptionBuilder) => OptionBuilder): this {
|
|
||||||
return this.addOption(fn, ApplicationCommandOptionTypes.Mentionable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore ban-types
|
|
||||||
static applyTo(klass: Function, ignore: Array<keyof OptionBased> = []): void {
|
|
||||||
const methods: Array<keyof OptionBased> = [
|
|
||||||
'addOption',
|
|
||||||
'addNestedOption',
|
|
||||||
'addStringOption',
|
|
||||||
'addIntegerOption',
|
|
||||||
'addNumberOption',
|
|
||||||
'addBooleanOption',
|
|
||||||
'addSubCommand',
|
|
||||||
'addSubCommandGroup',
|
|
||||||
'addUserOption',
|
|
||||||
'addChannelOption',
|
|
||||||
'addRoleOption',
|
|
||||||
'addMentionableOption',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const method of methods) {
|
|
||||||
if (ignore.includes(method)) continue;
|
|
||||||
|
|
||||||
klass.prototype[method] = OptionBased.prototype[method];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OptionBuilderNested extends OptionBuilder {
|
|
||||||
constructor(
|
|
||||||
type?: ApplicationCommandOptionTypes.SubCommand | ApplicationCommandOptionTypes.SubCommandGroup,
|
|
||||||
name?: string,
|
|
||||||
description?: string,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
override toJSON(): ApplicationCommandOption {
|
|
||||||
if (!this.type) throw new TypeError('Property \'type\' is required');
|
|
||||||
if (!this.name) throw new TypeError('Property \'name\' is required');
|
|
||||||
if (!this.description) {
|
|
||||||
throw new TypeError('Property \'description\' is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: this.type,
|
|
||||||
name: this.name,
|
|
||||||
description: this.description,
|
|
||||||
options: this.options?.map((o) => o.toJSON()) ?? [],
|
|
||||||
required: this.required ? true : false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OptionBased.applyTo(OptionBuilderNested);
|
|
||||||
|
|
||||||
export interface OptionBuilderNested extends OptionBuilder, OptionBased {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApplicationCommandOption {
|
|
||||||
/** Value of Application Command Option Type */
|
|
||||||
type: ApplicationCommandOptionTypes;
|
|
||||||
/** 1-32 character name matching lowercase `^[\w-]{1,32}$` */
|
|
||||||
name: string;
|
|
||||||
/** Localization object for the `name` field. Values follow the same restrictions as `name` */
|
|
||||||
nameLocalizations?: Localization;
|
|
||||||
/** 1-100 character description */
|
|
||||||
description: string;
|
|
||||||
/** Localization object for the `description` field. Values follow the same restrictions as `description` */
|
|
||||||
descriptionLocalizations?: Localization;
|
|
||||||
/** If the parameter is required or optional--default `false` */
|
|
||||||
required?: boolean;
|
|
||||||
/** Choices for `string` and `int` types for the user to pick from */
|
|
||||||
choices?: ApplicationCommandOptionChoice[];
|
|
||||||
/** If the option is a subcommand or subcommand group type, this nested options will be the parameters */
|
|
||||||
options?: ApplicationCommandOption[];
|
|
||||||
/** if autocomplete interactions are enabled for this `String`, `Integer`, or `Number` type option */
|
|
||||||
autocomplete?: boolean;
|
|
||||||
/** If the option is a channel type, the channels shown will be restricted to these types */
|
|
||||||
channelTypes?: ChannelTypes[];
|
|
||||||
/** Minimum number desired. */
|
|
||||||
minValue?: number;
|
|
||||||
/** Maximum number desired. */
|
|
||||||
maxValue?: number;
|
|
||||||
}
|
|
@ -1,931 +0,0 @@
|
|||||||
/** Types */
|
|
||||||
import type { Model } from './Base.ts';
|
|
||||||
import type { Snowflake } from '../Snowflake.ts';
|
|
||||||
import type { Session } from '../Session.ts';
|
|
||||||
import type { PermissionsOverwrites } from '../Util.ts';
|
|
||||||
|
|
||||||
/** External from vendor */
|
|
||||||
import {
|
|
||||||
ChannelTypes,
|
|
||||||
DiscordChannel,
|
|
||||||
DiscordInvite,
|
|
||||||
DiscordInviteMetadata,
|
|
||||||
DiscordListArchivedThreads,
|
|
||||||
DiscordMessage,
|
|
||||||
DiscordOverwrite,
|
|
||||||
DiscordThreadMember,
|
|
||||||
DiscordWebhook,
|
|
||||||
GatewayOpcodes,
|
|
||||||
TargetTypes,
|
|
||||||
VideoQualityModes,
|
|
||||||
} from '../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
/** Functions and others */
|
|
||||||
import { calculateShardId } from '../../discordeno/gateway/calculateShardId.ts';
|
|
||||||
import { urlToBase64 } from '../util/urlToBase64.ts';
|
|
||||||
|
|
||||||
/** Classes and routes */
|
|
||||||
import * as Routes from '../Routes.ts';
|
|
||||||
import Message, { CreateMessage, EditMessage, EmojiResolvable } from './Message.ts';
|
|
||||||
import Invite from './Invite.ts';
|
|
||||||
import Webhook from './Webhook.ts';
|
|
||||||
import User from './User.ts';
|
|
||||||
import ThreadMember from './ThreadMember.ts';
|
|
||||||
import Permissions from './Permissions.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class that represents the base for creating a new channel.
|
|
||||||
*/
|
|
||||||
export abstract class BaseChannel implements Model {
|
|
||||||
constructor(session: Session, data: DiscordChannel) {
|
|
||||||
this.id = data.id;
|
|
||||||
this.session = session;
|
|
||||||
this.name = data.name;
|
|
||||||
this.type = data.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** id's refers to the identification of the channel */
|
|
||||||
readonly id: Snowflake;
|
|
||||||
|
|
||||||
/** The session that instantiated the channel */
|
|
||||||
readonly session: Session;
|
|
||||||
|
|
||||||
/** Channel name defined by the entity */
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
/** Refers to the possible channel type implemented (Guild, DM, Voice, News, etc...) */
|
|
||||||
type: ChannelTypes;
|
|
||||||
|
|
||||||
/** If the channel is a TextChannel */
|
|
||||||
isText(): this is TextChannel {
|
|
||||||
return textBasedChannels.includes(this.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If the channel is a VoiceChannel */
|
|
||||||
isVoice(): this is VoiceChannel {
|
|
||||||
return this.type === ChannelTypes.GuildVoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If the channel is a DMChannel */
|
|
||||||
isDM(): this is DMChannel {
|
|
||||||
return this.type === ChannelTypes.DM;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If the channel is a NewChannel */
|
|
||||||
isNews(): this is NewsChannel {
|
|
||||||
return this.type === ChannelTypes.GuildNews;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If the channel is a ThreadChannel */
|
|
||||||
isThread(): this is ThreadChannel {
|
|
||||||
return this.type === ChannelTypes.GuildPublicThread || this.type === ChannelTypes.GuildPrivateThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If the channel is a StageChannel */
|
|
||||||
isStage(): this is StageChannel {
|
|
||||||
return this.type === ChannelTypes.GuildStageVoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return `<#${this.id}>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a category channel.
|
|
||||||
*/
|
|
||||||
export class CategoryChannel extends BaseChannel {
|
|
||||||
constructor(session: Session, data: DiscordChannel) {
|
|
||||||
super(session, data);
|
|
||||||
this.id = data.id;
|
|
||||||
this.name = data.name ? data.name : '';
|
|
||||||
this.nsfw = data.nsfw ? data.nsfw : false;
|
|
||||||
this.guildId = data.guild_id ? data.guild_id : undefined;
|
|
||||||
this.type = ChannelTypes.GuildCategory;
|
|
||||||
this.position = data.position ? data.position : undefined;
|
|
||||||
this.parentId = data.parent_id ? data.parent_id : undefined;
|
|
||||||
|
|
||||||
this.permissionOverwrites = data.permission_overwrites
|
|
||||||
? ChannelFactory.permissionOverwrites(data.permission_overwrites)
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
id: Snowflake;
|
|
||||||
parentId?: string;
|
|
||||||
name: string;
|
|
||||||
permissionOverwrites: PermissionsOverwrites[];
|
|
||||||
nsfw: boolean;
|
|
||||||
guildId?: Snowflake;
|
|
||||||
position?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** TextChannel */
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#create-channel-invite-json-params
|
|
||||||
* Represents the options object to create an invitation
|
|
||||||
*/
|
|
||||||
export interface DiscordInviteOptions {
|
|
||||||
/** duration of invite in seconds before expiry, or 0 for never. between 0 and 604800 (7 days) */
|
|
||||||
maxAge?: number;
|
|
||||||
/** max number of uses or 0 for unlimited. between 0 and 100 */
|
|
||||||
maxUses?: number;
|
|
||||||
/** if the invitation is unique. If it's true, don't try to reuse a similar invite (useful for creating many unique one time use invites) */
|
|
||||||
unique?: boolean;
|
|
||||||
/** whether this invite only grants temporary membership */
|
|
||||||
temporary: boolean;
|
|
||||||
reason?: string;
|
|
||||||
/** the type of target for this voice channel invite */
|
|
||||||
targetType?: TargetTypes;
|
|
||||||
/** the id of the user whose stream to display for this invite, required if targetType is 1, the user must be streaming in the channel */
|
|
||||||
targetUserId?: Snowflake;
|
|
||||||
/** the id of the embedded application to open for this invite, required if targetType is 2, the application must have the EMBEDDED flag */
|
|
||||||
targetApplicationId?: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Webhook create object */
|
|
||||||
export interface CreateWebhook {
|
|
||||||
/** name of the webhook (1-80 characters) */
|
|
||||||
name: string;
|
|
||||||
/** image for the default webhook avatar */
|
|
||||||
avatar?: string;
|
|
||||||
reason?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Available text-channel-types list */
|
|
||||||
export const textBasedChannels: ChannelTypes[] = [
|
|
||||||
ChannelTypes.DM,
|
|
||||||
ChannelTypes.GroupDm,
|
|
||||||
ChannelTypes.GuildPrivateThread,
|
|
||||||
ChannelTypes.GuildPublicThread,
|
|
||||||
ChannelTypes.GuildNews,
|
|
||||||
ChannelTypes.GuildVoice,
|
|
||||||
ChannelTypes.GuildText,
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Available text-channel-types */
|
|
||||||
export type TextBasedChannels =
|
|
||||||
| ChannelTypes.DM
|
|
||||||
| ChannelTypes.GroupDm
|
|
||||||
| ChannelTypes.GuildPrivateThread
|
|
||||||
| ChannelTypes.GuildPublicThread
|
|
||||||
| ChannelTypes.GuildNews
|
|
||||||
| ChannelTypes.GuildVoice
|
|
||||||
| ChannelTypes.GuildText;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a text channel.
|
|
||||||
*/
|
|
||||||
export class TextChannel {
|
|
||||||
constructor(session: Session, data: DiscordChannel) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.name = data.name;
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.rateLimitPerUser = data.rate_limit_per_user ?? 0;
|
|
||||||
this.nsfw = !!data.nsfw ?? false;
|
|
||||||
|
|
||||||
if (data.last_message_id) {
|
|
||||||
this.lastMessageId = data.last_message_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.last_pin_timestamp) {
|
|
||||||
this.lastPinTimestamp = data.last_pin_timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The session that instantiated the channel */
|
|
||||||
readonly session: Session;
|
|
||||||
/** id's refers to the identification of the channel */
|
|
||||||
readonly id: Snowflake;
|
|
||||||
/** Current channel name */
|
|
||||||
name?: string;
|
|
||||||
/** The type of the channel */
|
|
||||||
type: TextBasedChannels;
|
|
||||||
/** The id of the last message sent in this channel (or thread for GUILD_FORUM channels) (may not point to an existing or valid message or thread) */
|
|
||||||
lastMessageId?: Snowflake;
|
|
||||||
/** When the last pinned message was pinned. This may be undefined in events such as GUILD_CREATE when a message is not pinned. */
|
|
||||||
lastPinTimestamp?: string;
|
|
||||||
/** Amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages or manage_channel, are unaffected */
|
|
||||||
rateLimitPerUser: number;
|
|
||||||
/** If the channel is NSFW (Not-Safe-For-Work content) */
|
|
||||||
nsfw: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mixin
|
|
||||||
*/
|
|
||||||
// deno-lint-ignore ban-types
|
|
||||||
static applyTo(klass: Function, ignore: Array<keyof TextChannel> = []): void {
|
|
||||||
const methods: Array<keyof TextChannel> = [
|
|
||||||
'fetchPins',
|
|
||||||
'createInvite',
|
|
||||||
'fetchMessages',
|
|
||||||
'sendTyping',
|
|
||||||
'pinMessage',
|
|
||||||
'unpinMessage',
|
|
||||||
'addReaction',
|
|
||||||
'removeReaction',
|
|
||||||
'nukeReactions',
|
|
||||||
'fetchPins',
|
|
||||||
'sendMessage',
|
|
||||||
'editMessage',
|
|
||||||
'createWebhook',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const method of methods) {
|
|
||||||
if (ignore.includes(method)) continue;
|
|
||||||
|
|
||||||
klass.prototype[method] = TextChannel.prototype[method];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fetchPins makes an asynchronous request and gets the current channel pins.
|
|
||||||
* @returns A promise that resolves with an array of Message objects.
|
|
||||||
*/
|
|
||||||
async fetchPins(): Promise<Message[] | []> {
|
|
||||||
const messages = await this.session.rest.runMethod<DiscordMessage[]>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.CHANNEL_PINS(this.id),
|
|
||||||
);
|
|
||||||
return messages[0] ? messages.map((x: DiscordMessage) => new Message(this.session, x)) : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* createInvite makes an asynchronous request to create a new invitation.
|
|
||||||
* @param options - The options to create the invitation
|
|
||||||
* @returns The created invite
|
|
||||||
*/
|
|
||||||
async createInvite(options?: DiscordInviteOptions): Promise<Invite> {
|
|
||||||
const invite = await this.session.rest.runMethod<DiscordInvite>(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
Routes.CHANNEL_INVITES(this.id),
|
|
||||||
options
|
|
||||||
? {
|
|
||||||
max_age: options.maxAge,
|
|
||||||
max_uses: options.maxUses,
|
|
||||||
temporary: options.temporary,
|
|
||||||
unique: options.unique,
|
|
||||||
target_type: options.targetType,
|
|
||||||
target_user_id: options.targetUserId,
|
|
||||||
target_application_id: options.targetApplicationId,
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Invite(this.session, invite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fetchMessages makes an asynchronous request and gets the channel messages
|
|
||||||
* @param options - The options to get the messages
|
|
||||||
* @returns The messages
|
|
||||||
*/
|
|
||||||
async fetchMessages(options?: Routes.GetMessagesOptions): Promise<Message[] | []> {
|
|
||||||
if (options?.limit! > 100) throw Error('Values must be between 0-100');
|
|
||||||
const messages = await this.session.rest.runMethod<DiscordMessage[]>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.CHANNEL_MESSAGES(this.id, options),
|
|
||||||
);
|
|
||||||
return messages[0] ? messages.map((x) => new Message(this.session, x)) : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** sendTyping sends a typing POST request */
|
|
||||||
async sendTyping(): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
Routes.CHANNEL_TYPING(this.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pinMessage pins a channel message.
|
|
||||||
* Same as Message.pin().
|
|
||||||
* @param messageId - The id of the message to pin
|
|
||||||
* @returns The promise that resolves when the request is complete
|
|
||||||
*/
|
|
||||||
async pinMessage(messageId: Snowflake): Promise<void> {
|
|
||||||
await Message.prototype.pin.call({ id: messageId, channelId: this.id, session: this.session });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* unpinMessage unpin a channel message.
|
|
||||||
* Same as Message.unpin()
|
|
||||||
* @param messageId - The id of the message to unpin
|
|
||||||
* @returns The promise of the request
|
|
||||||
*/
|
|
||||||
async unpinMessage(messageId: Snowflake): Promise<void> {
|
|
||||||
await Message.prototype.unpin.call({ id: messageId, channelId: this.id, session: this.session });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* addReaction adds a reaction to the message.
|
|
||||||
* Same as Message.addReaction().
|
|
||||||
* @param messageId - The message to add the reaction to
|
|
||||||
* @param reaction - The reaction to add
|
|
||||||
* @returns The promise of the request
|
|
||||||
*/
|
|
||||||
async addReaction(messageId: Snowflake, reaction: EmojiResolvable): Promise<void> {
|
|
||||||
await Message.prototype.addReaction.call(
|
|
||||||
{ channelId: this.id, id: messageId, session: this.session },
|
|
||||||
reaction,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* removeReaction removes a reaction from the message.
|
|
||||||
* Same as Message.removeReaction().
|
|
||||||
* @param messageId - The id of the message to remove the reaction from
|
|
||||||
* @param reaction - The reaction to remove
|
|
||||||
* @param options - The user to remove the reaction from
|
|
||||||
*/
|
|
||||||
async removeReaction(
|
|
||||||
messageId: Snowflake,
|
|
||||||
reaction: EmojiResolvable,
|
|
||||||
options?: { userId: Snowflake },
|
|
||||||
): Promise<void> {
|
|
||||||
await Message.prototype.removeReaction.call(
|
|
||||||
{ channelId: this.id, id: messageId, session: this.session },
|
|
||||||
reaction,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* removeReactionEmoji removes an emoji reaction from the messageId provided.
|
|
||||||
* Same as Message.removeReactionEmoji().
|
|
||||||
* @param messageId - The message id to remove the reaction from.
|
|
||||||
* @param emoji - The emoji to remove.
|
|
||||||
* @param userId - The user id to remove the reaction from.
|
|
||||||
*/
|
|
||||||
async removeReactionEmoji(messageId: Snowflake, reaction: EmojiResolvable): Promise<void> {
|
|
||||||
await Message.prototype.removeReactionEmoji.call(
|
|
||||||
{ channelId: this.id, id: messageId, session: this.session },
|
|
||||||
reaction,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** nukeReactions nukes every reaction on the message.
|
|
||||||
* Same as Message.nukeReactions().
|
|
||||||
* @param messageId The message id to nuke reactions from.
|
|
||||||
* @returns A promise that resolves when the reactions are nuked.
|
|
||||||
*/
|
|
||||||
async nukeReactions(messageId: Snowflake): Promise<void> {
|
|
||||||
await Message.prototype.nukeReactions.call({ channelId: this.id, id: messageId });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fetchReactions gets the users who reacted with this emoji on the message.
|
|
||||||
* Same as Message.fetchReactions().
|
|
||||||
* @param messageId - The message id to get the reactions from.
|
|
||||||
* @param reaction - The emoji to get the reactions from.
|
|
||||||
* @param options - The options to get the reactions with.
|
|
||||||
* @returns The users who reacted with this emoji on the message.
|
|
||||||
*/
|
|
||||||
async fetchReactions(
|
|
||||||
messageId: Snowflake,
|
|
||||||
reaction: EmojiResolvable,
|
|
||||||
options?: Routes.GetReactions,
|
|
||||||
): Promise<User[]> {
|
|
||||||
const users = await Message.prototype.fetchReactions.call(
|
|
||||||
{ channelId: this.id, id: messageId, session: this.session },
|
|
||||||
reaction,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sendMessage sends a message to the channel.
|
|
||||||
* Same as Message.reply().
|
|
||||||
* @param options - Options for a new message.
|
|
||||||
* @returns The sent message.
|
|
||||||
*/
|
|
||||||
sendMessage(options: CreateMessage): Promise<Message> {
|
|
||||||
return Message.prototype.reply.call({ channelId: this.id, session: this.session }, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* editMessage edits a message.
|
|
||||||
* Same as Message.edit().
|
|
||||||
* @param messageId - Message ID.
|
|
||||||
* @param options - Options for edit a message.
|
|
||||||
* @returns The edited message.
|
|
||||||
*/
|
|
||||||
editMessage(messageId: Snowflake, options: EditMessage): Promise<Message> {
|
|
||||||
return Message.prototype.edit.call({ channelId: this.id, id: messageId, session: this.session }, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* createWebhook creates a webhook.
|
|
||||||
* @param options - Options for a new webhook.
|
|
||||||
* @returns The created webhook.
|
|
||||||
*/
|
|
||||||
async createWebhook(options: CreateWebhook): Promise<Webhook> {
|
|
||||||
const webhook = await this.session.rest.runMethod<DiscordWebhook>(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
Routes.CHANNEL_WEBHOOKS(this.id),
|
|
||||||
{
|
|
||||||
name: options.name,
|
|
||||||
avatar: options.avatar ? urlToBase64(options.avatar) : undefined,
|
|
||||||
reason: options.reason,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Webhook(this.session, webhook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GuildChannel */
|
|
||||||
/**
|
|
||||||
* Represent the options object to create a thread channel
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#start-thread-without-message
|
|
||||||
*/
|
|
||||||
export interface ThreadCreateOptions {
|
|
||||||
name: string;
|
|
||||||
autoArchiveDuration?: 60 | 1440 | 4320 | 10080;
|
|
||||||
type: 10 | 11 | 12;
|
|
||||||
invitable?: boolean;
|
|
||||||
rateLimitPerUser?: number;
|
|
||||||
reason?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representations of the objects to edit a guild channel
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#modify-channel-json-params-guild-channel
|
|
||||||
*/
|
|
||||||
export interface EditGuildChannelOptions {
|
|
||||||
name?: string;
|
|
||||||
position?: number;
|
|
||||||
permissionOverwrites?: PermissionsOverwrites[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EditNewsChannelOptions extends EditGuildChannelOptions {
|
|
||||||
type?: ChannelTypes.GuildNews | ChannelTypes.GuildText;
|
|
||||||
topic?: string | null;
|
|
||||||
nfsw?: boolean | null;
|
|
||||||
parentId?: Snowflake | null;
|
|
||||||
defaultAutoArchiveDuration?: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EditGuildTextChannelOptions extends EditNewsChannelOptions {
|
|
||||||
rateLimitPerUser?: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EditStageChannelOptions extends EditGuildChannelOptions {
|
|
||||||
bitrate?: number | null;
|
|
||||||
rtcRegion?: Snowflake | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EditVoiceChannelOptions extends EditStageChannelOptions {
|
|
||||||
nsfw?: boolean | null;
|
|
||||||
userLimit?: number | null;
|
|
||||||
parentId?: Snowflake | null;
|
|
||||||
videoQualityMode?: VideoQualityModes | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the option object to create a thread channel from a message
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#start-thread-from-message
|
|
||||||
*/
|
|
||||||
export interface ThreadCreateOptions {
|
|
||||||
name: string;
|
|
||||||
autoArchiveDuration?: 60 | 1440 | 4320 | 10080;
|
|
||||||
rateLimitPerUser?: number;
|
|
||||||
messageId: Snowflake;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/resources/channel#list-public-archived-threads-response-body
|
|
||||||
*/
|
|
||||||
export interface ReturnThreadsArchive {
|
|
||||||
threads: Record<Snowflake, ThreadChannel>;
|
|
||||||
members: Record<Snowflake, ThreadMember>;
|
|
||||||
hasMore: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GuildChannel extends BaseChannel implements Model {
|
|
||||||
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
|
|
||||||
super(session, data);
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.guildId = guildId;
|
|
||||||
this.position = data.position;
|
|
||||||
data.topic ? this.topic = data.topic : null;
|
|
||||||
data.parent_id ? this.parentId = data.parent_id : undefined;
|
|
||||||
this.permissionOverwrites = data.permission_overwrites
|
|
||||||
? ChannelFactory.permissionOverwrites(data.permission_overwrites)
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: Exclude<ChannelTypes, ChannelTypes.DM | ChannelTypes.GroupDm>;
|
|
||||||
guildId: Snowflake;
|
|
||||||
topic?: string;
|
|
||||||
position?: number;
|
|
||||||
parentId?: Snowflake;
|
|
||||||
permissionOverwrites: PermissionsOverwrites[];
|
|
||||||
|
|
||||||
async fetchInvites(): Promise<Invite[]> {
|
|
||||||
const invites = await this.session.rest.runMethod<DiscordInviteMetadata[]>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.CHANNEL_INVITES(this.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
return invites.map((invite) => new Invite(this.session, invite));
|
|
||||||
}
|
|
||||||
|
|
||||||
async edit(options: EditNewsChannelOptions): Promise<NewsChannel>;
|
|
||||||
async edit(options: EditStageChannelOptions): Promise<StageChannel>;
|
|
||||||
async edit(options: EditVoiceChannelOptions): Promise<VoiceChannel>;
|
|
||||||
async edit(
|
|
||||||
options: EditGuildTextChannelOptions | EditNewsChannelOptions | EditVoiceChannelOptions,
|
|
||||||
): Promise<Channel> {
|
|
||||||
const channel = await this.session.rest.runMethod<DiscordChannel>(
|
|
||||||
this.session.rest,
|
|
||||||
'PATCH',
|
|
||||||
Routes.CHANNEL(this.id),
|
|
||||||
{
|
|
||||||
name: options.name,
|
|
||||||
type: 'type' in options ? options.type : undefined,
|
|
||||||
position: options.position,
|
|
||||||
topic: 'topic' in options ? options.topic : undefined,
|
|
||||||
nsfw: 'nfsw' in options ? options.nfsw : undefined,
|
|
||||||
rate_limit_per_user: 'rateLimitPerUser' in options ? options.rateLimitPerUser : undefined,
|
|
||||||
bitrate: 'bitrate' in options ? options.bitrate : undefined,
|
|
||||||
user_limit: 'userLimit' in options ? options.userLimit : undefined,
|
|
||||||
permissions_overwrites: options.permissionOverwrites,
|
|
||||||
parent_id: 'parentId' in options ? options.parentId : undefined,
|
|
||||||
rtc_region: 'rtcRegion' in options ? options.rtcRegion : undefined,
|
|
||||||
video_quality_mode: 'videoQualityMode' in options ? options.videoQualityMode : undefined,
|
|
||||||
default_auto_archive_duration: 'defaultAutoArchiveDuration' in options
|
|
||||||
? options.defaultAutoArchiveDuration
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return ChannelFactory.from(this.session, channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getArchivedThreads(
|
|
||||||
options: Routes.ListArchivedThreads & { type: 'public' | 'private' | 'privateJoinedThreads' },
|
|
||||||
): Promise<ReturnThreadsArchive> {
|
|
||||||
let func: (channelId: Snowflake, options: Routes.ListArchivedThreads) => string;
|
|
||||||
|
|
||||||
switch (options.type) {
|
|
||||||
case 'public':
|
|
||||||
func = Routes.THREAD_ARCHIVED_PUBLIC;
|
|
||||||
break;
|
|
||||||
case 'private':
|
|
||||||
func = Routes.THREAD_START_PRIVATE;
|
|
||||||
break;
|
|
||||||
case 'privateJoinedThreads':
|
|
||||||
func = Routes.THREAD_ARCHIVED_PRIVATE_JOINED;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { threads, members, has_more } = await this.session.rest.runMethod<DiscordListArchivedThreads>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
func(this.id, options),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
threads: Object.fromEntries(
|
|
||||||
threads.map((thread) => [thread.id, new ThreadChannel(this.session, thread, this.id)]),
|
|
||||||
) as Record<Snowflake, ThreadChannel>,
|
|
||||||
members: Object.fromEntries(
|
|
||||||
members.map((threadMember) => [threadMember.id, new ThreadMember(this.session, threadMember)]),
|
|
||||||
) as Record<Snowflake, ThreadMember>,
|
|
||||||
hasMore: has_more,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async createThread(options: ThreadCreateOptions): Promise<ThreadChannel> {
|
|
||||||
const thread = await this.session.rest.runMethod<DiscordChannel>(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
'messageId' in options
|
|
||||||
? Routes.THREAD_START_PUBLIC(this.id, options.messageId)
|
|
||||||
: Routes.THREAD_START_PRIVATE(this.id),
|
|
||||||
{
|
|
||||||
name: options.name,
|
|
||||||
auto_archive_duration: options.autoArchiveDuration,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return new ThreadChannel(this.session, thread, thread.guild_id ?? this.guildId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** BaseVoiceChannel */
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/topics/gateway#update-voice-state
|
|
||||||
*/
|
|
||||||
export interface UpdateVoiceState {
|
|
||||||
guildId: string;
|
|
||||||
channelId?: string;
|
|
||||||
selfMute: boolean;
|
|
||||||
selfDeaf: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class BaseVoiceChannel extends GuildChannel {
|
|
||||||
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
|
|
||||||
super(session, data, guildId);
|
|
||||||
this.bitRate = data.bitrate;
|
|
||||||
this.userLimit = data.user_limit ?? 0;
|
|
||||||
this.videoQuality = data.video_quality_mode;
|
|
||||||
this.nsfw = !!data.nsfw;
|
|
||||||
this.type = data.type as number;
|
|
||||||
|
|
||||||
if (data.rtc_region) {
|
|
||||||
this.rtcRegion = data.rtc_region;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override type: ChannelTypes.GuildVoice | ChannelTypes.GuildStageVoice;
|
|
||||||
bitRate?: number;
|
|
||||||
userLimit: number;
|
|
||||||
rtcRegion?: Snowflake;
|
|
||||||
|
|
||||||
videoQuality?: VideoQualityModes;
|
|
||||||
nsfw: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function was gathered from Discordeno it may not work
|
|
||||||
*/
|
|
||||||
async connect(options?: UpdateVoiceState): Promise<void> {
|
|
||||||
const shardId = calculateShardId(this.session.gateway, BigInt(super.guildId));
|
|
||||||
const shard = this.session.gateway.manager.shards.get(shardId);
|
|
||||||
|
|
||||||
if (!shard) {
|
|
||||||
throw new Error(`Shard (id: ${shardId} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await shard.send({
|
|
||||||
op: GatewayOpcodes.VoiceStateUpdate,
|
|
||||||
d: {
|
|
||||||
guild_id: super.guildId,
|
|
||||||
channel_id: super.id,
|
|
||||||
self_mute: Boolean(options?.selfMute),
|
|
||||||
self_deaf: options?.selfDeaf ?? true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** DMChannel */
|
|
||||||
export class DMChannel extends BaseChannel implements Model {
|
|
||||||
constructor(session: Session, data: DiscordChannel) {
|
|
||||||
super(session, data);
|
|
||||||
this.user = new User(this.session, data.recipents!.find((r) => r.id !== this.session.botId)!);
|
|
||||||
this.type = data.type as ChannelTypes.DM | ChannelTypes.GroupDm;
|
|
||||||
if (data.last_message_id) {
|
|
||||||
this.lastMessageId = data.last_message_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: ChannelTypes.DM | ChannelTypes.GroupDm;
|
|
||||||
user: User;
|
|
||||||
lastMessageId?: Snowflake;
|
|
||||||
|
|
||||||
async close(): Promise<DMChannel> {
|
|
||||||
const channel = await this.session.rest.runMethod<DiscordChannel>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
Routes.CHANNEL(this.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new DMChannel(this.session, channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DMChannel extends Omit<TextChannel, 'type'>, Omit<BaseChannel, 'type'> {}
|
|
||||||
|
|
||||||
TextChannel.applyTo(DMChannel);
|
|
||||||
|
|
||||||
/** VoiceChannel */
|
|
||||||
export class VoiceChannel extends BaseVoiceChannel {
|
|
||||||
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
|
|
||||||
super(session, data, guildId);
|
|
||||||
this.type = data.type as number;
|
|
||||||
}
|
|
||||||
override type: ChannelTypes.GuildVoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VoiceChannel extends TextChannel, BaseVoiceChannel {}
|
|
||||||
|
|
||||||
TextChannel.applyTo(VoiceChannel);
|
|
||||||
|
|
||||||
/** NewsChannel */
|
|
||||||
export class NewsChannel extends GuildChannel {
|
|
||||||
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
|
|
||||||
super(session, data, guildId);
|
|
||||||
this.type = data.type as ChannelTypes.GuildNews;
|
|
||||||
this.defaultAutoArchiveDuration = data.default_auto_archive_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: ChannelTypes.GuildNews;
|
|
||||||
defaultAutoArchiveDuration?: number;
|
|
||||||
|
|
||||||
crosspostMessage(messageId: Snowflake): Promise<Message> {
|
|
||||||
return Message.prototype.crosspost.call({ id: messageId, channelId: this.id, session: this.session });
|
|
||||||
}
|
|
||||||
|
|
||||||
get publishMessage() {
|
|
||||||
return this.crosspostMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextChannel.applyTo(NewsChannel);
|
|
||||||
|
|
||||||
export interface NewsChannel extends TextChannel, GuildChannel {}
|
|
||||||
|
|
||||||
/** StageChannel */
|
|
||||||
export class StageChannel extends BaseVoiceChannel {
|
|
||||||
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
|
|
||||||
super(session, data, guildId);
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.topic = data.topic ? data.topic : undefined;
|
|
||||||
}
|
|
||||||
override type: ChannelTypes.GuildStageVoice;
|
|
||||||
topic?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ThreadChannel */
|
|
||||||
export class ThreadChannel extends GuildChannel implements Model {
|
|
||||||
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
|
|
||||||
super(session, data, guildId);
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.archived = !!data.thread_metadata?.archived;
|
|
||||||
this.archiveTimestamp = data.thread_metadata?.archive_timestamp;
|
|
||||||
this.autoArchiveDuration = data.thread_metadata?.auto_archive_duration;
|
|
||||||
this.locked = !!data.thread_metadata?.locked;
|
|
||||||
this.messageCount = data.message_count;
|
|
||||||
this.memberCount = data.member_count;
|
|
||||||
this.ownerId = data.owner_id;
|
|
||||||
|
|
||||||
if (data.member) {
|
|
||||||
this.member = new ThreadMember(session, data.member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: ChannelTypes.GuildNewsThread | ChannelTypes.GuildPrivateThread | ChannelTypes.GuildPublicThread;
|
|
||||||
archived?: boolean;
|
|
||||||
archiveTimestamp?: string;
|
|
||||||
autoArchiveDuration?: number;
|
|
||||||
locked?: boolean;
|
|
||||||
messageCount?: number;
|
|
||||||
memberCount?: number;
|
|
||||||
member?: ThreadMember;
|
|
||||||
ownerId?: Snowflake;
|
|
||||||
|
|
||||||
async joinThread(): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'PUT',
|
|
||||||
Routes.THREAD_ME(this.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async addToThread(guildMemberId: Snowflake): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'PUT',
|
|
||||||
Routes.THREAD_USER(this.id, guildMemberId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async leaveToThread(guildMemberId: Snowflake): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'DELETE',
|
|
||||||
Routes.THREAD_USER(this.id, guildMemberId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMember(memberId: Snowflake = this.session.botId): Promise<void> {
|
|
||||||
return ThreadMember.prototype.quitThread.call({ id: this.id, session: this.session }, memberId);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchMember(memberId: Snowflake = this.session.botId): Promise<ThreadMember> {
|
|
||||||
return ThreadMember.prototype.fetchMember.call({ id: this.id, session: this.session }, memberId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchMembers(): Promise<ThreadMember[]> {
|
|
||||||
const members = await this.session.rest.runMethod<DiscordThreadMember[]>(
|
|
||||||
this.session.rest,
|
|
||||||
'GET',
|
|
||||||
Routes.THREAD_MEMBERS(this.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
return members.map((threadMember) => new ThreadMember(this.session, threadMember));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ThreadChannel extends Omit<GuildChannel, 'type'>, Omit<TextChannel, 'type'> {}
|
|
||||||
|
|
||||||
TextChannel.applyTo(ThreadChannel);
|
|
||||||
|
|
||||||
export class GuildTextChannel extends GuildChannel {
|
|
||||||
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
|
|
||||||
super(session, data, guildId);
|
|
||||||
this.type = data.type as ChannelTypes.GuildText;
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: ChannelTypes.GuildText;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GuildTextChannel extends GuildChannel, TextChannel {}
|
|
||||||
|
|
||||||
TextChannel.applyTo(GuildTextChannel);
|
|
||||||
|
|
||||||
/** ChannelFactory */
|
|
||||||
export type Channel =
|
|
||||||
| GuildTextChannel
|
|
||||||
| TextChannel
|
|
||||||
| VoiceChannel
|
|
||||||
| DMChannel
|
|
||||||
| NewsChannel
|
|
||||||
| ThreadChannel
|
|
||||||
| StageChannel
|
|
||||||
| CategoryChannel;
|
|
||||||
|
|
||||||
export type ChannelInGuild =
|
|
||||||
| GuildTextChannel
|
|
||||||
| VoiceChannel
|
|
||||||
| StageChannel
|
|
||||||
| NewsChannel
|
|
||||||
| ThreadChannel;
|
|
||||||
|
|
||||||
export type ChannelWithMessages =
|
|
||||||
| GuildTextChannel
|
|
||||||
| VoiceChannel
|
|
||||||
| DMChannel
|
|
||||||
| NewsChannel
|
|
||||||
| ThreadChannel;
|
|
||||||
|
|
||||||
export type ChannelWithMessagesInGuild = Exclude<ChannelWithMessages, DMChannel>;
|
|
||||||
|
|
||||||
export type PartialChannel = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
position: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ChannelFactory {
|
|
||||||
static fromGuildChannel(session: Session, channel: DiscordChannel): ChannelInGuild {
|
|
||||||
switch (channel.type) {
|
|
||||||
case ChannelTypes.GuildPublicThread:
|
|
||||||
case ChannelTypes.GuildPrivateThread:
|
|
||||||
return new ThreadChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.GuildText:
|
|
||||||
return new GuildTextChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.GuildNews:
|
|
||||||
return new NewsChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.GuildVoice:
|
|
||||||
return new VoiceChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.GuildStageVoice:
|
|
||||||
return new StageChannel(session, channel, channel.guild_id!);
|
|
||||||
default:
|
|
||||||
throw new Error('Channel was not implemented');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static from(session: Session, channel: DiscordChannel): Channel {
|
|
||||||
switch (channel.type) {
|
|
||||||
case ChannelTypes.GuildPublicThread:
|
|
||||||
case ChannelTypes.GuildPrivateThread:
|
|
||||||
return new ThreadChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.GuildText:
|
|
||||||
return new GuildTextChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.GuildNews:
|
|
||||||
return new NewsChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.DM:
|
|
||||||
return new DMChannel(session, channel);
|
|
||||||
case ChannelTypes.GuildVoice:
|
|
||||||
return new VoiceChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.GuildStageVoice:
|
|
||||||
return new StageChannel(session, channel, channel.guild_id!);
|
|
||||||
case ChannelTypes.GuildCategory:
|
|
||||||
return new CategoryChannel(session, channel);
|
|
||||||
default:
|
|
||||||
if (textBasedChannels.includes(channel.type)) {
|
|
||||||
return new TextChannel(session, channel);
|
|
||||||
}
|
|
||||||
throw new Error('Channel was not implemented');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static permissionOverwrites(overwrites: DiscordOverwrite[]): PermissionsOverwrites[] {
|
|
||||||
return overwrites.map((v) => {
|
|
||||||
return {
|
|
||||||
id: v.id,
|
|
||||||
type: v.type,
|
|
||||||
allow: new Permissions(parseInt(v.allow!)),
|
|
||||||
deny: new Permissions(parseInt(v.deny!)),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { DiscordComponent, DiscordInputTextComponent } from '../../../discordeno/mod.ts';
|
|
||||||
import type { ActionRowComponent, ComponentsWithoutRow } from './Component.ts';
|
|
||||||
import { ButtonStyles, MessageComponentTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import BaseComponent from './Component.ts';
|
|
||||||
import Button from './ButtonComponent.ts';
|
|
||||||
import LinkButton from './LinkButtonComponent.ts';
|
|
||||||
import SelectMenu from './SelectMenuComponent.ts';
|
|
||||||
import InputText from './TextInputComponent.ts';
|
|
||||||
|
|
||||||
export class ActionRow extends BaseComponent implements ActionRowComponent {
|
|
||||||
constructor(session: Session, data: DiscordComponent) {
|
|
||||||
super(data.type);
|
|
||||||
|
|
||||||
this.session = session;
|
|
||||||
this.type = data.type as MessageComponentTypes.ActionRow;
|
|
||||||
this.components = data.components!.map((component) => {
|
|
||||||
switch (component.type) {
|
|
||||||
case MessageComponentTypes.Button:
|
|
||||||
if (component.style === ButtonStyles.Link) {
|
|
||||||
return new LinkButton(session, component);
|
|
||||||
}
|
|
||||||
return new Button(session, component);
|
|
||||||
case MessageComponentTypes.SelectMenu:
|
|
||||||
return new SelectMenu(session, component);
|
|
||||||
case MessageComponentTypes.InputText:
|
|
||||||
return new InputText(session, component as DiscordInputTextComponent);
|
|
||||||
case MessageComponentTypes.ActionRow:
|
|
||||||
throw new Error('Cannot have an action row inside an action row');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
override type: MessageComponentTypes.ActionRow;
|
|
||||||
components: ComponentsWithoutRow[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ActionRow;
|
|
@ -1,33 +0,0 @@
|
|||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { DiscordComponent } from '../../../discordeno/mod.ts';
|
|
||||||
import type { ButtonComponent, ClassicButton } from './Component.ts';
|
|
||||||
import { MessageComponentTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import BaseComponent from './Component.ts';
|
|
||||||
import Emoji from '../Emoji.ts';
|
|
||||||
|
|
||||||
export class Button extends BaseComponent implements ButtonComponent {
|
|
||||||
constructor(session: Session, data: DiscordComponent) {
|
|
||||||
super(data.type);
|
|
||||||
|
|
||||||
this.session = session;
|
|
||||||
this.type = data.type as MessageComponentTypes.Button;
|
|
||||||
this.customId = data.custom_id;
|
|
||||||
this.label = data.label;
|
|
||||||
this.style = data.style as ClassicButton;
|
|
||||||
this.disabled = data.disabled;
|
|
||||||
|
|
||||||
if (data.emoji) {
|
|
||||||
this.emoji = new Emoji(session, data.emoji);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
override type: MessageComponentTypes.Button;
|
|
||||||
customId?: string;
|
|
||||||
label?: string;
|
|
||||||
style: ClassicButton;
|
|
||||||
disabled?: boolean;
|
|
||||||
emoji?: Emoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Button;
|
|
@ -1,97 +0,0 @@
|
|||||||
import type Emoji from '../Emoji.ts';
|
|
||||||
import { ButtonStyles, MessageComponentTypes, TextStyles } from '../../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
export class BaseComponent {
|
|
||||||
constructor(type: MessageComponentTypes) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
type: MessageComponentTypes;
|
|
||||||
|
|
||||||
isActionRow(): this is ActionRowComponent {
|
|
||||||
return this.type === MessageComponentTypes.ActionRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
isButton(): this is ButtonComponent {
|
|
||||||
return this.type === MessageComponentTypes.Button;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelectMenu(): this is SelectMenuComponent {
|
|
||||||
return this.type === MessageComponentTypes.SelectMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
isTextInput(): this is TextInputComponent {
|
|
||||||
return this.type === MessageComponentTypes.InputText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Action Row Component */
|
|
||||||
export interface ActionRowComponent {
|
|
||||||
type: MessageComponentTypes.ActionRow;
|
|
||||||
components: Array<Exclude<Component, ActionRowComponent>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** All Components */
|
|
||||||
export type Component =
|
|
||||||
| ActionRowComponent
|
|
||||||
| ButtonComponent
|
|
||||||
| LinkButtonComponent
|
|
||||||
| SelectMenuComponent
|
|
||||||
| TextInputComponent;
|
|
||||||
|
|
||||||
/** Button Component */
|
|
||||||
export type ClassicButton = Exclude<ButtonStyles, ButtonStyles.Link>;
|
|
||||||
|
|
||||||
export type ComponentsWithoutRow = Exclude<Component, ActionRowComponent>;
|
|
||||||
|
|
||||||
export interface ButtonComponent {
|
|
||||||
type: MessageComponentTypes.Button;
|
|
||||||
style: ClassicButton;
|
|
||||||
label?: string;
|
|
||||||
emoji?: Emoji;
|
|
||||||
customId?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Link Button Component */
|
|
||||||
export interface LinkButtonComponent {
|
|
||||||
type: MessageComponentTypes.Button;
|
|
||||||
style: ButtonStyles.Link;
|
|
||||||
label?: string;
|
|
||||||
url: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Select Menu Component */
|
|
||||||
export interface SelectMenuComponent {
|
|
||||||
type: MessageComponentTypes.SelectMenu;
|
|
||||||
customId: string;
|
|
||||||
options: SelectMenuOption[];
|
|
||||||
placeholder?: string;
|
|
||||||
minValue?: number;
|
|
||||||
maxValue?: number;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Text Input Component */
|
|
||||||
export interface TextInputComponent {
|
|
||||||
type: MessageComponentTypes.InputText;
|
|
||||||
customId: string;
|
|
||||||
style: TextStyles;
|
|
||||||
label: string;
|
|
||||||
minLength?: number;
|
|
||||||
maxLength?: number;
|
|
||||||
required?: boolean;
|
|
||||||
value?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectMenuOption {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
description?: string;
|
|
||||||
emoji?: Emoji;
|
|
||||||
default?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BaseComponent;
|
|
@ -1,31 +0,0 @@
|
|||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { DiscordComponent, DiscordInputTextComponent } from '../../../discordeno/mod.ts';
|
|
||||||
import type { Component } from './Component.ts';
|
|
||||||
import { ButtonStyles, MessageComponentTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import ActionRow from './ActionRowComponent.ts';
|
|
||||||
import Button from './ButtonComponent.ts';
|
|
||||||
import LinkButton from './ButtonComponent.ts';
|
|
||||||
import SelectMenu from './SelectMenuComponent.ts';
|
|
||||||
import TextInput from './TextInputComponent.ts';
|
|
||||||
|
|
||||||
export class ComponentFactory {
|
|
||||||
/**
|
|
||||||
* Component factory
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
static from(session: Session, component: DiscordComponent): Component {
|
|
||||||
switch (component.type) {
|
|
||||||
case MessageComponentTypes.ActionRow:
|
|
||||||
return new ActionRow(session, component);
|
|
||||||
case MessageComponentTypes.Button:
|
|
||||||
if (component.style === ButtonStyles.Link) return new LinkButton(session, component);
|
|
||||||
return new Button(session, component);
|
|
||||||
case MessageComponentTypes.SelectMenu:
|
|
||||||
return new SelectMenu(session, component);
|
|
||||||
case MessageComponentTypes.InputText:
|
|
||||||
return new TextInput(session, component as DiscordInputTextComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ComponentFactory;
|
|
@ -1,33 +0,0 @@
|
|||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { ButtonStyles, DiscordComponent } from '../../../discordeno/mod.ts';
|
|
||||||
import type { LinkButtonComponent } from './Component.ts';
|
|
||||||
import { MessageComponentTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import BaseComponent from './Component.ts';
|
|
||||||
import Emoji from '../Emoji.ts';
|
|
||||||
|
|
||||||
export class LinkButton extends BaseComponent implements LinkButtonComponent {
|
|
||||||
constructor(session: Session, data: DiscordComponent) {
|
|
||||||
super(data.type);
|
|
||||||
|
|
||||||
this.session = session;
|
|
||||||
this.type = data.type as MessageComponentTypes.Button;
|
|
||||||
this.url = data.url!;
|
|
||||||
this.label = data.label;
|
|
||||||
this.style = data.style as number;
|
|
||||||
this.disabled = data.disabled;
|
|
||||||
|
|
||||||
if (data.emoji) {
|
|
||||||
this.emoji = new Emoji(session, data.emoji);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
override type: MessageComponentTypes.Button;
|
|
||||||
url: string;
|
|
||||||
label?: string;
|
|
||||||
style: ButtonStyles.Link;
|
|
||||||
disabled?: boolean;
|
|
||||||
emoji?: Emoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LinkButton;
|
|
@ -1,39 +0,0 @@
|
|||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { DiscordComponent } from '../../../discordeno/mod.ts';
|
|
||||||
import type { SelectMenuComponent, SelectMenuOption } from './Component.ts';
|
|
||||||
import { MessageComponentTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import BaseComponent from './Component.ts';
|
|
||||||
import Emoji from '../Emoji.ts';
|
|
||||||
|
|
||||||
export class SelectMenu extends BaseComponent implements SelectMenuComponent {
|
|
||||||
constructor(session: Session, data: DiscordComponent) {
|
|
||||||
super(data.type);
|
|
||||||
|
|
||||||
this.session = session;
|
|
||||||
this.type = data.type as MessageComponentTypes.SelectMenu;
|
|
||||||
this.customId = data.custom_id!;
|
|
||||||
this.options = data.options!.map((option) => {
|
|
||||||
return <SelectMenuOption> {
|
|
||||||
label: option.label,
|
|
||||||
description: option.description,
|
|
||||||
emoji: option.emoji || new Emoji(session, option.emoji!),
|
|
||||||
value: option.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.placeholder = data.placeholder;
|
|
||||||
this.minValues = data.min_values;
|
|
||||||
this.maxValues = data.max_values;
|
|
||||||
this.disabled = data.disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
override type: MessageComponentTypes.SelectMenu;
|
|
||||||
customId: string;
|
|
||||||
options: SelectMenuOption[];
|
|
||||||
placeholder?: string;
|
|
||||||
minValues?: number;
|
|
||||||
maxValues?: number;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SelectMenu;
|
|
@ -1,35 +0,0 @@
|
|||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { DiscordInputTextComponent } from '../../../discordeno/mod.ts';
|
|
||||||
import type { TextInputComponent } from './Component.ts';
|
|
||||||
import { MessageComponentTypes, TextStyles } from '../../../discordeno/mod.ts';
|
|
||||||
import BaseComponent from './Component.ts';
|
|
||||||
|
|
||||||
export class TextInput extends BaseComponent implements TextInputComponent {
|
|
||||||
constructor(session: Session, data: DiscordInputTextComponent) {
|
|
||||||
super(data.type);
|
|
||||||
|
|
||||||
this.session = session;
|
|
||||||
this.type = data.type as MessageComponentTypes.InputText;
|
|
||||||
this.customId = data.custom_id!;
|
|
||||||
this.label = data.label!;
|
|
||||||
this.style = data.style as TextStyles;
|
|
||||||
|
|
||||||
this.placeholder = data.placeholder;
|
|
||||||
this.value = data.value;
|
|
||||||
|
|
||||||
this.minLength = data.min_length;
|
|
||||||
this.maxLength = data.max_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
override type: MessageComponentTypes.InputText;
|
|
||||||
style: TextStyles;
|
|
||||||
customId: string;
|
|
||||||
label: string;
|
|
||||||
placeholder?: string;
|
|
||||||
value?: string;
|
|
||||||
minLength?: number;
|
|
||||||
maxLength?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TextInput;
|
|
File diff suppressed because it is too large
Load Diff
@ -1,39 +0,0 @@
|
|||||||
import type { Model } from '../Base.ts';
|
|
||||||
import type { Snowflake } from '../../Snowflake.ts';
|
|
||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { ApplicationCommandTypes, DiscordInteraction, InteractionTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import type { ApplicationCommandOptionChoice } from './BaseInteraction.ts';
|
|
||||||
import { InteractionResponseTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import BaseInteraction from './BaseInteraction.ts';
|
|
||||||
import * as Routes from '../../Routes.ts';
|
|
||||||
|
|
||||||
export class AutoCompleteInteraction extends BaseInteraction implements Model {
|
|
||||||
constructor(session: Session, data: DiscordInteraction) {
|
|
||||||
super(session, data);
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.commandId = data.data!.id;
|
|
||||||
this.commandName = data.data!.name;
|
|
||||||
this.commandType = data.data!.type;
|
|
||||||
this.commandGuildId = data.data!.guild_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: InteractionTypes.ApplicationCommandAutocomplete;
|
|
||||||
commandId: Snowflake;
|
|
||||||
commandName: string;
|
|
||||||
commandType: ApplicationCommandTypes;
|
|
||||||
commandGuildId?: Snowflake;
|
|
||||||
|
|
||||||
async respondWithChoices(choices: ApplicationCommandOptionChoice[]): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
Routes.INTERACTION_ID_TOKEN(this.id, this.token),
|
|
||||||
{
|
|
||||||
data: { choices },
|
|
||||||
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AutoCompleteInteraction;
|
|
@ -1,275 +0,0 @@
|
|||||||
import type { Model } from '../Base.ts';
|
|
||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { DiscordInteraction, DiscordMessage, DiscordMessageComponents } from '../../../discordeno/mod.ts';
|
|
||||||
import type CommandInteraction from './CommandInteraction.ts';
|
|
||||||
import type PingInteraction from './PingInteraction.ts';
|
|
||||||
import type ComponentInteraction from './ComponentInteraction.ts';
|
|
||||||
import type ModalSubmitInteraction from './ModalSubmitInteraction.ts';
|
|
||||||
import type AutoCompleteInteraction from './AutoCompleteInteraction.ts';
|
|
||||||
import type { CreateMessage } from '../Message.ts';
|
|
||||||
import type { MessageFlags } from '../../Util.ts';
|
|
||||||
import type { EditWebhookMessage } from '../Webhook.ts';
|
|
||||||
import { InteractionResponseTypes, InteractionTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import { Snowflake } from '../../Snowflake.ts';
|
|
||||||
import User from '../User.ts';
|
|
||||||
import Member from '../Member.ts';
|
|
||||||
import Message from '../Message.ts';
|
|
||||||
import Permsisions from '../Permissions.ts';
|
|
||||||
import Webhook from '../Webhook.ts';
|
|
||||||
import * as Routes from '../../Routes.ts';
|
|
||||||
|
|
||||||
export type InteractionResponseWith = { with: InteractionApplicationCommandCallbackData };
|
|
||||||
export type InteractionResponseWithData = InteractionResponse | InteractionResponseWith;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/slash-commands#interaction-response
|
|
||||||
*/
|
|
||||||
export interface InteractionResponse {
|
|
||||||
type: InteractionResponseTypes;
|
|
||||||
data?: InteractionApplicationCommandCallbackData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata
|
|
||||||
*/
|
|
||||||
export interface InteractionApplicationCommandCallbackData
|
|
||||||
extends Pick<CreateMessage, 'allowedMentions' | 'content' | 'embeds' | 'files'> {
|
|
||||||
customId?: string;
|
|
||||||
title?: string;
|
|
||||||
components?: DiscordMessageComponents;
|
|
||||||
flags?: MessageFlags;
|
|
||||||
choices?: ApplicationCommandOptionChoice[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice
|
|
||||||
*/
|
|
||||||
export interface ApplicationCommandOptionChoice {
|
|
||||||
name: string;
|
|
||||||
value: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class BaseInteraction implements Model {
|
|
||||||
constructor(session: Session, data: DiscordInteraction) {
|
|
||||||
this.session = session;
|
|
||||||
this.id = data.id;
|
|
||||||
this.token = data.token;
|
|
||||||
this.type = data.type;
|
|
||||||
this.guildId = data.guild_id;
|
|
||||||
this.channelId = data.channel_id;
|
|
||||||
this.applicationId = data.application_id;
|
|
||||||
this.version = data.version;
|
|
||||||
|
|
||||||
// @ts-expect-error: vendor error
|
|
||||||
const perms = data.app_permissions as string;
|
|
||||||
|
|
||||||
if (perms) {
|
|
||||||
this.appPermissions = new Permsisions(BigInt(perms));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.guild_id) {
|
|
||||||
this.user = new User(session, data.user!);
|
|
||||||
} else {
|
|
||||||
this.member = new Member(session, data.member!, data.guild_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
readonly id: Snowflake;
|
|
||||||
readonly token: string;
|
|
||||||
|
|
||||||
type: InteractionTypes;
|
|
||||||
guildId?: Snowflake;
|
|
||||||
channelId?: Snowflake;
|
|
||||||
applicationId?: Snowflake;
|
|
||||||
user?: User;
|
|
||||||
member?: Member;
|
|
||||||
appPermissions?: Permsisions;
|
|
||||||
|
|
||||||
readonly version: 1;
|
|
||||||
|
|
||||||
responded = false;
|
|
||||||
|
|
||||||
get createdTimestamp(): number {
|
|
||||||
return Snowflake.snowflakeToTimestamp(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
get createdAt(): Date {
|
|
||||||
return new Date(this.createdTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
isCommand(): this is CommandInteraction {
|
|
||||||
return this.type === InteractionTypes.ApplicationCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
isAutoComplete(): this is AutoCompleteInteraction {
|
|
||||||
return this.type === InteractionTypes.ApplicationCommandAutocomplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
isComponent(): this is ComponentInteraction {
|
|
||||||
return this.type === InteractionTypes.MessageComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
isPing(): this is PingInteraction {
|
|
||||||
return this.type === InteractionTypes.Ping;
|
|
||||||
}
|
|
||||||
|
|
||||||
isModalSubmit(): this is ModalSubmitInteraction {
|
|
||||||
return this.type === InteractionTypes.ModalSubmit;
|
|
||||||
}
|
|
||||||
|
|
||||||
inGuild(): this is this & { guildId: Snowflake } {
|
|
||||||
return !!this.guildId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// webhooks methods:
|
|
||||||
|
|
||||||
async editReply(options: EditWebhookMessage & { messageId?: Snowflake }): Promise<Message | undefined> {
|
|
||||||
const message = await this.session.rest.runMethod<DiscordMessage | undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'PATCH',
|
|
||||||
options.messageId
|
|
||||||
? Routes.WEBHOOK_MESSAGE(this.id, this.token, options.messageId)
|
|
||||||
: Routes.WEBHOOK_MESSAGE_ORIGINAL(this.id, this.token),
|
|
||||||
{
|
|
||||||
content: options.content,
|
|
||||||
embeds: options.embeds,
|
|
||||||
file: options.files,
|
|
||||||
components: options.components,
|
|
||||||
allowed_mentions: options.allowedMentions && {
|
|
||||||
parse: options.allowedMentions.parse,
|
|
||||||
replied_user: options.allowedMentions.repliedUser,
|
|
||||||
users: options.allowedMentions.users,
|
|
||||||
roles: options.allowedMentions.roles,
|
|
||||||
},
|
|
||||||
attachments: options.attachments?.map((attachment) => {
|
|
||||||
return {
|
|
||||||
id: attachment.id,
|
|
||||||
filename: attachment.name,
|
|
||||||
content_type: attachment.contentType,
|
|
||||||
size: attachment.size,
|
|
||||||
url: attachment.attachment,
|
|
||||||
proxy_url: attachment.proxyUrl,
|
|
||||||
height: attachment.height,
|
|
||||||
width: attachment.width,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
message_id: options.messageId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!message || !options.messageId) {
|
|
||||||
return message as undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Message(this.session, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendFollowUp(options: InteractionApplicationCommandCallbackData): Promise<Message> {
|
|
||||||
const message = await Webhook.prototype.execute.call({
|
|
||||||
id: this.applicationId!,
|
|
||||||
token: this.token,
|
|
||||||
session: this.session,
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
return message!;
|
|
||||||
}
|
|
||||||
|
|
||||||
async editFollowUp(messageId: Snowflake, options?: { threadId: Snowflake }): Promise<Message> {
|
|
||||||
const message = await Webhook.prototype.editMessage.call(
|
|
||||||
{
|
|
||||||
id: this.session.applicationId,
|
|
||||||
token: this.token,
|
|
||||||
},
|
|
||||||
messageId,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteFollowUp(messageId: Snowflake, threadId?: Snowflake): Promise<void> {
|
|
||||||
await Webhook.prototype.deleteMessage.call(
|
|
||||||
{
|
|
||||||
id: this.session.applicationId,
|
|
||||||
token: this.token,
|
|
||||||
},
|
|
||||||
messageId,
|
|
||||||
threadId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchFollowUp(messageId: Snowflake, threadId?: Snowflake): Promise<Message | undefined> {
|
|
||||||
const message = await Webhook.prototype.fetchMessage.call(
|
|
||||||
{
|
|
||||||
id: this.session.applicationId,
|
|
||||||
token: this.token,
|
|
||||||
},
|
|
||||||
messageId,
|
|
||||||
threadId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// end webhook methods
|
|
||||||
|
|
||||||
// deno-fmt-ignore
|
|
||||||
async respond(resp: InteractionResponse): Promise<Message | undefined>;
|
|
||||||
async respond(resp: InteractionResponseWith): Promise<Message | undefined>;
|
|
||||||
async respond(
|
|
||||||
resp: InteractionResponseWithData,
|
|
||||||
): Promise<Message | undefined> {
|
|
||||||
const options = 'with' in resp ? resp.with : resp.data;
|
|
||||||
const type = 'type' in resp ? resp.type : InteractionResponseTypes.ChannelMessageWithSource;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
content: options?.content,
|
|
||||||
custom_id: options?.customId,
|
|
||||||
file: options?.files,
|
|
||||||
allowed_mentions: options?.allowedMentions,
|
|
||||||
flags: options?.flags,
|
|
||||||
chocies: options?.choices,
|
|
||||||
embeds: options?.embeds,
|
|
||||||
title: options?.title,
|
|
||||||
components: options?.components,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!this.responded) {
|
|
||||||
await this.session.rest.sendRequest<undefined>(this.session.rest, {
|
|
||||||
url: Routes.INTERACTION_ID_TOKEN(this.id, this.token),
|
|
||||||
method: 'POST',
|
|
||||||
payload: this.session.rest.createRequestBody(this.session.rest, {
|
|
||||||
method: 'POST',
|
|
||||||
body: { type, data, file: options?.files },
|
|
||||||
headers: { 'Authorization': '' },
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.responded = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.sendFollowUp(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start custom methods
|
|
||||||
|
|
||||||
async respondWith(resp: InteractionApplicationCommandCallbackData): Promise<Message | undefined> {
|
|
||||||
const m = await this.respond({ with: resp });
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
async defer() {
|
|
||||||
await this.respond({ type: InteractionResponseTypes.DeferredChannelMessageWithSource });
|
|
||||||
}
|
|
||||||
|
|
||||||
async autocomplete() {
|
|
||||||
await this.respond({ type: InteractionResponseTypes.ApplicationCommandAutocompleteResult });
|
|
||||||
}
|
|
||||||
|
|
||||||
// end custom methods
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BaseInteraction;
|
|
@ -1,84 +0,0 @@
|
|||||||
import type { Model } from '../Base.ts';
|
|
||||||
import type { Snowflake } from '../../Snowflake.ts';
|
|
||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type {
|
|
||||||
ApplicationCommandTypes,
|
|
||||||
DiscordInteraction,
|
|
||||||
DiscordMemberWithUser,
|
|
||||||
InteractionTypes,
|
|
||||||
} from '../../../discordeno/mod.ts';
|
|
||||||
import BaseInteraction from './BaseInteraction.ts';
|
|
||||||
import CommandInteractionOptionResolver from './CommandInteractionOptionResolver.ts';
|
|
||||||
import Attachment from '../Attachment.ts';
|
|
||||||
import User from '../User.ts';
|
|
||||||
import Member from '../Member.ts';
|
|
||||||
import Message from '../Message.ts';
|
|
||||||
import Role from '../Role.ts';
|
|
||||||
|
|
||||||
export interface CommandInteractionDataResolved {
|
|
||||||
users: Map<Snowflake, User>;
|
|
||||||
members: Map<Snowflake, Member>;
|
|
||||||
roles: Map<Snowflake, Role>;
|
|
||||||
attachments: Map<Snowflake, Attachment>;
|
|
||||||
messages: Map<Snowflake, Message>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CommandInteraction extends BaseInteraction implements Model {
|
|
||||||
constructor(session: Session, data: DiscordInteraction) {
|
|
||||||
super(session, data);
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.commandId = data.data!.id;
|
|
||||||
this.commandName = data.data!.name;
|
|
||||||
this.commandType = data.data!.type;
|
|
||||||
this.commandGuildId = data.data!.guild_id;
|
|
||||||
this.options = new CommandInteractionOptionResolver(data.data!.options ?? []);
|
|
||||||
|
|
||||||
this.resolved = {
|
|
||||||
users: new Map(),
|
|
||||||
members: new Map(),
|
|
||||||
roles: new Map(),
|
|
||||||
attachments: new Map(),
|
|
||||||
messages: new Map(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.data!.resolved?.users) {
|
|
||||||
for (const [id, u] of Object.entries(data.data!.resolved.users)) {
|
|
||||||
this.resolved.users.set(id, new User(session, u));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.data!.resolved?.members && !!super.guildId) {
|
|
||||||
for (const [id, m] of Object.entries(data.data!.resolved.members)) {
|
|
||||||
this.resolved.members.set(id, new Member(session, m as DiscordMemberWithUser, super.guildId!));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.data!.resolved?.roles && !!super.guildId) {
|
|
||||||
for (const [id, r] of Object.entries(data.data!.resolved.roles)) {
|
|
||||||
this.resolved.roles.set(id, new Role(session, r, super.guildId!));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.data!.resolved?.attachments) {
|
|
||||||
for (const [id, a] of Object.entries(data.data!.resolved.attachments)) {
|
|
||||||
this.resolved.attachments.set(id, new Attachment(session, a));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.data!.resolved?.messages) {
|
|
||||||
for (const [id, m] of Object.entries(data.data!.resolved.messages)) {
|
|
||||||
this.resolved.messages.set(id, new Message(session, m));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: InteractionTypes.ApplicationCommand;
|
|
||||||
commandId: Snowflake;
|
|
||||||
commandName: string;
|
|
||||||
commandType: ApplicationCommandTypes;
|
|
||||||
commandGuildId?: Snowflake;
|
|
||||||
resolved: CommandInteractionDataResolved;
|
|
||||||
options: CommandInteractionOptionResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CommandInteraction;
|
|
@ -1,272 +0,0 @@
|
|||||||
import type { DiscordInteractionDataOption, DiscordInteractionDataResolved } from '../../../discordeno/mod.ts';
|
|
||||||
import { ApplicationCommandOptionTypes } from '../../../discordeno/mod.ts';
|
|
||||||
|
|
||||||
export function transformOasisInteractionDataOption(o: DiscordInteractionDataOption): CommandInteractionOption {
|
|
||||||
const output: CommandInteractionOption = { ...o, Otherwise: o.value as string | boolean | number | undefined };
|
|
||||||
|
|
||||||
switch (o.type) {
|
|
||||||
case ApplicationCommandOptionTypes.String:
|
|
||||||
output.String = o.value as string;
|
|
||||||
break;
|
|
||||||
case ApplicationCommandOptionTypes.Number:
|
|
||||||
output.Number = o.value as number;
|
|
||||||
break;
|
|
||||||
case ApplicationCommandOptionTypes.Integer:
|
|
||||||
output.Integer = o.value as number;
|
|
||||||
break;
|
|
||||||
case ApplicationCommandOptionTypes.Boolean:
|
|
||||||
output.Boolean = o.value as boolean;
|
|
||||||
break;
|
|
||||||
case ApplicationCommandOptionTypes.Role:
|
|
||||||
output.Role = BigInt(o.value as string);
|
|
||||||
break;
|
|
||||||
case ApplicationCommandOptionTypes.User:
|
|
||||||
output.User = BigInt(o.value as string);
|
|
||||||
break;
|
|
||||||
case ApplicationCommandOptionTypes.Channel:
|
|
||||||
output.Channel = BigInt(o.value as string);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ApplicationCommandOptionTypes.Mentionable:
|
|
||||||
case ApplicationCommandOptionTypes.SubCommand:
|
|
||||||
case ApplicationCommandOptionTypes.SubCommandGroup:
|
|
||||||
default:
|
|
||||||
output.Otherwise = o.value as string | boolean | number | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommandInteractionOption extends Omit<DiscordInteractionDataOption, 'value'> {
|
|
||||||
Attachment?: string;
|
|
||||||
Boolean?: boolean;
|
|
||||||
User?: bigint;
|
|
||||||
Role?: bigint;
|
|
||||||
Number?: number;
|
|
||||||
Integer?: number;
|
|
||||||
Channel?: bigint;
|
|
||||||
String?: string;
|
|
||||||
Mentionable?: string;
|
|
||||||
Otherwise: string | number | boolean | bigint | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to get the resolved options for a command
|
|
||||||
* It is really typesafe
|
|
||||||
* @example const option = ctx.options.getStringOption("name");
|
|
||||||
*/
|
|
||||||
export class CommandInteractionOptionResolver {
|
|
||||||
#subcommand?: string;
|
|
||||||
#group?: string;
|
|
||||||
|
|
||||||
hoistedOptions: CommandInteractionOption[];
|
|
||||||
resolved?: DiscordInteractionDataResolved;
|
|
||||||
|
|
||||||
constructor(options?: DiscordInteractionDataOption[], resolved?: DiscordInteractionDataResolved) {
|
|
||||||
this.hoistedOptions = options?.map(transformOasisInteractionDataOption) ?? [];
|
|
||||||
|
|
||||||
// warning: black magic do not edit and thank djs authors
|
|
||||||
|
|
||||||
if (this.hoistedOptions[0]?.type === ApplicationCommandOptionTypes.SubCommandGroup) {
|
|
||||||
this.#group = this.hoistedOptions[0].name;
|
|
||||||
this.hoistedOptions = (this.hoistedOptions[0].options ?? []).map(transformOasisInteractionDataOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hoistedOptions[0]?.type === ApplicationCommandOptionTypes.SubCommand) {
|
|
||||||
this.#subcommand = this.hoistedOptions[0].name;
|
|
||||||
this.hoistedOptions = (this.hoistedOptions[0].options ?? []).map(transformOasisInteractionDataOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resolved = resolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTypedOption(
|
|
||||||
name: string | number,
|
|
||||||
type: ApplicationCommandOptionTypes,
|
|
||||||
properties: Array<keyof CommandInteractionOption>,
|
|
||||||
required: boolean,
|
|
||||||
): CommandInteractionOption | void {
|
|
||||||
const option: CommandInteractionOption | undefined = this.get(name, required);
|
|
||||||
|
|
||||||
if (!option) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option.type !== type) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
|
|
||||||
if (required === true && properties.every((prop) => typeof option[prop] === 'undefined')) {
|
|
||||||
throw new TypeError(`Properties ${properties.join(', ')} are missing in option ${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return option;
|
|
||||||
}
|
|
||||||
|
|
||||||
get(name: string | number, required: true): CommandInteractionOption;
|
|
||||||
get(name: string | number, required: boolean): CommandInteractionOption | undefined;
|
|
||||||
get(name: string | number, required?: boolean) {
|
|
||||||
const option: CommandInteractionOption | undefined = this.hoistedOptions.find((o) =>
|
|
||||||
typeof name === 'number' ? o.name === name.toString() : o.name === name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!option) {
|
|
||||||
if (required && name in this.hoistedOptions.map((o) => o.name)) {
|
|
||||||
throw new TypeError('Option marked as required was undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return option;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for a string option */
|
|
||||||
getString(name: string | number, required: true): string;
|
|
||||||
getString(name: string | number, required?: boolean): string | undefined;
|
|
||||||
getString(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(
|
|
||||||
name,
|
|
||||||
ApplicationCommandOptionTypes.String,
|
|
||||||
['Otherwise'],
|
|
||||||
required,
|
|
||||||
);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for a number option */
|
|
||||||
getNumber(name: string | number, required: true): number;
|
|
||||||
getNumber(name: string | number, required?: boolean): number | undefined;
|
|
||||||
getNumber(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(
|
|
||||||
name,
|
|
||||||
ApplicationCommandOptionTypes.Number,
|
|
||||||
['Otherwise'],
|
|
||||||
required,
|
|
||||||
);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searhces for an integer option */
|
|
||||||
getInteger(name: string | number, required: true): number;
|
|
||||||
getInteger(name: string | number, required?: boolean): number | undefined;
|
|
||||||
getInteger(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(
|
|
||||||
name,
|
|
||||||
ApplicationCommandOptionTypes.Integer,
|
|
||||||
['Otherwise'],
|
|
||||||
required,
|
|
||||||
);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for a boolean option */
|
|
||||||
getBoolean(name: string | number, required: true): boolean;
|
|
||||||
getBoolean(name: string | number, required?: boolean): boolean | undefined;
|
|
||||||
getBoolean(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(
|
|
||||||
name,
|
|
||||||
ApplicationCommandOptionTypes.Boolean,
|
|
||||||
['Otherwise'],
|
|
||||||
required,
|
|
||||||
);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for a user option */
|
|
||||||
getUser(name: string | number, required: true): bigint;
|
|
||||||
getUser(name: string | number, required?: boolean): bigint | undefined;
|
|
||||||
getUser(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(name, ApplicationCommandOptionTypes.User, [
|
|
||||||
'Otherwise',
|
|
||||||
], required);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for a channel option */
|
|
||||||
getChannel(name: string | number, required: true): bigint;
|
|
||||||
getChannel(name: string | number, required?: boolean): bigint | undefined;
|
|
||||||
getChannel(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(
|
|
||||||
name,
|
|
||||||
ApplicationCommandOptionTypes.Channel,
|
|
||||||
['Otherwise'],
|
|
||||||
required,
|
|
||||||
);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for a mentionable-based option */
|
|
||||||
getMentionable(name: string | number, required: true): string;
|
|
||||||
getMentionable(name: string | number, required?: boolean): string | undefined;
|
|
||||||
getMentionable(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(
|
|
||||||
name,
|
|
||||||
ApplicationCommandOptionTypes.Mentionable,
|
|
||||||
['Otherwise'],
|
|
||||||
required,
|
|
||||||
);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for a mentionable-based option */
|
|
||||||
getRole(name: string | number, required: true): bigint;
|
|
||||||
getRole(name: string | number, required?: boolean): bigint | undefined;
|
|
||||||
getRole(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(name, ApplicationCommandOptionTypes.Role, [
|
|
||||||
'Otherwise',
|
|
||||||
], required);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for an attachment option */
|
|
||||||
getAttachment(name: string | number, required: true): string;
|
|
||||||
getAttachment(name: string | number, required?: boolean): string | undefined;
|
|
||||||
getAttachment(name: string | number, required = false) {
|
|
||||||
const option: CommandInteractionOption | void = this.getTypedOption(
|
|
||||||
name,
|
|
||||||
ApplicationCommandOptionTypes.Attachment,
|
|
||||||
['Otherwise'],
|
|
||||||
required,
|
|
||||||
);
|
|
||||||
|
|
||||||
return option?.Otherwise ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** searches for the focused option */
|
|
||||||
getFocused(full = false): string | number | bigint | boolean | undefined | CommandInteractionOption {
|
|
||||||
const focusedOption: CommandInteractionOption | void = this.hoistedOptions.find((option) => option.focused);
|
|
||||||
|
|
||||||
if (!focusedOption) {
|
|
||||||
throw new TypeError('No option found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return full ? focusedOption : focusedOption.Otherwise;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSubCommand(required = true): (string | CommandInteractionOption[] | undefined)[] {
|
|
||||||
if (required && !this.#subcommand) {
|
|
||||||
throw new TypeError('Option marked as required was undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
return [this.#subcommand, this.hoistedOptions];
|
|
||||||
}
|
|
||||||
|
|
||||||
getSubCommandGroup(required = false): (string | CommandInteractionOption[] | undefined)[] {
|
|
||||||
if (required && !this.#group) {
|
|
||||||
throw new TypeError('Option marked as required was undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
return [this.#group, this.hoistedOptions];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CommandInteractionOptionResolver;
|
|
@ -1,49 +0,0 @@
|
|||||||
import type { Model } from '../Base.ts';
|
|
||||||
import type { Snowflake } from '../../Snowflake.ts';
|
|
||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { DiscordInteraction, InteractionTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import { InteractionResponseTypes, MessageComponentTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import BaseInteraction from './BaseInteraction.ts';
|
|
||||||
import Message from '../Message.ts';
|
|
||||||
|
|
||||||
export class ComponentInteraction extends BaseInteraction implements Model {
|
|
||||||
constructor(session: Session, data: DiscordInteraction) {
|
|
||||||
super(session, data);
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.componentType = data.data!.component_type!;
|
|
||||||
this.customId = data.data!.custom_id;
|
|
||||||
this.targetId = data.data!.target_id;
|
|
||||||
this.values = data.data!.values;
|
|
||||||
this.message = new Message(session, data.message!);
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: InteractionTypes.MessageComponent;
|
|
||||||
componentType: MessageComponentTypes;
|
|
||||||
customId?: string;
|
|
||||||
targetId?: Snowflake;
|
|
||||||
values?: string[];
|
|
||||||
message: Message;
|
|
||||||
|
|
||||||
//TODO: create interface/class for components types
|
|
||||||
isButton(): boolean {
|
|
||||||
return this.componentType === MessageComponentTypes.Button;
|
|
||||||
}
|
|
||||||
|
|
||||||
isActionRow(): boolean {
|
|
||||||
return this.componentType === MessageComponentTypes.ActionRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
isTextInput(): boolean {
|
|
||||||
return this.componentType === MessageComponentTypes.InputText;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelectMenu(): boolean {
|
|
||||||
return this.componentType === MessageComponentTypes.SelectMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deferUpdate() {
|
|
||||||
await this.respond({ type: InteractionResponseTypes.DeferredUpdateMessage });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ComponentInteraction;
|
|
@ -1,69 +0,0 @@
|
|||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { Snowflake } from '../../Snowflake.ts';
|
|
||||||
import type { DiscordInteraction, DiscordMessageInteraction } from '../../../discordeno/mod.ts';
|
|
||||||
import { InteractionTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import User from '../User.ts';
|
|
||||||
import Member from '../Member.ts';
|
|
||||||
import CommandInteraction from './CommandInteraction.ts';
|
|
||||||
import ComponentInteraction from './ComponentInteraction.ts';
|
|
||||||
import PingInteraction from './PingInteraction.ts';
|
|
||||||
import AutoCompleteInteraction from './AutoCompleteInteraction.ts';
|
|
||||||
import ModalSubmitInteraction from './ModalSubmitInteraction.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @link https://discord.com/developers/docs/interactions/receiving-and-responding#message-interaction-object-message-interaction-structure
|
|
||||||
*/
|
|
||||||
export interface MessageInteraction {
|
|
||||||
/** id of the interaction */
|
|
||||||
id: Snowflake;
|
|
||||||
/** type of interaction */
|
|
||||||
type: InteractionTypes;
|
|
||||||
/** name of the application command, including subcommands and subcommand groups */
|
|
||||||
name: string;
|
|
||||||
/** user who invoked the interaction */
|
|
||||||
user: User;
|
|
||||||
/** member who invoked the interaction in the guild */
|
|
||||||
member?: Partial<Member>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Interaction =
|
|
||||||
| CommandInteraction
|
|
||||||
| ComponentInteraction
|
|
||||||
| PingInteraction
|
|
||||||
| AutoCompleteInteraction
|
|
||||||
| ModalSubmitInteraction;
|
|
||||||
|
|
||||||
export class InteractionFactory {
|
|
||||||
static from(session: Session, interaction: DiscordInteraction): Interaction {
|
|
||||||
switch (interaction.type) {
|
|
||||||
case InteractionTypes.Ping:
|
|
||||||
return new PingInteraction(session, interaction);
|
|
||||||
case InteractionTypes.ApplicationCommand:
|
|
||||||
return new CommandInteraction(session, interaction);
|
|
||||||
case InteractionTypes.MessageComponent:
|
|
||||||
return new ComponentInteraction(session, interaction);
|
|
||||||
case InteractionTypes.ApplicationCommandAutocomplete:
|
|
||||||
return new AutoCompleteInteraction(session, interaction);
|
|
||||||
case InteractionTypes.ModalSubmit:
|
|
||||||
return new ModalSubmitInteraction(session, interaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromMessage(
|
|
||||||
session: Session,
|
|
||||||
interaction: DiscordMessageInteraction,
|
|
||||||
_guildId?: Snowflake,
|
|
||||||
): MessageInteraction {
|
|
||||||
const obj = {
|
|
||||||
id: interaction.id,
|
|
||||||
type: interaction.type,
|
|
||||||
name: interaction.name,
|
|
||||||
user: new User(session, interaction.user),
|
|
||||||
// TODO: Parse member somehow with the guild id passed in message
|
|
||||||
};
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InteractionFactory;
|
|
@ -1,56 +0,0 @@
|
|||||||
import type { Model } from '../Base.ts';
|
|
||||||
import type { Snowflake } from '../../Snowflake.ts';
|
|
||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type {
|
|
||||||
DiscordInteraction,
|
|
||||||
DiscordMessageComponents,
|
|
||||||
InteractionTypes,
|
|
||||||
MessageComponentTypes,
|
|
||||||
} from '../../../discordeno/mod.ts';
|
|
||||||
import BaseInteraction from './BaseInteraction.ts';
|
|
||||||
import Message from '../Message.ts';
|
|
||||||
|
|
||||||
export type ModalInMessage = ModalSubmitInteraction & { message: Message };
|
|
||||||
|
|
||||||
export class ModalSubmitInteraction extends BaseInteraction implements Model {
|
|
||||||
constructor(session: Session, data: DiscordInteraction) {
|
|
||||||
super(session, data);
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.componentType = data.data!.component_type!;
|
|
||||||
this.customId = data.data!.custom_id;
|
|
||||||
this.targetId = data.data!.target_id;
|
|
||||||
this.values = data.data!.values;
|
|
||||||
|
|
||||||
this.components = data.data?.components?.map(ModalSubmitInteraction.transformComponent);
|
|
||||||
|
|
||||||
if (data.message) {
|
|
||||||
this.message = new Message(session, data.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: InteractionTypes.MessageComponent;
|
|
||||||
componentType: MessageComponentTypes;
|
|
||||||
customId?: string;
|
|
||||||
targetId?: Snowflake;
|
|
||||||
values?: string[];
|
|
||||||
message?: Message;
|
|
||||||
components;
|
|
||||||
|
|
||||||
static transformComponent(component: DiscordMessageComponents[number]) {
|
|
||||||
return {
|
|
||||||
type: component.type,
|
|
||||||
components: component.components.map((component) => {
|
|
||||||
return {
|
|
||||||
customId: component.custom_id,
|
|
||||||
value: (component as typeof component & { value: string }).value,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
inMessage(): this is ModalInMessage {
|
|
||||||
return !!this.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ModalSubmitInteraction;
|
|
@ -1,37 +0,0 @@
|
|||||||
import type { Model } from '../Base.ts';
|
|
||||||
import type { Snowflake } from '../../Snowflake.ts';
|
|
||||||
import type { Session } from '../../Session.ts';
|
|
||||||
import type { ApplicationCommandTypes, DiscordInteraction, InteractionTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import { InteractionResponseTypes } from '../../../discordeno/mod.ts';
|
|
||||||
import BaseInteraction from './BaseInteraction.ts';
|
|
||||||
import * as Routes from '../../Routes.ts';
|
|
||||||
|
|
||||||
export class PingInteraction extends BaseInteraction implements Model {
|
|
||||||
constructor(session: Session, data: DiscordInteraction) {
|
|
||||||
super(session, data);
|
|
||||||
this.type = data.type as number;
|
|
||||||
this.commandId = data.data!.id;
|
|
||||||
this.commandName = data.data!.name;
|
|
||||||
this.commandType = data.data!.type;
|
|
||||||
this.commandGuildId = data.data!.guild_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
override type: InteractionTypes.Ping;
|
|
||||||
commandId: Snowflake;
|
|
||||||
commandName: string;
|
|
||||||
commandType: ApplicationCommandTypes;
|
|
||||||
commandGuildId?: Snowflake;
|
|
||||||
|
|
||||||
async pong(): Promise<void> {
|
|
||||||
await this.session.rest.runMethod<undefined>(
|
|
||||||
this.session.rest,
|
|
||||||
'POST',
|
|
||||||
Routes.INTERACTION_ID_TOKEN(this.id, this.token),
|
|
||||||
{
|
|
||||||
type: InteractionResponseTypes.Pong,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PingInteraction;
|
|
@ -1,76 +0,0 @@
|
|||||||
// This module is browser-compatible.
|
|
||||||
|
|
||||||
// deno-lint-ignore-file ban-types
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event emitter (observer pattern)
|
|
||||||
*/
|
|
||||||
export class EventEmitter {
|
|
||||||
listeners = new Map<PropertyKey, Function[]>();
|
|
||||||
|
|
||||||
#addListener(event: string, func: Function) {
|
|
||||||
this.listeners.set(event, this.listeners.get(event) || []);
|
|
||||||
this.listeners.get(event)?.push(func);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
on(event: string, func: Function): this {
|
|
||||||
return this.#addListener(event, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
#removeListener(event: string, func: Function): this {
|
|
||||||
if (this.listeners.has(event)) {
|
|
||||||
const listener = this.listeners.get(event);
|
|
||||||
|
|
||||||
if (listener?.includes(func)) {
|
|
||||||
listener.splice(listener.indexOf(func), 1);
|
|
||||||
|
|
||||||
if (listener.length === 0) {
|
|
||||||
this.listeners.delete(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
off(event: string, func: Function): this {
|
|
||||||
return this.#removeListener(event, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
once(event: string, func: Function): this {
|
|
||||||
// it is important for this to be an arrow function
|
|
||||||
const closure = () => {
|
|
||||||
func();
|
|
||||||
this.off(event, func);
|
|
||||||
};
|
|
||||||
|
|
||||||
const listener = this.listeners.get(event) ?? [];
|
|
||||||
|
|
||||||
listener.push(closure);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(event: string, ...args: unknown[]): boolean {
|
|
||||||
const listener = this.listeners.get(event);
|
|
||||||
|
|
||||||
if (!listener) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
listener.forEach((f) => f(...args));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
listenerCount(eventName: string): number {
|
|
||||||
return this.listeners.get(eventName)?.length ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
rawListeners(eventName: string): Function[] | undefined {
|
|
||||||
return this.listeners.get(eventName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EventEmitter;
|
|
@ -1,69 +0,0 @@
|
|||||||
/** Converts a url to base 64. Useful for example, uploading/creating server emojis. */
|
|
||||||
export async function urlToBase64(url: string): Promise<string> {
|
|
||||||
const buffer = await fetch(url).then((res) => res.arrayBuffer());
|
|
||||||
const imageStr = encode(buffer);
|
|
||||||
const type = url.substring(url.lastIndexOf('.') + 1);
|
|
||||||
return `data:image/${type};base64,${imageStr}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// deno-fmt-ignore
|
|
||||||
const base64abc: string[] = [
|
|
||||||
"A", "B", "C",
|
|
||||||
"D", "E", "F",
|
|
||||||
"G", "H", "I",
|
|
||||||
"J", "K", "L",
|
|
||||||
"M", "N", "O",
|
|
||||||
"P", "Q", "R",
|
|
||||||
"S", "T", "U",
|
|
||||||
"V", "W", "X",
|
|
||||||
"Y", "Z", "a",
|
|
||||||
"b", "c", "d",
|
|
||||||
"e", "f", "g",
|
|
||||||
"h", "i", "j",
|
|
||||||
"k", "l", "m",
|
|
||||||
"n", "o", "p",
|
|
||||||
"q", "r", "s",
|
|
||||||
"t", "u", "v",
|
|
||||||
"w", "x", "y",
|
|
||||||
"z", "0", "1",
|
|
||||||
"2", "3", "4",
|
|
||||||
"5", "6", "7",
|
|
||||||
"8", "9", "+", "/",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
|
||||||
* Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
export function encode(data: ArrayBuffer | string): string {
|
|
||||||
const uint8: Uint8Array = typeof data === 'string'
|
|
||||||
? new TextEncoder().encode(data)
|
|
||||||
: data instanceof Uint8Array
|
|
||||||
? data
|
|
||||||
: new Uint8Array(data);
|
|
||||||
let result = '',
|
|
||||||
i;
|
|
||||||
const l: number = uint8.length;
|
|
||||||
for (i = 2; i < l; i += 3) {
|
|
||||||
result += base64abc[uint8[i - 2] >> 2];
|
|
||||||
result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)];
|
|
||||||
result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)];
|
|
||||||
result += base64abc[uint8[i] & 0x3f];
|
|
||||||
}
|
|
||||||
if (i === l + 1) {
|
|
||||||
// 1 octet yet to write
|
|
||||||
result += base64abc[uint8[i - 2] >> 2];
|
|
||||||
result += base64abc[(uint8[i - 2] & 0x03) << 4];
|
|
||||||
result += '==';
|
|
||||||
}
|
|
||||||
if (i === l) {
|
|
||||||
// 2 octets yet to write
|
|
||||||
result += base64abc[uint8[i - 2] >> 2];
|
|
||||||
result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)];
|
|
||||||
result += base64abc[(uint8[i - 1] & 0x0f) << 2];
|
|
||||||
result += '=';
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
191
packages/cache/Collection.ts
vendored
191
packages/cache/Collection.ts
vendored
@ -1,191 +0,0 @@
|
|||||||
import type { Session, Snowflake } from './deps.ts';
|
|
||||||
|
|
||||||
export class Collection<V> extends Map<Snowflake, V> {
|
|
||||||
constructor(session: Session, entries?: Iterable<readonly [Snowflake, V]>) {
|
|
||||||
super(entries);
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly session: Session;
|
|
||||||
|
|
||||||
get(key: Snowflake): V | undefined {
|
|
||||||
return super.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: Snowflake, value: V): this {
|
|
||||||
return super.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
has(key: Snowflake): boolean {
|
|
||||||
return super.has(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
return super.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
random(): V | undefined;
|
|
||||||
random(amount: number): V[];
|
|
||||||
random(amount?: number): V | V[] | undefined {
|
|
||||||
const arr = [...this.values()];
|
|
||||||
if (typeof amount === 'undefined') return arr[Math.floor(Math.random() * arr.length)];
|
|
||||||
if (!arr.length) return [];
|
|
||||||
if (amount && amount > arr.length) amount = arr.length;
|
|
||||||
return Array.from(
|
|
||||||
{ length: Math.min(amount, arr.length) },
|
|
||||||
(): V => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
find(fn: (value: V, key: Snowflake, structCache: this) => boolean): V | undefined {
|
|
||||||
for (const [key, value] of this.entries()) {
|
|
||||||
if (fn(value, key, this)) return value;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
filter(fn: (value: V, key: Snowflake, structCache: this) => boolean): Collection<V> {
|
|
||||||
const result = new Collection<V>(this.session);
|
|
||||||
|
|
||||||
for (const [key, value] of this.entries()) {
|
|
||||||
if (fn(value, key, this)) result.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach<T>(fn: (value: V, key: Snowflake, structCache: this) => T): void {
|
|
||||||
super.forEach((v: V, k: Snowflake) => {
|
|
||||||
fn(v, k, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
clone(): Collection<V> {
|
|
||||||
return new Collection(this.session, this.entries());
|
|
||||||
}
|
|
||||||
|
|
||||||
concat(structures: Collection<V>[]): Collection<V> {
|
|
||||||
const conc = this.clone();
|
|
||||||
|
|
||||||
for (const structure of structures) {
|
|
||||||
if (!structure || !(structure instanceof Collection)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of structure.entries()) {
|
|
||||||
conc.set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conc;
|
|
||||||
}
|
|
||||||
|
|
||||||
some(fn: (value: V, key: Snowflake, structCache: this) => boolean): boolean {
|
|
||||||
for (const [key, value] of this.entries()) {
|
|
||||||
if (fn(value, key, this)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
every(fn: (value: V, key: Snowflake, structCache: this) => boolean): boolean {
|
|
||||||
for (const [key, value] of this.entries()) {
|
|
||||||
if (!fn(value, key, this)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
first(): V | undefined;
|
|
||||||
first(amount: number): V[];
|
|
||||||
first(amount?: number): V | V[] | undefined {
|
|
||||||
if (!amount || amount <= 1) {
|
|
||||||
return this.values().next().value;
|
|
||||||
}
|
|
||||||
const values = [...this.values()];
|
|
||||||
amount = Math.min(values.length, amount);
|
|
||||||
return values.slice(0, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
last(): V | undefined;
|
|
||||||
last(amount: number): V[];
|
|
||||||
last(amount?: number): V | V[] | undefined {
|
|
||||||
const values = [...this.values()];
|
|
||||||
if (!amount || amount <= 1) {
|
|
||||||
return values[values.length - 1];
|
|
||||||
}
|
|
||||||
amount = Math.min(values.length, amount);
|
|
||||||
return values.slice(-amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse(): this {
|
|
||||||
const entries = [...this.entries()].reverse();
|
|
||||||
this.clear();
|
|
||||||
for (const [key, value] of entries) this.set(key, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
map<T>(fn: (value: V, key: Snowflake, collection: this) => T): T[] {
|
|
||||||
const result: T[] = [];
|
|
||||||
for (const [key, value] of this.entries()) {
|
|
||||||
result.push(fn(value, key, this));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduce<T>(fn: (acc: T, value: V, key: Snowflake, structCache: this) => T, initV?: T): T {
|
|
||||||
const entries = this.entries();
|
|
||||||
const first = entries.next().value;
|
|
||||||
let result = initV;
|
|
||||||
if (result !== undefined) {
|
|
||||||
result = fn(result, first[1], first[0], this);
|
|
||||||
} else {
|
|
||||||
result = first;
|
|
||||||
}
|
|
||||||
for (const [key, value] of entries) {
|
|
||||||
result = fn(result!, value, key, this);
|
|
||||||
}
|
|
||||||
return result!;
|
|
||||||
}
|
|
||||||
|
|
||||||
get size(): number {
|
|
||||||
return super.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
get empty(): boolean {
|
|
||||||
return this.size === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFields(key: Snowflake, obj: Partial<V>) {
|
|
||||||
const value = this.get(key);
|
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const prop in obj) {
|
|
||||||
if (obj[prop]) {
|
|
||||||
value[prop] = obj[prop]!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
getOr(key: Snowflake, or: V): V | undefined {
|
|
||||||
return this.get(key) ?? or;
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieve<T>(key: Snowflake, fn: (value: V) => T) {
|
|
||||||
const value = this.get(key);
|
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Collection;
|
|
66
packages/cache/channels.ts
vendored
66
packages/cache/channels.ts
vendored
@ -1,66 +0,0 @@
|
|||||||
import type { ChannelInGuild, ChannelTypes, ChannelWithMessagesInGuild, DiscordChannel, Snowflake } from './deps.ts';
|
|
||||||
import type { CachedMessage } from './messages.ts';
|
|
||||||
import type { CachedGuild } from './guilds.ts';
|
|
||||||
import type { SessionCache } from './mod.ts';
|
|
||||||
import { Collection } from './Collection.ts';
|
|
||||||
import { ChannelFactory, DMChannel, textBasedChannels } from './deps.ts';
|
|
||||||
|
|
||||||
export interface CachedGuildChannel extends Omit<ChannelWithMessagesInGuild, 'type'> {
|
|
||||||
type: ChannelTypes;
|
|
||||||
messages: Collection<CachedMessage>;
|
|
||||||
guild: CachedGuild;
|
|
||||||
guildId: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CachedGuildChannel extends Omit<ChannelInGuild, 'type'> {
|
|
||||||
type: ChannelTypes;
|
|
||||||
guild: CachedGuild;
|
|
||||||
guildId: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CachedDMChannel extends DMChannel {
|
|
||||||
messages: Collection<CachedMessage>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function channelBootstrapper(cache: SessionCache, channel: DiscordChannel) {
|
|
||||||
if (!channel.guild_id) {
|
|
||||||
cache.dms.set(
|
|
||||||
channel.id,
|
|
||||||
Object.assign(new DMChannel(cache.session, channel), {
|
|
||||||
messages: new Collection<CachedMessage>(cache.session),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.guilds.retrieve(channel.guild_id, (guild) => {
|
|
||||||
if (textBasedChannels.includes(channel.type)) {
|
|
||||||
guild.channels.set(
|
|
||||||
channel.id,
|
|
||||||
Object.assign(
|
|
||||||
ChannelFactory.fromGuildChannel(cache.session, channel) as ChannelWithMessagesInGuild,
|
|
||||||
{
|
|
||||||
messages: new Collection<CachedMessage>(cache.session),
|
|
||||||
guildId: channel.guild_id!,
|
|
||||||
get guild(): CachedGuild {
|
|
||||||
return cache.guilds.get(this.guildId)!;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
guild.channels.set(
|
|
||||||
channel.id,
|
|
||||||
<CachedGuildChannel> Object.assign(
|
|
||||||
ChannelFactory.fromGuildChannel(cache.session, channel),
|
|
||||||
{
|
|
||||||
guildId: channel.guild_id!,
|
|
||||||
get guild(): CachedGuild {
|
|
||||||
return cache.guilds.get(this.guildId)!;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
2
packages/cache/deps.ts
vendored
2
packages/cache/deps.ts
vendored
@ -1,2 +0,0 @@
|
|||||||
export * from '../biscuit/mod.ts';
|
|
||||||
export * from '../discordeno/mod.ts';
|
|
50
packages/cache/guilds.ts
vendored
50
packages/cache/guilds.ts
vendored
@ -1,50 +0,0 @@
|
|||||||
import type { DiscordGuild, DiscordMemberWithUser } from './deps.ts';
|
|
||||||
import type { SessionCache } from './mod.ts';
|
|
||||||
import type { CachedMember } from './members.ts';
|
|
||||||
import type { CachedUser } from './users.ts';
|
|
||||||
import type { CachedGuildChannel } from './channels.ts';
|
|
||||||
import { ChannelFactory, Guild, Member } from './deps.ts';
|
|
||||||
import { Collection } from './Collection.ts';
|
|
||||||
|
|
||||||
export interface CachedGuild extends Omit<Guild, 'members' | 'channels'> {
|
|
||||||
channels: Collection<CachedGuildChannel>;
|
|
||||||
members: Collection<CachedMember>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function guildBootstrapper(cache: SessionCache, guild: DiscordGuild) {
|
|
||||||
const members = new Collection(
|
|
||||||
cache.session,
|
|
||||||
guild.members?.map((data) => {
|
|
||||||
const obj: CachedMember = Object.assign(
|
|
||||||
new Member(cache.session, data as DiscordMemberWithUser, guild.id),
|
|
||||||
{
|
|
||||||
userId: data.user!.id,
|
|
||||||
get user(): CachedUser | undefined {
|
|
||||||
return cache.users.get(this.userId);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return [data.user!.id, obj as CachedMember];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const channels = new Collection(
|
|
||||||
cache.session,
|
|
||||||
guild.channels?.map((data) => {
|
|
||||||
const obj = data && Object.assign(ChannelFactory.from(cache.session, data), {
|
|
||||||
messages: new Map(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return [data.id, obj as CachedGuildChannel];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
cache.guilds.set(
|
|
||||||
guild.id,
|
|
||||||
Object.assign(
|
|
||||||
new Guild(cache.session, guild),
|
|
||||||
{ members, channels },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
26
packages/cache/members.ts
vendored
26
packages/cache/members.ts
vendored
@ -1,26 +0,0 @@
|
|||||||
import type { DiscordMemberWithUser, Snowflake } from './deps.ts';
|
|
||||||
import type { SessionCache } from './mod.ts';
|
|
||||||
import type { CachedUser } from './users.ts';
|
|
||||||
import { Member } from './deps.ts';
|
|
||||||
|
|
||||||
export interface CachedMember extends Omit<Member, 'user'> {
|
|
||||||
userId: Snowflake;
|
|
||||||
user?: CachedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function memberBootstrapper(cache: SessionCache, member: DiscordMemberWithUser, guildId: Snowflake) {
|
|
||||||
cache.guilds.retrieve(guildId, (guild) => {
|
|
||||||
guild.members.set(
|
|
||||||
member.user.id,
|
|
||||||
Object.assign(
|
|
||||||
new Member(cache.session, member, guildId),
|
|
||||||
{
|
|
||||||
userId: member.user.id,
|
|
||||||
get user(): CachedUser | undefined {
|
|
||||||
return cache.users.get(this.userId);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
150
packages/cache/messages.ts
vendored
150
packages/cache/messages.ts
vendored
@ -1,150 +0,0 @@
|
|||||||
import type {
|
|
||||||
DiscordEmoji,
|
|
||||||
DiscordMemberWithUser,
|
|
||||||
DiscordMessage,
|
|
||||||
DiscordMessageReactionAdd,
|
|
||||||
DiscordMessageReactionRemove,
|
|
||||||
DiscordMessageReactionRemoveAll,
|
|
||||||
Snowflake,
|
|
||||||
} from './deps.ts';
|
|
||||||
import type { CachedUser } from './users.ts';
|
|
||||||
import type { SessionCache } from './mod.ts';
|
|
||||||
import { Emoji, GuildEmoji, Message, MessageReaction } from './deps.ts';
|
|
||||||
import { memberBootstrapper } from './members.ts';
|
|
||||||
|
|
||||||
export interface CachedMessage extends Omit<Message, 'author'> {
|
|
||||||
authorId: Snowflake;
|
|
||||||
author?: CachedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function messageBootstrapper(cache: SessionCache, message: DiscordMessage, partial: boolean) {
|
|
||||||
if (message.member) {
|
|
||||||
const member: DiscordMemberWithUser = Object.assign(message.member, { user: message.author });
|
|
||||||
|
|
||||||
memberBootstrapper(cache, member, message.guild_id!);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cache.dms.has(message.channel_id)) {
|
|
||||||
// is dm
|
|
||||||
cache.dms.retrieve(message.channel_id, (dm) => {
|
|
||||||
dm.messages[partial ? 'updateFields' : 'set'](
|
|
||||||
message.id,
|
|
||||||
Object.assign(
|
|
||||||
new Message(cache.session, message),
|
|
||||||
{
|
|
||||||
authorId: message.author.id,
|
|
||||||
get author(): CachedUser | undefined {
|
|
||||||
return cache.users.get(this.authorId);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// is not dm
|
|
||||||
cache.guilds.retrieve(message.guild_id!, (guild) =>
|
|
||||||
guild.channels.retrieve(message.channel_id, (dm) => {
|
|
||||||
dm.messages[partial ? 'updateFields' : 'set'](
|
|
||||||
message.id,
|
|
||||||
Object.assign(
|
|
||||||
new Message(cache.session, message),
|
|
||||||
{
|
|
||||||
authorId: message.author.id,
|
|
||||||
get author(): CachedUser | undefined {
|
|
||||||
return cache.users.get(this.authorId);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reactionBootstrapper(
|
|
||||||
cache: SessionCache,
|
|
||||||
reaction: DiscordMessageReactionAdd | DiscordMessageReactionRemove,
|
|
||||||
remove: boolean,
|
|
||||||
) {
|
|
||||||
cache.emojis.set(reaction.emoji.id ?? reaction.emoji.name!, new Emoji(cache.session, reaction.emoji));
|
|
||||||
|
|
||||||
function onAdd(message: CachedMessage) {
|
|
||||||
const reactions = message.reactions.map((r) => r.emoji.name);
|
|
||||||
|
|
||||||
const upsertData = {
|
|
||||||
count: 1,
|
|
||||||
emoji: reaction.emoji,
|
|
||||||
me: reaction.user_id === cache.session.botId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (reactions.length === 0) {
|
|
||||||
message.reactions = [];
|
|
||||||
} else if (!reactions.includes(reaction.emoji.name)) {
|
|
||||||
message.reactions.push(new MessageReaction(cache.session, upsertData));
|
|
||||||
} else {
|
|
||||||
const current = message.reactions?.[reactions.indexOf(reaction.emoji.name)];
|
|
||||||
|
|
||||||
if (current && message.reactions?.[message.reactions.indexOf(current)]) {
|
|
||||||
// add 1 to reaction count
|
|
||||||
++message.reactions[message.reactions.indexOf(current)].count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRemove(message: CachedMessage) {
|
|
||||||
const reactions = message.reactions.map((r) => r.emoji.name);
|
|
||||||
|
|
||||||
if (reactions.indexOf(reaction.emoji.name) !== undefined) {
|
|
||||||
const current = message.reactions[reactions.indexOf(reaction.emoji.name)];
|
|
||||||
|
|
||||||
if (current) {
|
|
||||||
if (current.count > 0) {
|
|
||||||
current.count--;
|
|
||||||
}
|
|
||||||
if (current.count === 0) {
|
|
||||||
message.reactions.splice(reactions?.indexOf(reaction.emoji.name), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reaction.guild_id) {
|
|
||||||
cache.guilds.retrieve(reaction.guild_id, (guild) => {
|
|
||||||
guild.channels.retrieve(reaction.channel_id, (channel) => {
|
|
||||||
channel.messages.retrieve(reaction.message_id, (message) => {
|
|
||||||
if (remove) onRemove(message);
|
|
||||||
else onAdd(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cache.dms.retrieve(reaction.channel_id, (channel) => {
|
|
||||||
channel.messages.retrieve(reaction.message_id, (message) => {
|
|
||||||
if (remove) onRemove(message);
|
|
||||||
else onAdd(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reactionBootstrapperDeletions(cache: SessionCache, payload: DiscordMessageReactionRemoveAll) {
|
|
||||||
if (payload.guild_id) {
|
|
||||||
cache.guilds.retrieve(payload.guild_id, (guild) => {
|
|
||||||
guild.channels.retrieve(payload.channel_id, (channel) => {
|
|
||||||
channel.messages.retrieve(payload.message_id, (message: CachedMessage) => {
|
|
||||||
message.reactions = [];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cache.dms.retrieve(payload.channel_id, (channel) => {
|
|
||||||
channel.messages.retrieve(payload.message_id, (message) => {
|
|
||||||
message.reactions = [];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function emojiBootstrapper(cache: SessionCache, emoji: DiscordEmoji, guildId: Snowflake) {
|
|
||||||
if (!emoji.id) return;
|
|
||||||
cache.emojis.set(emoji.id, new GuildEmoji(cache.session, emoji, guildId));
|
|
||||||
}
|
|
77
packages/cache/mod.ts
vendored
77
packages/cache/mod.ts
vendored
@ -1,77 +0,0 @@
|
|||||||
import type { Emoji, Session, SymCache } from './deps.ts';
|
|
||||||
import type { CachedGuild } from './guilds.ts';
|
|
||||||
import type { CachedUser } from './users.ts';
|
|
||||||
import type { CachedDMChannel } from './channels.ts';
|
|
||||||
import { Collection } from './Collection.ts';
|
|
||||||
import { memberBootstrapper } from './members.ts';
|
|
||||||
import { userBootstrapper } from './users.ts';
|
|
||||||
import { channelBootstrapper } from './channels.ts';
|
|
||||||
import { guildBootstrapper } from './guilds.ts';
|
|
||||||
import { messageBootstrapper, reactionBootstrapper, reactionBootstrapperDeletions } from './messages.ts';
|
|
||||||
|
|
||||||
export const cache_sym = Symbol('@cache');
|
|
||||||
|
|
||||||
export interface SessionCache extends SymCache {
|
|
||||||
guilds: Collection<CachedGuild>;
|
|
||||||
users: Collection<CachedUser>;
|
|
||||||
dms: Collection<CachedDMChannel>;
|
|
||||||
emojis: Collection<Emoji>;
|
|
||||||
session: Session;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enableCache(session: Session): SessionCache {
|
|
||||||
const cache = {
|
|
||||||
guilds: new Collection<CachedGuild>(session),
|
|
||||||
users: new Collection<CachedUser>(session),
|
|
||||||
dms: new Collection<CachedDMChannel>(session),
|
|
||||||
emojis: new Collection<Emoji>(session),
|
|
||||||
cache: cache_sym,
|
|
||||||
session,
|
|
||||||
};
|
|
||||||
|
|
||||||
session.on('raw', (data) => {
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
const raw = data.d as any;
|
|
||||||
|
|
||||||
if (!raw) return;
|
|
||||||
|
|
||||||
switch (data.t) {
|
|
||||||
// TODO: add more events
|
|
||||||
// for now users have to use the bootstrappers that are not implemented yet
|
|
||||||
case 'MESSAGE_CREATE':
|
|
||||||
messageBootstrapper(cache, raw, false);
|
|
||||||
break;
|
|
||||||
case 'MESSAGE_UPDATE':
|
|
||||||
messageBootstrapper(cache, raw, !raw.edited_timestamp);
|
|
||||||
break;
|
|
||||||
case 'CHANNEL_CREATE':
|
|
||||||
channelBootstrapper(cache, raw);
|
|
||||||
break;
|
|
||||||
case 'GUILD_MEMBER_ADD':
|
|
||||||
memberBootstrapper(cache, raw, raw.guild_id);
|
|
||||||
break;
|
|
||||||
case 'GUILD_CREATE':
|
|
||||||
guildBootstrapper(cache, raw);
|
|
||||||
break;
|
|
||||||
case 'GUILD_DELETE':
|
|
||||||
cache.guilds.delete(raw.id);
|
|
||||||
break;
|
|
||||||
case 'MESSAGE_REACTION_ADD':
|
|
||||||
reactionBootstrapper(cache, raw, false);
|
|
||||||
break;
|
|
||||||
case 'MESSAGE_REACTION_REMOVE':
|
|
||||||
reactionBootstrapper(cache, raw, false);
|
|
||||||
break;
|
|
||||||
case 'MESSAGE_REACTION_REMOVE_ALL':
|
|
||||||
reactionBootstrapperDeletions(cache, raw);
|
|
||||||
break;
|
|
||||||
case 'READY':
|
|
||||||
userBootstrapper(cache, raw.user);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
session.emit('debug', `NOT CACHED: ${JSON.stringify(raw)}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return cache;
|
|
||||||
}
|
|
9
packages/cache/users.ts
vendored
9
packages/cache/users.ts
vendored
@ -1,9 +0,0 @@
|
|||||||
import type { DiscordUser } from './deps.ts';
|
|
||||||
import type { SessionCache } from './mod.ts';
|
|
||||||
import { User } from './deps.ts';
|
|
||||||
|
|
||||||
export type CachedUser = User;
|
|
||||||
|
|
||||||
export function userBootstrapper(cache: SessionCache, user: DiscordUser) {
|
|
||||||
cache.users.set(user.id, new User(cache.session, user));
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
# Standalone WS / Proxy WS
|
|
||||||
|
|
||||||
This WS service is meant for ADVANCED DEVELOPERS ONLY!
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
- **Zero Downtime Updates**:
|
|
||||||
|
|
||||||
- Your bot can be updated in a matter of seconds. With normal sharding, you have to restart which also has to process
|
|
||||||
identifying all your shards with a 1/~5s rate limit. With WS handling moved to a proxy process, this allows you to
|
|
||||||
instantly get the bot code restarted without any concerns of delays. If you have a bot on 200,000 servers normally
|
|
||||||
this would mean a 20 minute delay to restart your bot if you made a small change and restarted.
|
|
||||||
|
|
||||||
- **Zero Downtime Resharding**:
|
|
||||||
|
|
||||||
- Discord stops letting your bot get added to new servers at certain points in time. For example, suppose you had
|
|
||||||
150,000 servers running 150 shards. The maximum amount of servers your shards could hold is 150 \* 2500 = 375,000.
|
|
||||||
If your bot reaches this, it can no longer join new servers until it re-shards.
|
|
||||||
- DD proxy provides 2 types of re-sharding. Automated and manual. You can also have both.
|
|
||||||
- `Automated`: This system will automatically begin a Zero-downtime resharding process behind the scenes when you
|
|
||||||
reach 80% of your maximum servers allowed by your shards. For example, since 375,000 was the max, at 300,000 we
|
|
||||||
would begin re-sharding behind the scenes with `ZERO DOWNTIME`.
|
|
||||||
- 80% of maximum servers reached (The % of 80% is customizable.)
|
|
||||||
- Identify limits have room to allow re-sharding. (Also customizable)
|
|
||||||
- `Manual`: You can also trigger this manually should you choose.
|
|
||||||
|
|
||||||
- **Horizontal Scaling**:
|
|
||||||
|
|
||||||
- The proxy system allows you to scale the bot horizontally. When you reach a huge size, you can either keep spending
|
|
||||||
more money to keep beefing up your server or you can buy several cheaper servers and scale horizontally. The proxy
|
|
||||||
means you can have WS handling on a completely separate system.
|
|
||||||
|
|
||||||
- **No Loss Restarts**:
|
|
||||||
|
|
||||||
- When you restart a bot without the proxy system, normally you would lose many events. Users may be using commands or
|
|
||||||
messages are sent that will not be filtered. As your bot's grow this number rises dramatically. Users may join who
|
|
||||||
wont get the auto-roles or any other actions your bot should take. With the proxy system, you can keep restarting
|
|
||||||
your bot and never lose any events. Events will be put into a queue while your bot is down(max size of queue is
|
|
||||||
customizable), once the bot is available the queue will begin processing all events.
|
|
||||||
|
|
||||||
- **Controllers**:
|
|
||||||
|
|
||||||
- The controller aspect gives you full control over everything inside the proxy. You can provide a function to simply
|
|
||||||
override the handler. For example, if you would like a certain function to do something different, instead of having
|
|
||||||
to fork and maintain your fork, you can just provide a function to override.
|
|
||||||
|
|
||||||
- **Clustering With Workers**:
|
|
||||||
- Take full advantage of all your CPU cores by using workers to spread the load. Control how many shards per worker
|
|
||||||
and how many workers to maximize efficiency!
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```ts
|
|
||||||
createGatewayManager({
|
|
||||||
// TODO: (docs) Fill this out
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## API/Docs
|
|
||||||
|
|
||||||
// TODO: (docs) Fill this out. List all props/methods.
|
|
@ -1,7 +0,0 @@
|
|||||||
import { GatewayManager } from './manager/gatewayManager.ts';
|
|
||||||
|
|
||||||
export function calculateShardId(gateway: GatewayManager, guildId: bigint) {
|
|
||||||
if (gateway.manager.totalShards === 1) return 0;
|
|
||||||
|
|
||||||
return Number((guildId >> 22n) % BigInt(gateway.manager.totalShards - 1));
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { GatewayManager } from './gatewayManager.ts';
|
|
||||||
|
|
||||||
/** Handler used to determine max number of shards to use based upon the max concurrency. */
|
|
||||||
export function calculateTotalShards(gateway: GatewayManager): number {
|
|
||||||
// Bots under 100k servers do not have access to total shards.
|
|
||||||
if (gateway.manager.totalShards < 100) return gateway.manager.totalShards;
|
|
||||||
|
|
||||||
// Calculate a multiple of `maxConcurrency` which can be used to connect to the gateway.
|
|
||||||
return Math.ceil(
|
|
||||||
gateway.manager.totalShards /
|
|
||||||
// If `maxConcurrency` is 1 we can safely use 16.
|
|
||||||
(gateway.gatewayBot.sessionStartLimit.maxConcurrency === 1
|
|
||||||
? 16
|
|
||||||
: gateway.gatewayBot.sessionStartLimit.maxConcurrency),
|
|
||||||
) * gateway.gatewayBot.sessionStartLimit.maxConcurrency;
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
import { GatewayManager } from './gatewayManager.ts';
|
|
||||||
|
|
||||||
export function calculateWorkerId(manager: GatewayManager, shardId: number) {
|
|
||||||
// Ignore decimal numbers.
|
|
||||||
let workerId = Math.floor((shardId) / manager.shardsPerWorker);
|
|
||||||
// If the workerId overflows the maximal allowed workers we by default just use to last worker.
|
|
||||||
if (workerId >= manager.totalWorkers) {
|
|
||||||
// The Id of the last available worker is total -1
|
|
||||||
workerId = manager.totalWorkers - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return workerId;
|
|
||||||
}
|
|
@ -1,294 +0,0 @@
|
|||||||
import { DiscordGatewayPayload } from '../../../api-types/discord.ts';
|
|
||||||
import { GatewayBot, PickPartial } from '../../../api-types/shared.ts';
|
|
||||||
import { LeakyBucket } from '../../util/bucket.ts';
|
|
||||||
import { CreateShard, createShard } from '../shard/createShard.ts';
|
|
||||||
import { Shard, ShardGatewayConfig } from '../shard/types.ts';
|
|
||||||
import { calculateTotalShards } from './calculateTotalShards.ts';
|
|
||||||
import { calculateWorkerId } from './calculateWorkerId.ts';
|
|
||||||
// import {
|
|
||||||
// markNewGuildShardId,
|
|
||||||
// resharder,
|
|
||||||
// resharderCloseOldShards,
|
|
||||||
// resharderIsPending,
|
|
||||||
// reshardingEditGuildShardIds,
|
|
||||||
// } from "./resharder.ts";
|
|
||||||
import { spawnShards } from './spawnShards.ts';
|
|
||||||
import { prepareBuckets } from './prepareBuckets.ts';
|
|
||||||
import { tellWorkerToIdentify } from './tellWorkerToIdentify.ts';
|
|
||||||
import { createShardManager, ShardManager } from './shardManager.ts';
|
|
||||||
import { stop } from './stop.ts';
|
|
||||||
|
|
||||||
export type GatewayManager = ReturnType<typeof createGatewayManager>;
|
|
||||||
|
|
||||||
/** Create a new Gateway Manager.
|
|
||||||
*
|
|
||||||
* @param options: Customize every bit of the manager. If something is not
|
|
||||||
* provided, it will fallback to a default which should be suitable for most
|
|
||||||
* bots.
|
|
||||||
*/
|
|
||||||
export function createGatewayManager(
|
|
||||||
options: PickPartial<CreateGatewayManager, 'handleDiscordPayload' | 'gatewayBot' | 'gatewayConfig'>,
|
|
||||||
) {
|
|
||||||
const prepareBucketsOverwritten = options.prepareBuckets ?? prepareBuckets;
|
|
||||||
const spawnShardsOverwritten = options.spawnShards ?? spawnShards;
|
|
||||||
const stopOverwritten = options.stop ?? stop;
|
|
||||||
const tellWorkerToIdentifyOverwritten = options.tellWorkerToIdentify ?? tellWorkerToIdentify;
|
|
||||||
const calculateTotalShardsOverwritten = options.calculateTotalShards ?? calculateTotalShards;
|
|
||||||
const calculateWorkerIdOverwritten = options.calculateWorkerId ?? calculateWorkerId;
|
|
||||||
|
|
||||||
const totalShards = options.totalShards ?? options.gatewayBot.shards ?? 1;
|
|
||||||
|
|
||||||
const gatewayManager = {
|
|
||||||
// ----------
|
|
||||||
// PROPERTIES
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/** The max concurrency buckets.
|
|
||||||
* Those will be created when the `spawnShards` (which calls `prepareBuckets` under the hood) function gets called.
|
|
||||||
*/
|
|
||||||
buckets: new Map<
|
|
||||||
number,
|
|
||||||
{
|
|
||||||
workers: { id: number; queue: number[] }[];
|
|
||||||
leak: LeakyBucket;
|
|
||||||
}
|
|
||||||
>(),
|
|
||||||
/** Id of the first Shard which should get controlled by this manager.
|
|
||||||
*
|
|
||||||
* NOTE: This is intended for testing purposes
|
|
||||||
* if big bots want to test the gateway on smaller scale.
|
|
||||||
* This is not recommended to be used in production.
|
|
||||||
*/
|
|
||||||
firstShardId: options.firstShardId ?? 0,
|
|
||||||
/** Important data which is used by the manager to connect shards to the gateway. */
|
|
||||||
gatewayBot: options.gatewayBot,
|
|
||||||
/** Id of the last Shard which should get controlled by this manager.
|
|
||||||
*
|
|
||||||
* NOTE: This is intended for testing purposes
|
|
||||||
* if big bots want to test the gateway on smaller scale.
|
|
||||||
* This is not recommended to be used in production.
|
|
||||||
*/
|
|
||||||
lastShardId: options.lastShardId ?? totalShards - 1 ?? 1,
|
|
||||||
/** This is where the Shards get stored.
|
|
||||||
* This will not be used when having a custom workers solution.
|
|
||||||
*/
|
|
||||||
manager: {} as ShardManager,
|
|
||||||
/** Delay in milliseconds to wait before spawning next shard.
|
|
||||||
* OPTIMAL IS ABOVE 5100. YOU DON'T WANT TO HIT THE RATE LIMIT!!!
|
|
||||||
*/
|
|
||||||
spawnShardDelay: options.spawnShardDelay ?? 5300,
|
|
||||||
/** How many Shards should get assigned to a Worker.
|
|
||||||
*
|
|
||||||
* IMPORTANT: Discordeno will NOT spawn Workers for you.
|
|
||||||
* Instead you have to overwrite the `tellWorkerToIdentify` function to make that for you.
|
|
||||||
* Look at the [BigBot template gateway solution](https://github.com/discordeno/discordeno/tree/main/template/bigbot/src/gateway) for reference.
|
|
||||||
*
|
|
||||||
* NOTE: The last Worker will IGNORE this value,
|
|
||||||
* which means that the last worker can get assigned an unlimited amount of shards.
|
|
||||||
* This is not a bug but intended behavior and means you have to assign more workers to this manager.
|
|
||||||
*/
|
|
||||||
shardsPerWorker: options.shardsPerWorker ?? 25,
|
|
||||||
/** The total amount of Workers which get controlled by this manager.
|
|
||||||
*
|
|
||||||
* IMPORTANT: Discordeno will NOT spawn Workers for you.
|
|
||||||
* Instead you have to overwrite the `tellWorkerToIdentify` function to make that for you.
|
|
||||||
* Look at the [BigBot template gateway solution](https://github.com/discordeno/discordeno/tree/main/template/bigbot/src/gateway) for reference.
|
|
||||||
*/
|
|
||||||
totalWorkers: options.totalWorkers ?? 4,
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
// PROPERTIES
|
|
||||||
// ----------
|
|
||||||
/** Prepares the buckets for identifying.
|
|
||||||
*
|
|
||||||
* NOTE: Most of the time this function does not need to be called,
|
|
||||||
* since it gets called by the `spawnShards` function indirectly.
|
|
||||||
*/
|
|
||||||
prepareBuckets: function () {
|
|
||||||
return prepareBucketsOverwritten(this);
|
|
||||||
},
|
|
||||||
/** This function starts to spawn the Shards assigned to this manager.
|
|
||||||
*
|
|
||||||
* The managers `buckets` will be created and
|
|
||||||
*
|
|
||||||
* if `resharding.useOptimalLargeBotSharding` is set to true,
|
|
||||||
* `totalShards` gets double checked and adjusted accordingly if wrong.
|
|
||||||
*/
|
|
||||||
spawnShards: function () {
|
|
||||||
return spawnShardsOverwritten(this);
|
|
||||||
},
|
|
||||||
/** Stop the gateway. This closes all shards. */
|
|
||||||
stop: function (code: number, reason: string) {
|
|
||||||
return stopOverwritten(this, code, reason);
|
|
||||||
},
|
|
||||||
/** Tell the Worker with this Id to identify this Shard.
|
|
||||||
*
|
|
||||||
* Useful if a custom Worker solution should be used.
|
|
||||||
*
|
|
||||||
* IMPORTANT: Discordeno will NOT spawn Workers for you.
|
|
||||||
* Instead you have to overwrite the `tellWorkerToIdentify` function to make that for you.
|
|
||||||
* Look at the [BigBot template gateway solution](https://github.com/discordeno/discordeno/tree/main/template/bigbot/src/gateway) for reference.
|
|
||||||
*/
|
|
||||||
tellWorkerToIdentify: function (workerId: number, shardId: number, bucketId: number) {
|
|
||||||
return tellWorkerToIdentifyOverwritten(this, workerId, shardId, bucketId);
|
|
||||||
},
|
|
||||||
// TODO: fix debug
|
|
||||||
/** Handle the different logs. Used for debugging. */
|
|
||||||
debug: options.debug || function () {},
|
|
||||||
|
|
||||||
// /** The methods related to resharding. */
|
|
||||||
// resharding: {
|
|
||||||
// /** Whether the resharder should automatically switch to LARGE BOT SHARDING when the bot is above 100K servers. */
|
|
||||||
// useOptimalLargeBotSharding: options.resharding?.useOptimalLargeBotSharding ?? true,
|
|
||||||
// /** Whether or not to automatically reshard.
|
|
||||||
// *
|
|
||||||
// * @default true
|
|
||||||
// */
|
|
||||||
// reshard: options.resharding?.reshard ?? true,
|
|
||||||
// /** The percentage at which resharding should occur.
|
|
||||||
// *
|
|
||||||
// * @default 80
|
|
||||||
// */
|
|
||||||
// reshardPercentage: options.resharding?.reshardPercentage ?? 80,
|
|
||||||
// /** Handles resharding the bot when necessary. */
|
|
||||||
// resharder: options.resharding?.resharder ?? resharder,
|
|
||||||
// /** Handles checking if all new shards are online in the new gateway. */
|
|
||||||
// isPending: options.resharding?.isPending ?? resharderIsPending,
|
|
||||||
// /** Handles closing all shards in the old gateway. */
|
|
||||||
// closeOldShards: options.resharding?.closeOldShards ?? resharderCloseOldShards,
|
|
||||||
// /** Handles checking if it is time to reshard and triggers the resharder. */
|
|
||||||
// check: options.resharding?.check ?? startReshardingChecks,
|
|
||||||
// /** Handler to mark a guild id with its new shard id in cache. */
|
|
||||||
// markNewGuildShardId: options.resharding?.markNewGuildShardId ?? markNewGuildShardId,
|
|
||||||
// /** Handler to update all guilds in cache with the new shard id. */
|
|
||||||
// editGuildShardIds: options.resharding?.editGuildShardIds ?? reshardingEditGuildShardIds,
|
|
||||||
// },
|
|
||||||
|
|
||||||
/** Calculate the amount of Shards which should be used based on the bot's max concurrency. */
|
|
||||||
calculateTotalShards: function () {
|
|
||||||
return calculateTotalShardsOverwritten(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Calculate the Id of the Worker related to this Shard. */
|
|
||||||
calculateWorkerId: function (shardId: number) {
|
|
||||||
return calculateWorkerIdOverwritten(this, shardId);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
gatewayManager.manager = createShardManager({
|
|
||||||
createShardOptions: options.createShardOptions,
|
|
||||||
gatewayConfig: options.gatewayConfig,
|
|
||||||
shardIds: [],
|
|
||||||
totalShards,
|
|
||||||
|
|
||||||
handleMessage: function (shard, message) {
|
|
||||||
return options.handleDiscordPayload(shard, message);
|
|
||||||
},
|
|
||||||
|
|
||||||
requestIdentify: async (shardId) => {
|
|
||||||
// TODO: improve
|
|
||||||
await gatewayManager.buckets.get(shardId % gatewayManager.gatewayBot.sessionStartLimit.maxConcurrency)!.leak
|
|
||||||
.acquire(1);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return gatewayManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateGatewayManager {
|
|
||||||
/** Delay in milliseconds to wait before spawning next shard. OPTIMAL IS ABOVE 5100. YOU DON'T WANT TO HIT THE RATE LIMIT!!! */
|
|
||||||
spawnShardDelay: number;
|
|
||||||
/** Total amount of shards your bot uses. Useful for zero-downtime updates or resharding. */
|
|
||||||
totalShards: number;
|
|
||||||
/** The amount of shards to load per worker. */
|
|
||||||
shardsPerWorker: number;
|
|
||||||
/** The total amount of workers to use for your bot. */
|
|
||||||
totalWorkers: number;
|
|
||||||
/** Id of the first Shard which should get controlled by this manager.
|
|
||||||
*
|
|
||||||
* NOTE: This is intended for testing purposes
|
|
||||||
* if big bots want to test the gateway on smaller scale.
|
|
||||||
* This is not recommended to be used in production.
|
|
||||||
*/
|
|
||||||
firstShardId: number;
|
|
||||||
/** Id of the last Shard which should get controlled by this manager.
|
|
||||||
*
|
|
||||||
* NOTE: This is intended for testing purposes
|
|
||||||
* if big bots want to test the gateway on smaller scale.
|
|
||||||
* This is not recommended to be used in production.
|
|
||||||
*/
|
|
||||||
lastShardId: number;
|
|
||||||
|
|
||||||
/** Important data which is used by the manager to connect shards to the gateway. */
|
|
||||||
gatewayBot: GatewayBot;
|
|
||||||
|
|
||||||
gatewayConfig: PickPartial<ShardGatewayConfig, 'token'>;
|
|
||||||
|
|
||||||
/** Options which are used to create a new shard. */
|
|
||||||
createShardOptions?: Omit<CreateShard, 'id' | 'totalShards' | 'requestIdentify' | 'gatewayConfig'>;
|
|
||||||
|
|
||||||
/** Stored as bucketId: { workers: [workerId, [ShardIds]], createNextShard: boolean } */
|
|
||||||
buckets: Map<
|
|
||||||
number,
|
|
||||||
{
|
|
||||||
workers: { id: number; queue: number[] }[];
|
|
||||||
leak: LeakyBucket;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
// METHODS
|
|
||||||
|
|
||||||
/** Prepares the buckets for identifying */
|
|
||||||
prepareBuckets: typeof prepareBuckets;
|
|
||||||
/** The handler for spawning ALL the shards. */
|
|
||||||
spawnShards: typeof spawnShards;
|
|
||||||
/** The handler to close all shards. */
|
|
||||||
stop: typeof stop;
|
|
||||||
/** Sends the discord payload to another server. */
|
|
||||||
handleDiscordPayload: (shard: Shard, data: DiscordGatewayPayload) => any;
|
|
||||||
/** Tell the worker to begin identifying this shard */
|
|
||||||
tellWorkerToIdentify: typeof tellWorkerToIdentify;
|
|
||||||
/** Handle the different logs. Used for debugging. */
|
|
||||||
debug: (text: GatewayDebugEvents, ...args: any[]) => unknown;
|
|
||||||
/** The methods related to resharding. */
|
|
||||||
// resharding: {
|
|
||||||
// /** Whether the resharder should automatically switch to LARGE BOT SHARDING when you are above 100K servers. */
|
|
||||||
// useOptimalLargeBotSharding: boolean;
|
|
||||||
// /** Whether or not to automatically reshard. */
|
|
||||||
// reshard: boolean;
|
|
||||||
// /** The percentage at which resharding should occur. */
|
|
||||||
// reshardPercentage: number;
|
|
||||||
// /** Handles resharding the bot when necessary. */
|
|
||||||
// resharder: typeof resharder;
|
|
||||||
// /** Handles checking if all new shards are online in the new gateway. */
|
|
||||||
// isPending: typeof resharderIsPending;
|
|
||||||
// /** Handles closing all shards in the old gateway. */
|
|
||||||
// closeOldShards: typeof resharderCloseOldShards;
|
|
||||||
// /** Handler to mark a guild id with its new shard id in cache. */
|
|
||||||
// markNewGuildShardId: typeof markNewGuildShardId;
|
|
||||||
// /** Handler to update all guilds in cache with the new shard id. */
|
|
||||||
// editGuildShardIds: typeof reshardingEditGuildShardIds;
|
|
||||||
// };
|
|
||||||
/** Calculates the number of shards to use based on the max concurrency */
|
|
||||||
calculateTotalShards: typeof calculateTotalShards;
|
|
||||||
|
|
||||||
/** Calculate the id of the worker related ot this Shard. */
|
|
||||||
calculateWorkerId: typeof calculateWorkerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GatewayDebugEvents =
|
|
||||||
| 'GW ERROR'
|
|
||||||
| 'GW CLOSED'
|
|
||||||
| 'GW CLOSED_RECONNECT'
|
|
||||||
| 'GW RAW'
|
|
||||||
| 'GW RECONNECT'
|
|
||||||
| 'GW INVALID_SESSION'
|
|
||||||
| 'GW RESUMED'
|
|
||||||
| 'GW RESUMING'
|
|
||||||
| 'GW IDENTIFYING'
|
|
||||||
| 'GW RAW_SEND'
|
|
||||||
| 'GW MAX REQUESTS'
|
|
||||||
| 'GW DEBUG'
|
|
||||||
| 'GW HEARTBEATING'
|
|
||||||
| 'GW HEARTBEATING_STARTED'
|
|
||||||
| 'GW HEARTBEATING_DETAILS'
|
|
||||||
| 'GW HEARTBEATING_CLOSED';
|
|
@ -1,8 +0,0 @@
|
|||||||
export * from './calculateTotalShards.ts';
|
|
||||||
export * from './calculateWorkerId.ts';
|
|
||||||
export * from './gatewayManager.ts';
|
|
||||||
export * from './prepareBuckets.ts';
|
|
||||||
export * from './shardManager.ts';
|
|
||||||
export * from './spawnShards.ts';
|
|
||||||
export * from './stop.ts';
|
|
||||||
export * from './tellWorkerToIdentify.ts';
|
|
@ -1,47 +0,0 @@
|
|||||||
import { createLeakyBucket } from '../../util/bucket.ts';
|
|
||||||
import { GatewayManager } from './gatewayManager.ts';
|
|
||||||
|
|
||||||
export function prepareBuckets(gateway: GatewayManager) {
|
|
||||||
for (let i = 0; i < gateway.gatewayBot.sessionStartLimit.maxConcurrency; ++i) {
|
|
||||||
gateway.buckets.set(i, {
|
|
||||||
workers: [],
|
|
||||||
leak: createLeakyBucket({
|
|
||||||
max: 1,
|
|
||||||
refillAmount: 1,
|
|
||||||
// special number which is proven to be working dont change
|
|
||||||
refillInterval: gateway.spawnShardDelay,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ORGANIZE ALL SHARDS INTO THEIR OWN BUCKETS
|
|
||||||
for (let shardId = gateway.firstShardId; shardId <= gateway.lastShardId; ++shardId) {
|
|
||||||
// gateway.debug("GW DEBUG", `1. Running for loop in spawnShards function for shardId ${i}.`);
|
|
||||||
if (shardId >= gateway.manager.totalShards) {
|
|
||||||
throw new Error(
|
|
||||||
`Shard (id: ${shardId}) is bigger or equal to the used amount of used shards which is ${gateway.manager.totalShards}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bucketId = shardId % gateway.gatewayBot.sessionStartLimit.maxConcurrency;
|
|
||||||
const bucket = gateway.buckets.get(bucketId);
|
|
||||||
if (!bucket) {
|
|
||||||
throw new Error(
|
|
||||||
`Shard (id: ${shardId}) got assigned to an illegal bucket id: ${bucketId}, expected a bucket id between 0 and ${
|
|
||||||
gateway.gatewayBot.sessionStartLimit.maxConcurrency - 1
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIND A QUEUE IN THIS BUCKET THAT HAS SPACE
|
|
||||||
// const worker = bucket.workers.find((w) => w.queue.length < gateway.shardsPerWorker);
|
|
||||||
const workerId = gateway.calculateWorkerId(shardId);
|
|
||||||
const worker = bucket.workers.find((w) => w.id === workerId);
|
|
||||||
if (worker) {
|
|
||||||
// IF THE QUEUE HAS SPACE JUST ADD IT TO THIS QUEUE
|
|
||||||
worker.queue.push(shardId);
|
|
||||||
} else {
|
|
||||||
bucket.workers.push({ id: workerId, queue: [shardId] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,339 +0,0 @@
|
|||||||
import { GatewayBot } from '../../types/shared.ts';
|
|
||||||
import { createGatewayManager, GatewayManager } from './gatewayManager.ts';
|
|
||||||
|
|
||||||
export type Resharder = ReturnType<typeof activateResharder>;
|
|
||||||
|
|
||||||
export function activateResharder(options: ActivateResharderOptions) {
|
|
||||||
const resharder = {
|
|
||||||
// ----------
|
|
||||||
// PROPERTIES
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/** Interval in milliseconds of when to check whether it's time to reshard.
|
|
||||||
*
|
|
||||||
* @default 28800000 (8 hours)
|
|
||||||
*/
|
|
||||||
checkInterval: options.checkInterval || 28800000,
|
|
||||||
|
|
||||||
/** Gateway manager which is currently processing all shards and events. */
|
|
||||||
gateway: options.gatewayManager,
|
|
||||||
|
|
||||||
/** Timeout of the reshard checker. */
|
|
||||||
intervalId: undefined as number | undefined,
|
|
||||||
|
|
||||||
/** Percentage at which resharding should occur.
|
|
||||||
* @default 80
|
|
||||||
*/
|
|
||||||
percentage: options.percentage ?? 80,
|
|
||||||
|
|
||||||
/** Whether the resharder should automatically switch to LARGE BOT SHARDING when the bot is above 100K servers. */
|
|
||||||
useOptimalLargeBotSharding: options.useOptimalLargeBotSharding ?? true,
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
// METHODS
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/** Activate the resharder and delay the next reshard check. */
|
|
||||||
activate: function () {
|
|
||||||
return activate(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Function which is used to fetch the current gateway information of the bot.
|
|
||||||
* This function is mainly used by the reshard checker.
|
|
||||||
*/
|
|
||||||
getGatewayBot: options.getGatewayBot,
|
|
||||||
|
|
||||||
/** Reshard the bots gateway. */
|
|
||||||
reshard: function (gatewayBot: GatewayBot) {
|
|
||||||
return reshard(this, gatewayBot);
|
|
||||||
},
|
|
||||||
|
|
||||||
tellWorkerToPrepare: options.tellWorkerToPrepare,
|
|
||||||
};
|
|
||||||
|
|
||||||
resharder.activate();
|
|
||||||
|
|
||||||
return resharder;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /** The methods related to resharding. */
|
|
||||||
// resharding: {
|
|
||||||
// /** Whether the resharder should automatically switch to LARGE BOT SHARDING when the bot is above 100K servers. */
|
|
||||||
// useOptimalLargeBotSharding: options.resharding?.useOptimalLargeBotSharding ?? true,
|
|
||||||
// /** Whether or not to automatically reshard.
|
|
||||||
// *
|
|
||||||
// * @default true
|
|
||||||
// */
|
|
||||||
// reshard: options.resharding?.reshard ?? true,
|
|
||||||
// /** The percentage at which resharding should occur.
|
|
||||||
// *
|
|
||||||
// * @default 80
|
|
||||||
// */
|
|
||||||
// reshardPercentage: options.resharding?.reshardPercentage ?? 80,
|
|
||||||
// /** Handles resharding the bot when necessary. */
|
|
||||||
// resharder: options.resharding?.resharder ?? resharder,
|
|
||||||
// /** Handles checking if all new shards are online in the new gateway. */
|
|
||||||
// isPending: options.resharding?.isPending ?? resharderIsPending,
|
|
||||||
// /** Handles closing all shards in the old gateway. */
|
|
||||||
// closeOldShards: options.resharding?.closeOldShards ?? resharderCloseOldShards,
|
|
||||||
// /** Handles checking if it is time to reshard and triggers the resharder. */
|
|
||||||
// check: options.resharding?.check ?? startReshardingChecks,
|
|
||||||
// /** Handler to mark a guild id with its new shard id in cache. */
|
|
||||||
// markNewGuildShardId: options.resharding?.markNewGuildShardId ?? markNewGuildShardId,
|
|
||||||
// /** Handler to update all guilds in cache with the new shard id. */
|
|
||||||
// editGuildShardIds: options.resharding?.editGuildShardIds ?? reshardingEditGuildShardIds,
|
|
||||||
// },
|
|
||||||
|
|
||||||
export interface ActivateResharderOptions {
|
|
||||||
/** Interval in milliseconds of when to check whether it's time to reshard.
|
|
||||||
*
|
|
||||||
* @default 28800000 (8 hours)
|
|
||||||
*/
|
|
||||||
checkInterval?: number;
|
|
||||||
/** Gateway manager which the resharder should be bound to. */
|
|
||||||
gatewayManager: GatewayManager;
|
|
||||||
/** Percentage at which resharding should occur.
|
|
||||||
* @default 80
|
|
||||||
*/
|
|
||||||
percentage?: number;
|
|
||||||
/** Whether the resharder should automatically switch to LARGE BOT SHARDING when the bot is above 100K servers. */
|
|
||||||
useOptimalLargeBotSharding?: boolean;
|
|
||||||
|
|
||||||
/** Function which can be used to fetch the current gateway information of the bot.
|
|
||||||
* This function is mainly used by the reshard checker.
|
|
||||||
*/
|
|
||||||
getGatewayBot(): Promise<GatewayBot>;
|
|
||||||
|
|
||||||
/** Function which is used to tell a Worker that it should identify a resharder Shard to the gateway and wait for further instructions.
|
|
||||||
* The worker should **NOT** process any events coming from this Shard.
|
|
||||||
*/
|
|
||||||
tellWorkerToPrepare(
|
|
||||||
gatewayManager: GatewayManager,
|
|
||||||
workerId: number,
|
|
||||||
shardId: number,
|
|
||||||
bucketId: number,
|
|
||||||
): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Handler that by default will check to see if resharding should occur. Can be overridden if you have multiple servers and you want to communicate through redis pubsub or whatever you prefer. */
|
|
||||||
export function activate(resharder: Resharder): void {
|
|
||||||
if (resharder.intervalId !== undefined) {
|
|
||||||
throw new Error('[RESHARDER] Cannot activate the resharder more than one time.');
|
|
||||||
}
|
|
||||||
|
|
||||||
resharder.intervalId = setInterval(async () => {
|
|
||||||
// gateway.debug("GW DEBUG", "[Resharding] Checking if resharding is needed.");
|
|
||||||
|
|
||||||
// TODO: is it possible to route this to REST?
|
|
||||||
const result = await resharder.getGatewayBot();
|
|
||||||
|
|
||||||
const percentage =
|
|
||||||
((result.shards - resharder.gateway.manager.totalShards) / resharder.gateway.manager.totalShards) * 100;
|
|
||||||
// Less than necessary% being used so do nothing
|
|
||||||
if (percentage < resharder.percentage) return;
|
|
||||||
|
|
||||||
// Don't have enough identify rate limits to reshard
|
|
||||||
if (result.sessionStartLimit.remaining < result.shards) return;
|
|
||||||
|
|
||||||
// MULTI-SERVER BOTS OVERRIDE THIS IF YOU NEED TO RESHARD SERVER BY SERVER
|
|
||||||
return resharder.reshard(result);
|
|
||||||
}, resharder.checkInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function reshard(resharder: Resharder, gatewayBot: GatewayBot) {
|
|
||||||
// oldGateway.debug("GW DEBUG", "[Resharding] Starting the reshard process.");
|
|
||||||
|
|
||||||
// Create a temporary gateway manager for easier handling.
|
|
||||||
const tmpManager = createGatewayManager({
|
|
||||||
gatewayBot: gatewayBot,
|
|
||||||
gatewayConfig: resharder.gateway.manager.gatewayConfig,
|
|
||||||
handleDiscordPayload: () => {},
|
|
||||||
tellWorkerToIdentify: resharder.tellWorkerToPrepare,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Begin resharding
|
|
||||||
|
|
||||||
// If more than 100K servers, begin switching to 16x sharding
|
|
||||||
if (resharder.useOptimalLargeBotSharding) {
|
|
||||||
// gateway.debug("GW DEBUG", "[Resharding] Using optimal large bot sharding solution.");
|
|
||||||
tmpManager.manager.totalShards = resharder.gateway.calculateTotalShards(resharder.gateway);
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpManager.spawnShards(tmpManager);
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
// TIMER TO KEEP CHECKING WHEN ALL SHARDS HAVE RESHARDED
|
|
||||||
const timer = setInterval(async () => {
|
|
||||||
const pending = await gateway.resharding.isPending(gateway, oldGateway);
|
|
||||||
// STILL PENDING ON SOME SHARDS TO BE CREATED
|
|
||||||
if (pending) return;
|
|
||||||
|
|
||||||
// ENABLE EVENTS ON NEW SHARDS AND IGNORE EVENTS ON OLD
|
|
||||||
const oldHandler = oldGateway.handleDiscordPayload;
|
|
||||||
gateway.handleDiscordPayload = oldHandler;
|
|
||||||
oldGateway.handleDiscordPayload = function (og, data, shardId) {
|
|
||||||
// ALLOW EXCEPTION FOR CHUNKING TO PREVENT REQUESTS FREEZING
|
|
||||||
if (data.t !== 'GUILD_MEMBERS_CHUNK') return;
|
|
||||||
oldHandler(og, data, shardId);
|
|
||||||
};
|
|
||||||
|
|
||||||
// STOP TIMER
|
|
||||||
clearInterval(timer);
|
|
||||||
await gateway.resharding.editGuildShardIds();
|
|
||||||
await gateway.resharding.closeOldShards(oldGateway);
|
|
||||||
gateway.debug('GW DEBUG', '[Resharding] Complete.');
|
|
||||||
resolve(gateway);
|
|
||||||
}, 30000);
|
|
||||||
}) as Promise<GatewayManager>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /** The handler to automatically reshard when necessary. */
|
|
||||||
// export async function resharder(
|
|
||||||
// oldGateway: GatewayManager,
|
|
||||||
// results: GatewayBot,
|
|
||||||
// ) {
|
|
||||||
// oldGateway.debug("GW DEBUG", "[Resharding] Starting the reshard process.");
|
|
||||||
|
|
||||||
// const gateway = createGatewayManager({
|
|
||||||
// ...oldGateway,
|
|
||||||
// // RESET THE SETS AND COLLECTIONS
|
|
||||||
// // cache: {
|
|
||||||
// // guildIds: new Set(),
|
|
||||||
// // loadingGuildIds: new Set(),
|
|
||||||
// // editedMessages: new Collection(),
|
|
||||||
// // },
|
|
||||||
// shards: new Collection(),
|
|
||||||
// // loadingShards: new Collection(),
|
|
||||||
// buckets: new Collection(),
|
|
||||||
// // utf8decoder: new TextDecoder(),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// for (const [key, value] of Object.entries(oldGateway)) {
|
|
||||||
// if (key === "handleDiscordPayload") {
|
|
||||||
// gateway.handleDiscordPayload = async function (_, data, shardId) {
|
|
||||||
// if (data.t === "READY") {
|
|
||||||
// const payload = data.d as DiscordReady;
|
|
||||||
// await gateway.resharding.markNewGuildShardId(payload.guilds.map((g) => BigInt(g.id)), shardId);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // USE ANY CUSTOMIZED OPTIONS FROM OLD GATEWAY
|
|
||||||
// // @ts-ignore TODO: fix this dynamical assignment
|
|
||||||
// gateway[key] = oldGateway[key as keyof typeof oldGateway];
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Begin resharding
|
|
||||||
// gateway.maxShards = results.shards;
|
|
||||||
// // FOR MANUAL SHARD CONTROL, OVERRIDE THIS SHARD ID!
|
|
||||||
// gateway.lastShardId = oldGateway.lastShardId === oldGateway.maxShards ? gateway.maxShards : oldGateway.lastShardId;
|
|
||||||
// gateway.shardsRecommended = results.shards;
|
|
||||||
// gateway.sessionStartLimitTotal = results.sessionStartLimit.total;
|
|
||||||
// gateway.sessionStartLimitRemaining = results.sessionStartLimit.remaining;
|
|
||||||
// gateway.sessionStartLimitResetAfter = results.sessionStartLimit.resetAfter;
|
|
||||||
// gateway.maxConcurrency = results.sessionStartLimit.maxConcurrency;
|
|
||||||
// // If more than 100K servers, begin switching to 16x sharding
|
|
||||||
// if (gateway.useOptimalLargeBotSharding) {
|
|
||||||
// gateway.debug("GW DEBUG", "[Resharding] Using optimal large bot sharding solution.");
|
|
||||||
// gateway.maxShards = gateway.calculateTotalShards(gateway.maxShards, results.sessionStartLimit.maxConcurrency);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// gateway.spawnShards(gateway, gateway.firstShardId);
|
|
||||||
|
|
||||||
// return new Promise((resolve) => {
|
|
||||||
// // TIMER TO KEEP CHECKING WHEN ALL SHARDS HAVE RESHARDED
|
|
||||||
// const timer = setInterval(async () => {
|
|
||||||
// const pending = await gateway.resharding.isPending(gateway, oldGateway);
|
|
||||||
// // STILL PENDING ON SOME SHARDS TO BE CREATED
|
|
||||||
// if (pending) return;
|
|
||||||
|
|
||||||
// // ENABLE EVENTS ON NEW SHARDS AND IGNORE EVENTS ON OLD
|
|
||||||
// const oldHandler = oldGateway.handleDiscordPayload;
|
|
||||||
// gateway.handleDiscordPayload = oldHandler;
|
|
||||||
// oldGateway.handleDiscordPayload = function (og, data, shardId) {
|
|
||||||
// // ALLOW EXCEPTION FOR CHUNKING TO PREVENT REQUESTS FREEZING
|
|
||||||
// if (data.t !== "GUILD_MEMBERS_CHUNK") return;
|
|
||||||
// oldHandler(og, data, shardId);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // STOP TIMER
|
|
||||||
// clearInterval(timer);
|
|
||||||
// await gateway.resharding.editGuildShardIds();
|
|
||||||
// await gateway.resharding.closeOldShards(oldGateway);
|
|
||||||
// gateway.debug("GW DEBUG", "[Resharding] Complete.");
|
|
||||||
// resolve(gateway);
|
|
||||||
// }, 30000);
|
|
||||||
// }) as Promise<GatewayManager>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/** Handler that by default will check all new shards are online in the new gateway. The handler can be overridden if you have multiple servers to communicate through redis pubsub or whatever you prefer. */
|
|
||||||
export async function resharderIsPending(
|
|
||||||
gateway: GatewayManager,
|
|
||||||
oldGateway: GatewayManager,
|
|
||||||
) {
|
|
||||||
for (let i = gateway.firstShardId; i < gateway.lastShardId; i++) {
|
|
||||||
const shard = gateway.shards.get(i);
|
|
||||||
if (!shard?.ready) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Handler that by default closes all shards in the old gateway. Can be overridden if you have multiple servers and you want to communicate through redis pubsub or whatever you prefer. */
|
|
||||||
export async function resharderCloseOldShards(oldGateway: GatewayManager) {
|
|
||||||
// SHUT DOWN ALL SHARDS IF NOTHING IN QUEUE
|
|
||||||
oldGateway.shards.forEach((shard) => {
|
|
||||||
// CLOSE THIS SHARD IT HAS NO QUEUE
|
|
||||||
if (!shard.processingQueue && !shard.queue.length) {
|
|
||||||
return oldGateway.closeWS(
|
|
||||||
shard.ws,
|
|
||||||
3066,
|
|
||||||
'Shard has been resharded. Closing shard since it has no queue.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// IF QUEUE EXISTS GIVE IT 5 MINUTES TO COMPLETE
|
|
||||||
setTimeout(() => {
|
|
||||||
oldGateway.closeWS(
|
|
||||||
shard.ws,
|
|
||||||
3066,
|
|
||||||
'Shard has been resharded. Delayed closing shard since it had a queue.',
|
|
||||||
);
|
|
||||||
}, 300000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// /** Handler that by default will check to see if resharding should occur. Can be overridden if you have multiple servers and you want to communicate through redis pubsub or whatever you prefer. */
|
|
||||||
// export async function startReshardingChecks(gateway: GatewayManager) {
|
|
||||||
// gateway.debug("GW DEBUG", "[Resharding] Checking if resharding is needed.");
|
|
||||||
//
|
|
||||||
// // TODO: is it possible to route this to REST?
|
|
||||||
// const results = (await fetch(`https://discord.com/api/gateway/bot`, {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bot ${gateway.token}`,
|
|
||||||
// },
|
|
||||||
// }).then((res) => res.json()).then((res) => transformGatewayBot(res))) as GatewayBot;
|
|
||||||
//
|
|
||||||
// const percentage = ((results.shards - gateway.maxShards) / gateway.maxShards) * 100;
|
|
||||||
// // Less than necessary% being used so do nothing
|
|
||||||
// if (percentage < gateway.reshardPercentage) return;
|
|
||||||
//
|
|
||||||
// // Don't have enough identify rate limits to reshard
|
|
||||||
// if (results.sessionStartLimit.remaining < results.shards) return;
|
|
||||||
//
|
|
||||||
// // MULTI-SERVER BOTS OVERRIDE THIS IF YOU NEED TO RESHARD SERVER BY SERVER
|
|
||||||
// return gateway.resharding.resharder(gateway, results);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/** Handler that by default will save the new shard id for each guild this becomes ready in new gateway. This can be overridden to save the shard ids in a redis cache layer or whatever you prefer. These ids will be used later to update all guilds. */
|
|
||||||
export async function markNewGuildShardId(guildIds: bigint[], shardId: number) {
|
|
||||||
// PLACEHOLDER TO LET YOU MARK A GUILD ID AND SHARD ID FOR LATER USE ONCE RESHARDED
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Handler that by default does not do anything since by default the library will not cache. */
|
|
||||||
export async function reshardingEditGuildShardIds() {
|
|
||||||
// PLACEHOLDER TO LET YOU UPDATE CACHED GUILDS
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
import { DiscordGatewayPayload } from '../../../api-types/discord.ts';
|
|
||||||
import { PickPartial } from '../../../api-types/shared.ts';
|
|
||||||
import { CreateShard, createShard } from '../shard/createShard.ts';
|
|
||||||
import { Shard, ShardGatewayConfig } from '../shard/types.ts';
|
|
||||||
|
|
||||||
// TODO: debug
|
|
||||||
|
|
||||||
/** This is a Shard manager.
|
|
||||||
* This does not manage a specific range of Shard but the provided Shards on create or when an identify is requested.
|
|
||||||
* The aim of this is to provide an easy to use manager which can be used by workers or any other kind of separate process.
|
|
||||||
*/
|
|
||||||
export type ShardManager = ReturnType<typeof createShardManager>;
|
|
||||||
|
|
||||||
/** Create a new Shard manager.
|
|
||||||
* This does not manage a specific range of Shard but the provided Shards on create or when an identify is requested.
|
|
||||||
* The aim of this is to provide an easy to use manager which can be used by workers or any other kind of separate process.
|
|
||||||
*/
|
|
||||||
export function createShardManager(options: CreateShardManager) {
|
|
||||||
return {
|
|
||||||
// ----------
|
|
||||||
// PROPERTIES
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/** Options which are used to create a new Shard. */
|
|
||||||
createShardOptions: {
|
|
||||||
...options.createShardOptions,
|
|
||||||
events: {
|
|
||||||
...options.createShardOptions?.events,
|
|
||||||
message: options.createShardOptions?.events?.message ?? options.handleMessage,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
/** Gateway configuration which is used when creating a Shard. */
|
|
||||||
gatewayConfig: options.gatewayConfig,
|
|
||||||
/** Managed Shards. */
|
|
||||||
shards: new Map(
|
|
||||||
options.shardIds.map((shardId) => {
|
|
||||||
const shard = createShard({
|
|
||||||
...options.createShardOptions,
|
|
||||||
id: shardId,
|
|
||||||
totalShards: options.totalShards,
|
|
||||||
gatewayConfig: options.gatewayConfig,
|
|
||||||
requestIdentify: async function () {
|
|
||||||
return await options.requestIdentify(shardId);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return [shardId, shard] as const;
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
/** Total amount of Shards used by the bot. */
|
|
||||||
totalShards: options.totalShards,
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
// METHODS
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/** Tell the manager to identify a Shard.
|
|
||||||
* If this Shard is not already managed this will also add the Shard to the manager.
|
|
||||||
*/
|
|
||||||
identify: async function (shardId: number) {
|
|
||||||
let shard = this.shards.get(shardId);
|
|
||||||
|
|
||||||
if (!shard) {
|
|
||||||
shard = createShard({
|
|
||||||
...this.createShardOptions,
|
|
||||||
id: shardId,
|
|
||||||
totalShards: this.totalShards,
|
|
||||||
gatewayConfig: this.gatewayConfig,
|
|
||||||
requestIdentify: async function () {
|
|
||||||
return await options.requestIdentify(shardId);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.shards.set(shardId, shard);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await shard.identify();
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Kill a shard.
|
|
||||||
* Close a shards connection to Discord's gateway (if any) and remove it from the manager.
|
|
||||||
*/
|
|
||||||
kill: async function (shardId: number) {
|
|
||||||
const shard = this.shards.get(shardId);
|
|
||||||
if (!shard) return;
|
|
||||||
|
|
||||||
this.shards.delete(shardId);
|
|
||||||
return await shard.shutdown();
|
|
||||||
},
|
|
||||||
|
|
||||||
/** This function communicates with the parent manager,
|
|
||||||
* in order to know whether this manager is allowed to identify a new shard.
|
|
||||||
*/
|
|
||||||
requestIdentify: options.requestIdentify,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateShardManager {
|
|
||||||
// ----------
|
|
||||||
// PROPERTIES
|
|
||||||
// ----------
|
|
||||||
/** Options which are used to create a new Shard. */
|
|
||||||
createShardOptions?: Omit<CreateShard, 'id' | 'totalShards' | 'requestIdentify' | 'gatewayConfig'>;
|
|
||||||
/** Gateway configuration which is used when creating a Shard. */
|
|
||||||
gatewayConfig: PickPartial<ShardGatewayConfig, 'token'>;
|
|
||||||
/** Ids of the Shards which should be managed. */
|
|
||||||
shardIds: number[];
|
|
||||||
/** Total amount of Shard used by the bot. */
|
|
||||||
totalShards: number;
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
// METHODS
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/** This function is used when a shard receives any message from Discord. */
|
|
||||||
handleMessage(shard: Shard, message: DiscordGatewayPayload): unknown;
|
|
||||||
|
|
||||||
/** This function communicates with the parent manager,
|
|
||||||
* in order to know whether this manager is allowed to identify a new shard. #
|
|
||||||
*/
|
|
||||||
requestIdentify(shardId: number): Promise<void>;
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
import { GatewayIntents } from '../../../api-types/shared.ts';
|
|
||||||
import { createLeakyBucket } from '../../util/bucket.ts';
|
|
||||||
import { createShard } from '../shard/createShard.ts';
|
|
||||||
import { Shard } from '../shard/types.ts';
|
|
||||||
import { createGatewayManager, GatewayManager } from './gatewayManager.ts';
|
|
||||||
|
|
||||||
/** Begin spawning shards. */
|
|
||||||
export function spawnShards(gateway: GatewayManager) {
|
|
||||||
// PREPARES THE MAX SHARD COUNT BY CONCURRENCY
|
|
||||||
// if (manager.resharding.useOptimalLargeBotSharding) {
|
|
||||||
// // gateway.debug("GW DEBUG", "[Spawning] Using optimal large bot sharding solution.");
|
|
||||||
// manager.manager.totalShards = manager.calculateTotalShards(
|
|
||||||
// manager,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// PREPARES ALL SHARDS IN SPECIFIC BUCKETS
|
|
||||||
gateway.prepareBuckets();
|
|
||||||
|
|
||||||
// SPREAD THIS OUT TO DIFFERENT WORKERS TO BEGIN STARTING UP
|
|
||||||
gateway.buckets.forEach((bucket, bucketId) => {
|
|
||||||
// gateway.debug("GW DEBUG", `2. Running forEach loop in spawnShards function.`);
|
|
||||||
|
|
||||||
for (const worker of bucket.workers) {
|
|
||||||
// gateway.debug("GW DEBUG", `3. Running for of loop in spawnShards function.`);
|
|
||||||
|
|
||||||
for (const shardId of worker.queue) {
|
|
||||||
gateway.tellWorkerToIdentify(worker.id, shardId, bucketId).catch(console.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import { delay } from '../../util/delay.ts';
|
|
||||||
import { GatewayManager } from './gatewayManager.ts';
|
|
||||||
|
|
||||||
export async function stop(gateway: GatewayManager, code: number, reason: string) {
|
|
||||||
gateway.manager.shards.forEach((shard) => shard.close(code, reason));
|
|
||||||
|
|
||||||
await delay(5000);
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
import { GatewayIntents } from '../../../api-types/shared.ts';
|
|
||||||
import { createShard } from '../shard/createShard.ts';
|
|
||||||
import { GatewayManager } from './gatewayManager.ts';
|
|
||||||
|
|
||||||
/** Allows users to hook in and change to communicate to different workers across different servers or anything they like. For example using redis pubsub to talk to other servers. */
|
|
||||||
export async function tellWorkerToIdentify(
|
|
||||||
gateway: GatewayManager,
|
|
||||||
_workerId: number,
|
|
||||||
shardId: number,
|
|
||||||
_bucketId: number,
|
|
||||||
): Promise<void> {
|
|
||||||
return await gateway.manager.identify(shardId);
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from './manager/mod.ts';
|
|
||||||
export * from './shard/mod.ts';
|
|
@ -1,9 +0,0 @@
|
|||||||
import { Shard } from './types.ts';
|
|
||||||
|
|
||||||
export function calculateSafeRequests(shard: Shard) {
|
|
||||||
// * 2 adds extra safety layer for discords OP 1 requests that we need to respond to
|
|
||||||
const safeRequests = shard.maxRequestsPerRateLimitTick -
|
|
||||||
Math.ceil(shard.rateLimitResetInterval / shard.heart.interval) * 2;
|
|
||||||
|
|
||||||
return safeRequests < 0 ? 0 : safeRequests;
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import { Shard } from './types.ts';
|
|
||||||
|
|
||||||
export function close(shard: Shard, code: number, reason: string): void {
|
|
||||||
if (shard.socket?.readyState !== WebSocket.OPEN) return;
|
|
||||||
|
|
||||||
return shard.socket?.close(code, reason);
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
import { Shard, ShardState } from './types.ts';
|
|
||||||
|
|
||||||
// TODO: Remove code marked WSL GATEWAY PATCH once a bug in bun is fixed:
|
|
||||||
// `https://github.com/Jarred-Sumner/bun/issues/521`
|
|
||||||
|
|
||||||
export async function connect(shard: Shard): Promise<void> {
|
|
||||||
let gotHello = false;
|
|
||||||
|
|
||||||
// Only set the shard to `Connecting` state,
|
|
||||||
// if the connection request does not come from an identify or resume action.
|
|
||||||
if (![ShardState.Identifying, ShardState.Resuming].includes(shard.state)) {
|
|
||||||
shard.state = ShardState.Connecting;
|
|
||||||
}
|
|
||||||
shard.events.connecting?.(shard);
|
|
||||||
|
|
||||||
// Explicitly setting the encoding to json, since we do not support ETF.
|
|
||||||
const socket = new WebSocket(`${shard.gatewayConfig.url}/?v=${shard.gatewayConfig.version}&encoding=json`);
|
|
||||||
shard.socket = socket;
|
|
||||||
|
|
||||||
// TODO: proper event handling
|
|
||||||
socket.onerror = (event) => console.log({ error: event });
|
|
||||||
|
|
||||||
socket.onclose = (event) => shard.handleClose(event);
|
|
||||||
|
|
||||||
socket.onmessage = (message) => {
|
|
||||||
// START WSL GATEWAY PATCH
|
|
||||||
gotHello = true;
|
|
||||||
// END WSL GATEWAY PATCH
|
|
||||||
shard.handleMessage(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
socket.onopen = () => {
|
|
||||||
// START WSL GATEWAY PATCH
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!gotHello) {
|
|
||||||
shard.handleMessage({
|
|
||||||
data: JSON.stringify({ t: null, s: null, op: 10, d: { heartbeat_interval: 41250 } }),
|
|
||||||
} as any);
|
|
||||||
}
|
|
||||||
}, 250);
|
|
||||||
// END WSL GATEWAY PATCH
|
|
||||||
|
|
||||||
// Only set the shard to `Unidentified` state,
|
|
||||||
// if the connection request does not come from an identify or resume action.
|
|
||||||
if (![ShardState.Identifying, ShardState.Resuming].includes(shard.state)) {
|
|
||||||
shard.state = ShardState.Unidentified;
|
|
||||||
}
|
|
||||||
shard.events.connected?.(shard);
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,333 +0,0 @@
|
|||||||
import { identify } from './identify.ts';
|
|
||||||
import { handleMessage } from './handleMessage.ts';
|
|
||||||
import {
|
|
||||||
DEFAULT_HEARTBEAT_INTERVAL,
|
|
||||||
GATEWAY_RATE_LIMIT_RESET_INTERVAL,
|
|
||||||
MAX_GATEWAY_REQUESTS_PER_INTERVAL,
|
|
||||||
Shard,
|
|
||||||
ShardEvents,
|
|
||||||
ShardGatewayConfig,
|
|
||||||
ShardHeart,
|
|
||||||
ShardSocketCloseCodes,
|
|
||||||
ShardSocketRequest,
|
|
||||||
ShardState,
|
|
||||||
} from './types.ts';
|
|
||||||
import { startHeartbeating } from './startHeartbeating.ts';
|
|
||||||
import { stopHeartbeating } from './stopHeartbeating.ts';
|
|
||||||
import { resume } from './resume.ts';
|
|
||||||
import { createLeakyBucket, LeakyBucket } from '../../util/bucket.ts';
|
|
||||||
import { calculateSafeRequests } from './calculateSafeRequests.ts';
|
|
||||||
import { send } from './send.ts';
|
|
||||||
import { handleClose } from './handleClose.ts';
|
|
||||||
import { connect } from './connect.ts';
|
|
||||||
import { close } from './close.ts';
|
|
||||||
import { shutdown } from './shutdown.ts';
|
|
||||||
import { isOpen } from './isOpen.ts';
|
|
||||||
import { DiscordGatewayPayload, DiscordStatusUpdate } from '../../../api-types/discord.ts';
|
|
||||||
import { GatewayIntents, PickPartial } from '../../../api-types/shared.ts';
|
|
||||||
import { API_VERSION } from '../../util/constants.ts';
|
|
||||||
|
|
||||||
// TODO: debug
|
|
||||||
// TODO: function overwrite
|
|
||||||
// TODO: improve shard event resolving
|
|
||||||
|
|
||||||
/** */
|
|
||||||
export function createShard(
|
|
||||||
options: CreateShard,
|
|
||||||
) {
|
|
||||||
// This is done for performance reasons
|
|
||||||
const calculateSafeRequestsOverwritten = options.calculateSafeRequests ?? calculateSafeRequests;
|
|
||||||
const closeOverwritten = options.close ?? close;
|
|
||||||
const connectOverwritten = options.connect ?? connect;
|
|
||||||
const identifyOverwritten = options.identify ?? identify;
|
|
||||||
const sendOverwritten = options.send ?? send;
|
|
||||||
const shutdownOverwritten = options.shutdown ?? shutdown;
|
|
||||||
const resumeOverwritten = options.resume ?? resume;
|
|
||||||
const handleCloseOverwritten = options.handleClose ?? handleClose;
|
|
||||||
const handleMessageOverwritten = options.handleMessage ?? handleMessage;
|
|
||||||
const isOpenOverwritten = options.isOpen ?? isOpen;
|
|
||||||
const startHeartbeatingOverwritten = options.startHeartbeating ?? startHeartbeating;
|
|
||||||
const stopHeartbeatingOverwritten = options.stopHeartbeating ?? stopHeartbeating;
|
|
||||||
|
|
||||||
return {
|
|
||||||
// ----------
|
|
||||||
// PROPERTIES
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/** The gateway configuration which is used to connect to Discord. */
|
|
||||||
gatewayConfig: {
|
|
||||||
compress: options.gatewayConfig.compress ?? false,
|
|
||||||
intents: options.gatewayConfig.intents ?? 0,
|
|
||||||
properties: {
|
|
||||||
os: options.gatewayConfig?.properties?.os ?? 'linux',
|
|
||||||
browser: options.gatewayConfig?.properties?.browser ?? 'Discordeno',
|
|
||||||
device: options.gatewayConfig?.properties?.device ?? 'Discordeno',
|
|
||||||
},
|
|
||||||
token: options.gatewayConfig.token,
|
|
||||||
url: options.gatewayConfig.url ?? 'wss://gateway.discord.gg',
|
|
||||||
version: options.gatewayConfig.version ?? API_VERSION,
|
|
||||||
} as ShardGatewayConfig,
|
|
||||||
/** This contains all the heartbeat information */
|
|
||||||
heart: {
|
|
||||||
acknowledged: false,
|
|
||||||
interval: DEFAULT_HEARTBEAT_INTERVAL,
|
|
||||||
} as ShardHeart,
|
|
||||||
/** Id of the shard. */
|
|
||||||
id: options.id,
|
|
||||||
/** The maximum of requests which can be send to discord per rate limit tick.
|
|
||||||
* Typically this value should not be changed.
|
|
||||||
*/
|
|
||||||
maxRequestsPerRateLimitTick: MAX_GATEWAY_REQUESTS_PER_INTERVAL,
|
|
||||||
/** The previous payload sequence number. */
|
|
||||||
previousSequenceNumber: options.previousSequenceNumber || null,
|
|
||||||
/** In which interval (in milliseconds) the gateway resets it's rate limit. */
|
|
||||||
rateLimitResetInterval: GATEWAY_RATE_LIMIT_RESET_INTERVAL,
|
|
||||||
/** Current session id of the shard if present. */
|
|
||||||
sessionId: undefined as string | undefined,
|
|
||||||
/** This contains the WebSocket connection to Discord, if currently connected. */
|
|
||||||
socket: undefined as WebSocket | undefined,
|
|
||||||
/** Current internal state of the shard. */
|
|
||||||
state: ShardState.Offline,
|
|
||||||
/** The total amount of shards which are used to communicate with Discord. */
|
|
||||||
totalShards: options.totalShards,
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
// METHODS
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
/** The shard related event handlers. */
|
|
||||||
events: options.events ?? {} as ShardEvents,
|
|
||||||
|
|
||||||
/** Calculate the amount of requests which can safely be made per rate limit interval,
|
|
||||||
* before the gateway gets disconnected due to an exceeded rate limit.
|
|
||||||
*/
|
|
||||||
calculateSafeRequests: function () {
|
|
||||||
return calculateSafeRequestsOverwritten(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Close the socket connection to discord if present. */
|
|
||||||
close: function (code: number, reason: string) {
|
|
||||||
return closeOverwritten(this, code, reason);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Connect the shard with the gateway and start heartbeating.
|
|
||||||
* This will not identify the shard to the gateway.
|
|
||||||
*/
|
|
||||||
connect: async function () {
|
|
||||||
return await connectOverwritten(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Identify the shard to the gateway.
|
|
||||||
* If not connected, this will also connect the shard to the gateway.
|
|
||||||
*/
|
|
||||||
identify: async function () {
|
|
||||||
return await identifyOverwritten(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Check whether the connection to Discord is currently open. */
|
|
||||||
isOpen: function () {
|
|
||||||
return isOpenOverwritten(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Function which can be overwritten in order to get the shards presence. */
|
|
||||||
// This function allows to be async, in case the devs create the presence based on eg. database values.
|
|
||||||
// Passing the shard's id there to make it easier for the dev to use this function.
|
|
||||||
makePresence: options.makePresence,
|
|
||||||
|
|
||||||
/** Attempt to resume the previous shards session with the gateway. */
|
|
||||||
resume: async function () {
|
|
||||||
return await resumeOverwritten(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Send a message to Discord.
|
|
||||||
* @param {boolean} [highPriority=false] - Whether this message should be send asap.
|
|
||||||
*/
|
|
||||||
send: async function (message: ShardSocketRequest, highPriority: boolean = false) {
|
|
||||||
return await sendOverwritten(this, message, highPriority);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Shutdown the shard.
|
|
||||||
* Forcefully disconnect the shard from Discord.
|
|
||||||
* The shard may not attempt to reconnect with Discord.
|
|
||||||
*/
|
|
||||||
shutdown: async function () {
|
|
||||||
return await shutdownOverwritten(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @private Internal shard bucket.
|
|
||||||
* Only access this if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Bucket for handling shard request rate limits.
|
|
||||||
*/
|
|
||||||
bucket: createLeakyBucket({
|
|
||||||
max: MAX_GATEWAY_REQUESTS_PER_INTERVAL,
|
|
||||||
refillInterval: GATEWAY_RATE_LIMIT_RESET_INTERVAL,
|
|
||||||
refillAmount: MAX_GATEWAY_REQUESTS_PER_INTERVAL,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/** @private Internal shard function.
|
|
||||||
* Only use this function if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Handle a gateway connection close.
|
|
||||||
*/
|
|
||||||
handleClose: async function (close: CloseEvent) {
|
|
||||||
return await handleCloseOverwritten(this, close);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @private Internal shard function.
|
|
||||||
* Only use this function if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Handle an incoming gateway message.
|
|
||||||
*/
|
|
||||||
handleMessage: async function (message: MessageEvent<any>) {
|
|
||||||
return await handleMessageOverwritten(this, message);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** This function communicates with the management process, in order to know whether its free to identify. */
|
|
||||||
requestIdentify: async function () {
|
|
||||||
return await options.requestIdentify(this.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @private Internal state.
|
|
||||||
* Only use this if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Cache for pending gateway requests which should have been send while the gateway went offline.
|
|
||||||
*/
|
|
||||||
offlineSendQueue: [] as ((_?: unknown) => void)[],
|
|
||||||
|
|
||||||
/** @private Internal shard map.
|
|
||||||
* Only use this map if you know what you are doing.
|
|
||||||
*
|
|
||||||
* This is used to resolve internal waiting states.
|
|
||||||
* Mapped by SelectedEvents => ResolveFunction
|
|
||||||
*/
|
|
||||||
resolves: new Map<'READY' | 'RESUMED' | 'INVALID_SESSION', (payload: DiscordGatewayPayload) => void>(),
|
|
||||||
|
|
||||||
/** @private Internal shard function.
|
|
||||||
* Only use this function if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Start sending heartbeat payloads to Discord in the provided interval.
|
|
||||||
*/
|
|
||||||
startHeartbeating: function (interval: number) {
|
|
||||||
return startHeartbeatingOverwritten(this, interval);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @private Internal shard function.
|
|
||||||
* Only use this function if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Stop the heartbeating process with discord.
|
|
||||||
*/
|
|
||||||
stopHeartbeating: function () {
|
|
||||||
return stopHeartbeatingOverwritten(this);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateShard {
|
|
||||||
/** Id of the shard which should be created. */
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
/** Gateway configuration for the shard. */
|
|
||||||
gatewayConfig: PickPartial<ShardGatewayConfig, 'token'>;
|
|
||||||
|
|
||||||
/** The total amount of shards which are used to communicate with Discord. */
|
|
||||||
totalShards: number;
|
|
||||||
|
|
||||||
/** This function communicates with the management process, in order to know whether its free to identify.
|
|
||||||
* When this function resolves, this means that the shard is allowed to send an identify payload to discord.
|
|
||||||
*/
|
|
||||||
requestIdentify: (shardId: number) => Promise<void>;
|
|
||||||
|
|
||||||
/** Calculate the amount of requests which can safely be made per rate limit interval,
|
|
||||||
* before the gateway gets disconnected due to an exceeded rate limit.
|
|
||||||
*/
|
|
||||||
calculateSafeRequests?: typeof calculateSafeRequests;
|
|
||||||
|
|
||||||
/** Close the socket connection to discord if present. */
|
|
||||||
close?: typeof close;
|
|
||||||
|
|
||||||
/** Connect the shard with the gateway and start heartbeating.
|
|
||||||
* This will not identify the shard to the gateway.
|
|
||||||
*/
|
|
||||||
connect?: typeof connect;
|
|
||||||
|
|
||||||
/** @private Internal shard function.
|
|
||||||
* Only use this function if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Handle a gateway connection close.
|
|
||||||
*/
|
|
||||||
handleClose?: typeof handleClose;
|
|
||||||
|
|
||||||
/** @private Internal shard function.
|
|
||||||
* Only use this function if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Handle an incoming gateway message.
|
|
||||||
*/
|
|
||||||
handleMessage?: typeof handleMessage;
|
|
||||||
|
|
||||||
/** Identify the shard to the gateway.
|
|
||||||
* If not connected, this will also connect the shard to the gateway.
|
|
||||||
*/
|
|
||||||
identify?: typeof identify;
|
|
||||||
|
|
||||||
/** Check whether the connection to Discord is currently open. */
|
|
||||||
isOpen?: typeof isOpen;
|
|
||||||
|
|
||||||
/** Function which can be overwritten in order to get the shards presence. */
|
|
||||||
makePresence?(shardId: number): Promise<DiscordStatusUpdate> | DiscordStatusUpdate;
|
|
||||||
|
|
||||||
/** The maximum of requests which can be send to discord per rate limit tick.
|
|
||||||
* Typically this value should not be changed.
|
|
||||||
*/
|
|
||||||
maxRequestsPerRateLimitTick?: number;
|
|
||||||
|
|
||||||
/** The previous payload sequence number. */
|
|
||||||
previousSequenceNumber?: number;
|
|
||||||
|
|
||||||
/** In which interval (in milliseconds) the gateway resets it's rate limit. */
|
|
||||||
rateLimitResetInterval?: number;
|
|
||||||
|
|
||||||
/** Attempt to resume the previous shards session with the gateway. */
|
|
||||||
resume?: typeof resume;
|
|
||||||
|
|
||||||
/** Send a message to Discord.
|
|
||||||
* @param {boolean} [highPriority=false] - Whether this message should be send asap.
|
|
||||||
*/
|
|
||||||
send?: typeof send;
|
|
||||||
|
|
||||||
/** Shutdown the shard.
|
|
||||||
* Forcefully disconnect the shard from Discord.
|
|
||||||
* The shard may not attempt to reconnect with Discord.
|
|
||||||
*/
|
|
||||||
shutdown?: typeof shutdown;
|
|
||||||
|
|
||||||
/** @private Internal shard function.
|
|
||||||
* Only use this function if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Start sending heartbeat payloads to Discord in the provided interval.
|
|
||||||
*/
|
|
||||||
startHeartbeating?: typeof startHeartbeating;
|
|
||||||
|
|
||||||
/** Current internal state of the shard. */
|
|
||||||
state?: ShardState;
|
|
||||||
|
|
||||||
/** @private Internal shard function.
|
|
||||||
* Only use this function if you know what you are doing.
|
|
||||||
*
|
|
||||||
* Stop the heartbeating process with discord.
|
|
||||||
*/
|
|
||||||
stopHeartbeating?: typeof stopHeartbeating;
|
|
||||||
|
|
||||||
/** The shard related event handlers. */
|
|
||||||
events?: ShardEvents;
|
|
||||||
/** This contains all the heartbeat information */
|
|
||||||
heart?: ShardHeart;
|
|
||||||
/** Bucket for handling shard request rate limits. */
|
|
||||||
bucket?: LeakyBucket;
|
|
||||||
/** Cache for pending gateway requests which should have been send while the gateway went offline. */
|
|
||||||
offlineSendQueue?: ShardSocketRequest[];
|
|
||||||
/** This is used to resolve internal waiting states.
|
|
||||||
* Mapped by SelectedEvents => ResolveFunction
|
|
||||||
*/
|
|
||||||
resolves?: Shard['resolves'];
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user