Next: A complete Biscuit rewrite (#131)

* FINALLY WE REACHED AN AGREEMENT

* chore: d-types adapt

* websocket sucks, rest

* changes

* new look for core

* 💀

* fix cdn routes, more structures

Co-authored-by: Free 公園 <FreeAoi@users.noreply.github.com>

* CDN routes

* chore: change to rome

Co-authored-by: Free 公園 <FreeAoi@users.noreply.github.com>

* Oh shit, here we go again

Co-authored-by: Free 公園 <FreeAoi@users.noreply.github.com>

* fixes

* mixin, handler events, ws
Co-authored-by: Yuzu
<yuzudev@users.noreply.github.com>
Co-authored-by: Free 公園 <FreeAoi@users.noreply.github.com>

* change type

* Error system (#133)

* Co-authored-by: Free 公園 <FreeAoi@users.noreply.github.com>

* chore: biscuit rebase

* token leak goes brrrr

* fix: events

* chore: road to raw data

* fix: managers typing

* chore: fix gateway typing

* feat: helpers

* style: linter

* Types for routes (#134)

* typing for routes

* managers

Co-authored-by: Marcos Susaña <marcosjgs03@gmail.com>

* Types for routes (#134)

* I wanna cry

* Next (#136)

* Merge #137

* chore: lineWidth to 140

* chore: README update

---------

Co-authored-by: Yuzu <yuzuru@programmer.net>
Co-authored-by: Free 公園 <FreeAoi@users.noreply.github.com>
Co-authored-by: ThisIsAName <46913407+NejireSupremacy@users.noreply.github.com>
Co-authored-by: MARCROCK22 <57925328+MARCROCK22@users.noreply.github.com>
Co-authored-by: MARCROCK22 <marcos22dev@gmail.com>
This commit is contained in:
Marcos Susaña 2023-05-29 22:34:47 -04:00 committed by GitHub
parent 13bf6a0101
commit 87cdad0b03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
169 changed files with 5810 additions and 18640 deletions

View File

@ -5,9 +5,9 @@ charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
indent_size = 4
indent_style = space
indent_size = 2
quote_type = single
[Makefile]
indent_style = tab
indent_style = space

View File

@ -1,5 +0,0 @@
node_modules/
build
dist
examples/**
tsup.config.ts

View File

@ -1,224 +0,0 @@
root: true
env:
browser: true
es2020: true
node: true
extends:
- 'prettier'
- 'eslint:recommended'
- 'plugin:@typescript-eslint/eslint-recommended'
- 'plugin:@typescript-eslint/recommended'
ignorePatterns:
- 'node_modules'
- 'dist'
- 'coverage'
- '**/*.js'
- '**/*.d.ts'
- '__tests__'
- '__test__'
parser: '@typescript-eslint/parser'
parserOptions:
project: './packages/**/tsconfig.json'
sourceType: 'module'
plugins:
- '@typescript-eslint'
# silly eslint bug
overrides:
- files: ['*.ts']
rules:
no-undef: 'off'
rules:
'@typescript-eslint/consistent-type-imports': 'error'
'@typescript-eslint/no-duplicate-imports': 'error'
'@typescript-eslint/prefer-optional-chain': 'error'
'@typescript-eslint/no-explicit-any': 'off'
'@typescript-eslint/explicit-function-return-type': 'off'
'@typescript-eslint/no-non-null-assertion': 'off'
'@typescript-eslint/ban-ts-comment': 'off'
'@typescript-eslint/no-unused-vars': 'off'
'@typescript-eslint/naming-convention':
- 'error'
- selector: 'default'
format: null
- selector: 'variable'
format:
- 'camelCase'
- 'PascalCase'
- 'UPPER_CASE'
- selector: 'typeLike'
format: ['PascalCase']
'@typescript-eslint/explicit-module-boundary-types': 'off'
'@typescript-eslint/no-empty-interface': 'off'
'@typescript-eslint/adjacent-overload-signatures': 'error'
'@typescript-eslint/consistent-type-assertions': 'error'
'@typescript-eslint/no-array-constructor': 'error'
'@typescript-eslint/no-empty-function': 'error'
'@typescript-eslint/no-inferrable-types': 'error'
'@typescript-eslint/no-misused-new': 'error'
'@typescript-eslint/no-namespace': 'error'
'@typescript-eslint/no-this-alias': 'error'
'@typescript-eslint/no-use-before-define': 'off'
'@typescript-eslint/no-var-requires': 'error'
'@typescript-eslint/triple-slash-reference': 'error'
'@typescript-eslint/type-annotation-spacing': 'error'
'@typescript-eslint/array-type': 'error'
'@typescript-eslint/no-unnecessary-qualifier': 'error'
'@typescript-eslint/no-unnecessary-type-arguments': 'off' # disabled as it started to be buggy
'@typescript-eslint/quotes':
- 'error'
- 'single'
- avoidEscape: true
allowTemplateLiterals: true
'@typescript-eslint/semi':
- 'error'
- 'always'
'@typescript-eslint/no-useless-constructor': 'error'
'@typescript-eslint/no-redeclare':
- 'error'
'@typescript-eslint/member-delimiter-style':
- 'error'
- multiline:
delimiter: 'semi'
requireLast: true
singleline:
delimiter: 'semi'
requireLast: false
'@typescript-eslint/space-before-function-paren':
- 'error'
- anonymous: 'always'
named: 'never'
asyncArrow: 'always'
'arrow-parens':
- 'error'
- 'as-needed'
'no-var': 'error'
'prefer-const': 'error'
'prefer-rest-params': 'error'
'prefer-spread': 'error'
'constructor-super': 'error'
'for-direction': 'error'
'getter-return': 'error'
'no-async-promise-executor': 'error'
'no-case-declarations': 'error'
'no-class-assign': 'error'
'no-compare-neg-zero': 'error'
'no-cond-assign': 'error'
'no-const-assign': 'error'
'no-constant-condition': 'error'
'no-control-regex': 'error'
'no-debugger': 'error'
'no-delete-var': 'error'
'no-dupe-args': 'error'
'no-dupe-keys': 'error'
'no-duplicate-case': 'error'
'no-empty': 'error'
'no-empty-character-class': 'error'
'no-empty-pattern': 'error'
'no-ex-assign': 'error'
'no-extra-boolean-cast': 'error'
'no-extra-semi': 'error'
'no-fallthrough': 'error'
'no-func-assign': 'error'
'no-global-assign': 'error'
'no-inner-declarations': 'error'
'no-invalid-regexp': 'error'
'no-irregular-whitespace': 'error'
'no-misleading-character-class': 'error'
'no-mixed-spaces-and-tabs': 'error'
'no-new-symbol': 'error'
'no-obj-calls': 'error'
'no-octal': 'error'
'no-prototype-builtins': 'error'
'no-redeclare': 'off'
'no-regex-spaces': 'error'
'no-self-assign': 'error'
'no-shadow-restricted-names': 'error'
'no-sparse-arrays': 'error'
'no-this-before-super': 'error'
'no-undef': 'error'
'no-unexpected-multiline': 'error'
'no-unreachable': 'error'
'no-unsafe-finally': 'error'
'no-unsafe-negation': 'error'
'no-unused-labels': 'error'
'no-useless-catch': 'error'
'no-useless-escape': 'error'
'no-with': 'error'
'require-yield': 'error'
'use-isnan': 'error'
'valid-typeof': 'error'
# 'comma-dangle': ['error', 'never']
'dot-notation': 'error'
'eol-last': 'error'
eqeqeq:
- 'error'
- 'always'
- 'null': 'ignore'
'no-console': 'warn'
'no-duplicate-imports': 'off'
'no-multiple-empty-lines': 'error'
'no-throw-literal': 'error'
'no-trailing-spaces': 'error'
'no-undef-init': 'error'
'object-shorthand': 'error'
'quote-props':
- 'error'
- 'consistent-as-needed'
'spaced-comment': 'error'
yoda: 'error'
curly: 'error'
'object-curly-spacing':
- 'error'
- 'always'
'lines-between-class-members':
- 'error'
- 'always'
- exceptAfterSingleLine: true
'no-else-return': 'error'
# always
'padded-blocks':
- 'error'
- classes: 'never'
'block-spacing':
- 'error'
- 'always'
'space-before-blocks':
- 'error'
- 'always'
'brace-style':
- 'error'
- '1tbs'
- allowSingleLine: true
'keyword-spacing':
- 'error'
- before: true
after: true
'space-in-parens':
- 'error'
- 'never'
# settings: {}

View File

@ -1,2 +0,0 @@
arrowParens: 'avoid'
singleQuote: true

View File

@ -6,22 +6,21 @@
"scripts": {
"build": "turbo run build",
"clean": "turbo run clean",
"lint": "turbo run lint",
"check": "rome check ./packages/",
"check:apply": "rome check ./packages/ --apply",
"lint": "rome format ./packages/ --write --quote-style single --trailing-comma none",
"dev": "turbo run dev --parallel"
},
"engines": {
"npm": ">=7.0.0",
"node": ">=14.0.0"
"node": ">=16.0.0"
},
"devDependencies": {
"@changesets/cli": "^2.24.1",
"@types/node": "^18.6.3",
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
"eslint": "^8.21.0",
"eslint-config-prettier": "^8.5.0",
"rome": "^12.0.0",
"turbo": "^1.4.2",
"typescript": "^4.7.4",
"@changesets/cli": "^2.24.1"
"typescript": "^5.0.4"
},
"packageManager": "npm@8.14.0",
"bugs": {
@ -57,6 +56,10 @@
{
"name": "Drylozu",
"url": "https://github.com/Drylozu"
},
{
"name": "FreeAoi",
"url": "https://github.com/FreeAoi"
}
],
"homepage": "https://biscuitjs.com",

View File

@ -1,62 +0,0 @@
# @biscuitland/api-types
## 2.3.0
### Minor Changes
- fix TODO
## 2.2.3
### Patch Changes
- bug fixes
## 2.2.2
### Patch Changes
- rename guildLocales to guildLocale in interactions
## 2.2.1
### Patch Changes
- select menu options now can be empty since the latest Discord API update
## 2.2.0
### Minor Changes
- Functionality to delete ephemeral messages added, select menus were updated
## 2.1.2
### Patch Changes
- minor changes
## 2.1.1
### Patch Changes
- dumb hotfix that LH asked for (blame Yuzu)
## 2.1.0
### Minor Changes
- Changes to cache and forum channels ✨
- Forum channels and updates to @biscuitland/cache
## 2.0.6
### Patch Changes
- Minor fixes
## 2.0.5
### Major Changes
- publish

View File

@ -1,38 +0,0 @@
# @biscuitland/api-types
## Most importantly, api-types is:
1:1 type definitions package for the [Discord](https://discord.com/developers/docs/intro) API.
[<img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white">](https://github.com/oasisjs/biscuit)
[<img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white">](https://discord.gg/XNw2RZFzaP)
<img align="right" src="https://raw.githubusercontent.com/oasisjs/biscuit/main/assets/icon.svg" alt="biscuit"/>
## Install (for [node18](https://nodejs.org/en/download/))
```sh-session
npm install @biscuitland/api-types
yarn add @biscuitland/api-types
```
## Example
```ts
import type { DiscordUser } from '@biscuitland/api-types';
```
## Example for [Deno](https://deno.land/)
```ts
import type { DiscordUser } from "https://unpkg.com/@biscuitland/api-types@2.0.5/dist/index.d.ts";
```
We deliver this package through [unpkg](https://unpkg.com/) and it does contain constants and routes too
## Links
- [Website](https://biscuitjs.com/)
- [Documentation](https://docs.biscuitjs.com/)
- [Discord](https://discord.gg/XNw2RZFzaP)
- [core](https://www.npmjs.com/package/@biscuitland/core) | [cache](https://www.npmjs.com/package/@biscuitland/cache) | [rest](https://www.npmjs.com/package/@biscuitland/rest) | [ws](https://www.npmjs.com/package/@biscuitland/ws) | [helpers](https://www.npmjs.com/package/@biscuitland/helpers)

View File

@ -1,71 +0,0 @@
{
"name": "@biscuitland/api-types",
"version": "2.3.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"build": "tsup",
"clean": "rm -rf dist && rm -rf .turbo",
"dev": "tsup --watch"
},
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
},
"require": "./dist/index.js"
}
},
"devDependencies": {
"tsup": "^6.1.3"
},
"license": "Apache-2.0",
"author": "Yuzuru <yuzuru@programmer.net>",
"contributors": [
{
"name": "Yuzuru",
"url": "https://github.com/yuzudev",
"author": true
},
{
"name": "miia",
"url": "https://github.com/dragurimu"
},
{
"name": "n128",
"url": "https://github.com/nicolito128"
},
{
"name": "socram03",
"url": "https://github.com/socram03"
},
{
"name": "Drylozu",
"url": "https://github.com/Drylozu"
}
],
"homepage": "https://biscuitjs.com",
"repository": {
"type": "git",
"url": "git+https://github.com/oasisjs/biscuit.git"
},
"bugs": {
"url": "https://github.com/oasisjs/biscuit"
},
"keywords": [
"api",
"discord",
"bots",
"typescript",
"botdev"
],
"publishConfig": {
"access": "public"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
export * as Constants from './utils/constants';
export * from './utils/routes';
export * from './v10/index';
export * from './common';

View File

@ -1,29 +0,0 @@
import type { Snowflake } from '../common';
import { baseEndpoints as Endpoints } from './constants';
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}`;
}

View File

@ -1,25 +0,0 @@
/** https://discord.com/developers/docs/reference#api-reference-base-url */
export const BASE_URL = 'https://discord.com/api';
/** https://discord.com/developers/docs/reference#api-versioning-api-versions */
export const API_VERSION = 10;
/** https://github.com/oasisjs/biscuit/releases */
export const BISCUIT_VERSION = '2.0.5';
/** https://discord.com/developers/docs/reference#user-agent */
export const USER_AGENT = `DiscordBot (https://github.com/oasisjs/biscuit, v${BISCUIT_VERSION})`;
/** https://discord.com/developers/docs/reference#image-formatting-image-base-url */
export const IMAGE_BASE_URL = 'https://cdn.discordapp.com';
// This can be modified by big brain bots and use a proxy
export const baseEndpoints = {
BASE_URL: `${BASE_URL}/v${API_VERSION}`,
CDN_URL: IMAGE_BASE_URL
};
export const SLASH_COMMANDS_NAME_REGEX = /^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u;
export const CONTEXT_MENU_COMMANDS_NAME_REGEX = /^[\w-\s]{1,32}$/;
export const CHANNEL_MENTION_REGEX = /<#[0-9]+>/g;
export const DISCORD_SNOWFLAKE_REGEX = /^(?<id>\d{17,19})$/;

View File

@ -1,519 +0,0 @@
import type { AuditLogEvents, Snowflake } from '../common';
export * from './cdn';
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}`;
}
export interface ListGuildMembers {
limit?: number;
after?: string;
}
export function GUILD_MEMBERS(guildId: Snowflake, options?: ListGuildMembers) {
let url = `/guilds/${guildId}/members?`;
if (options?.limit) { url += `limit=${options.limit}`; }
if (options?.after) { url += `&after=${options.after}`; }
return url;
}
/** 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, emojiId?: Snowflake): string {
if (emojiId) { return `/guilds/${guildId}/emojis/${emojiId}`; }
return `/guilds/${guildId}/emojis`;
}
export interface GetAuditLogs {
userId?: Snowflake;
actionType?: AuditLogEvents;
before?: Snowflake;
limit?: number;
}
export function GUILD_AUDIT_LOGS(guildId: Snowflake, options?: GetAuditLogs) {
let url = `/guilds/${guildId}/audit-logs?`;
if (options) {
const obj = {
user_id: options.userId,
action_type: options.actionType,
before: options.before,
limit: options.limit
};
for (const [key, value] of Object.entries(obj)) {
if (!value) { continue; }
url += `&${key}=${value}`;
}
}
return url;
}
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 += `thread_id=${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 += `thread_id=${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 += `?thread_id=${options.threadId}`; }
if (options?.wait && options.threadId) { url += `?wait=${options.wait}&thread_id=${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`;
}
/**
* @link https://discord.com/developers/docs/resources/guild#get-guild-channels
* @param guildId The guild id.
* @returns Get guild channels url.
*/
export function GUILD_CHANNELS(guildId: Snowflake): string {
return `/guilds/${guildId}/channels`;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,95 +0,0 @@
# @biscuitland/cache
## 2.3.0
### Minor Changes
- fix TODO
### Patch Changes
- Updated dependencies
- @biscuitland/api-types@2.3.0
## 2.2.3
### Patch Changes
- bug fixes
- Updated dependencies
- @biscuitland/api-types@2.2.3
## 2.2.2
### Patch Changes
- rename guildLocales to guildLocale in interactions
- Updated dependencies
- @biscuitland/api-types@2.2.2
## 2.2.1
### Patch Changes
- select menu options now can be empty since the latest Discord API update
- Updated dependencies
- @biscuitland/api-types@2.2.1
## 2.2.0
### Minor Changes
- Functionality to delete ephemeral messages added, select menus were updated
### Patch Changes
- Updated dependencies
- @biscuitland/api-types@2.2.0
## 2.1.2
### Patch Changes
- minor changes
- Updated dependencies
- @biscuitland/api-types@2.1.2
## 2.1.1
### Patch Changes
- dumb hotfix that LH asked for (blame Yuzu)
- Updated dependencies
- @biscuitland/api-types@2.1.1
## 2.1.0
### Minor Changes
- Changes to cache and forum channels ✨
- Forum channels and updates to @biscuitland/cache
### Patch Changes
- Updated dependencies
- Updated dependencies
- @biscuitland/api-types@2.1.0
## 2.0.6
### Patch Changes
- Minor fixes
- Updated dependencies
- @biscuitland/api-types@2.0.6
## 2.0.5
### Major Changes
- publish
### Patch Changes
- Updated dependencies
- @biscuitland/api-types@2.0.5

View File

@ -1,39 +0,0 @@
# @biscuitland/cache
## Most importantly, biscuit's cache is:
A resource control cache layer, based on carriers and resource-intensive policies
[<img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white">](https://github.com/oasisjs/biscuit)
[<img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white">](https://discord.gg/XNw2RZFzaP)
<img align="right" src="https://raw.githubusercontent.com/oasisjs/biscuit/main/assets/icon.svg" alt="biscuit"/>
## Install (for [node18](https://nodejs.org/en/download/))
```sh-session
npm install @biscuitland/cache
```
## Example (Basic)
```ts
import { Cache, MemoryCacheAdapter } from '@biscuitland/cache';
const bootstrap = async () => {
const cache = new Cache({
adapter: new MemoryCacheAdapter(),
});
// You can listen to the raw biscuit event
cache.start(<payloads>);
};
bootstrap();
```
## Links
- [Documentation](https://docs.biscuitjs.com/)
- [Website](https://biscuitjs.com/)

View File

@ -1,75 +0,0 @@
{
"name": "@biscuitland/cache",
"version": "2.3.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"build": "tsup",
"clean": "rm -rf dist && rm -rf .turbo",
"dev": "tsup --watch"
},
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
},
"require": "./dist/index.js"
}
},
"dependencies": {
"@biscuitland/api-types": "^2.3.0",
"ioredis": "^5.2.2"
},
"devDependencies": {
"tsup": "^6.1.3"
},
"license": "Apache-2.0",
"author": "Yuzuru <yuzuru@programmer.net>",
"contributors": [
{
"name": "Yuzuru",
"url": "https://github.com/yuzudev",
"author": true
},
{
"name": "miia",
"url": "https://github.com/dragurimu"
},
{
"name": "n128",
"url": "https://github.com/nicolito128"
},
{
"name": "socram03",
"url": "https://github.com/socram03"
},
{
"name": "Drylozu",
"url": "https://github.com/Drylozu"
}
],
"homepage": "https://biscuitjs.com",
"repository": {
"type": "git",
"url": "git+https://github.com/oasisjs/biscuit.git"
},
"bugs": {
"url": "https://github.com/oasisjs/biscuit"
},
"keywords": [
"api",
"discord",
"bots",
"typescript",
"botdev"
],
"publishConfig": {
"access": "public"
}
}

View File

@ -1,252 +0,0 @@
/* eslint-disable no-case-declarations */
import type { CacheOptions, CO } from './types';
import type { CacheAdapter } from './scheme/adapters/cache-adapter';
import { MemoryCacheAdapter } from './scheme/adapters/memory-cache-adapter';
import {
ChannelResource,
GuildEmojiResource,
GuildMemberResource,
GuildResource,
GuildRoleResource,
GuildStickerResource,
GuildVoiceResource,
PresenceResource,
UserResource,
} from './resources';
import { Options } from './utils/options';
export class Cache {
static readonly DEFAULTS = {
adapter: new MemoryCacheAdapter(),
};
readonly options: CO;
#adapter: CacheAdapter;
// move to resources assigned
readonly channels: ChannelResource;
readonly emojis: GuildEmojiResource;
readonly members: GuildMemberResource;
readonly guilds: GuildResource;
readonly roles: GuildRoleResource;
readonly stickers: GuildStickerResource;
readonly voices: GuildVoiceResource;
readonly presences: PresenceResource;
readonly users: UserResource;
constructor(options: CacheOptions) {
this.options = Options({}, Cache.DEFAULTS, options);
this.#adapter = this.options.adapter;
this.channels = new ChannelResource(this.#adapter);
this.emojis = new GuildEmojiResource(this.#adapter);
this.members = new GuildMemberResource(this.#adapter);
this.guilds = new GuildResource(this.#adapter);
this.roles = new GuildRoleResource(this.#adapter);
this.stickers = new GuildStickerResource(this.#adapter);
this.voices = new GuildVoiceResource(this.#adapter);
this.presences = new PresenceResource(this.#adapter);
this.users = new UserResource(this.#adapter);
}
/**
* @inheritDoc
*/
async start(event: any) {
let resources: any[] = [];
let contents: any[] = [];
switch (event.t) {
case 'READY':
resources = [];
await this.users.set(event.d.user.id, event.d.user);
for (const guild of event.d.guilds) {
resources.push(this.guilds.set(guild.id, guild));
}
await Promise.all(resources);
break;
case 'USER_UPDATE':
await this.users.set(event.d.id, event.d);
break;
case 'PRESENCE_UPDATE':
await this.presences.set(event.d.user?.id, event.d);
break;
case 'GUILD_CREATE':
case 'GUILD_UPDATE':
await this.guilds.set(event.d.id, event.d);
break;
case 'GUILD_DELETE':
if (event.d.unavailable) {
await this.guilds.set(event.d.id, event.d);
} else {
await this.guilds.remove(event.d.id);
}
break;
case 'CHANNEL_CREATE':
case 'CHANNEL_UPDATE':
// modify [Add elimination system]
await this.channels.set(event.d.id, event.d);
break;
case 'CHANNEL_DELETE':
// modify [Add elimination system]
await this.channels.remove(event.d.id);
break;
case 'MESSAGE_CREATE':
if (event.d.webhook_id) {
return;
}
if (event.d.author) {
await this.users.set(event.d.author.id, event.d.author);
}
break;
case 'GUILD_ROLE_CREATE':
case 'GUILD_ROLE_UPDATE':
await this.roles.set(
event.d.role.id,
event.d.guild_id,
event.d.role
);
break;
case 'GUILD_ROLE_DELETE':
await this.roles.remove(event.d.role.id, event.d.guild_id);
break;
case 'GUILD_EMOJIS_UPDATE':
contents = [];
contents = await this.emojis.items(event.d.guild_id);
for (const emoji of event.d.emojis) {
const emote = contents.find(o => o?.id === emoji.id);
if (!emote || emote !== emoji) {
await this.emojis.set(
emoji.id,
event.d.guild_id,
emoji
);
}
}
for (const emoji of contents) {
const emote = event.d.emojis.find(
(o: any) => o.id === emoji?.id
);
if (!emote) {
await this.emojis.remove(emote.id, event.d.guild_id);
}
}
break;
case 'GUILD_STICKERS_UPDATE':
contents = [];
contents = await this.stickers.items(event.d.guild_id);
for (const sticker of event.d.stickers) {
const stick = contents.find(o => o?.id === sticker.id);
if (!stick || stick !== sticker) {
await this.stickers.set(
sticker.id,
event.d.guild_id,
sticker
);
}
}
for (const sticker of contents) {
const stick = event.d.stickers.find(
(o: any) => o.id === sticker?.id
);
if (!stick) {
await this.stickers.remove(stick.id, event.d.guild_id);
}
}
break;
case 'GUILD_MEMBER_ADD':
case 'GUILD_MEMBER_UPDATE':
await this.members.set(
event.d.user.id,
event.d.guild_id,
event.d
);
break;
case 'GUILD_MEMBER_REMOVE':
await this.members.remove(event.d.user.id, event.d.guild_id);
break;
case 'GUILD_MEMBERS_CHUNK':
resources = [];
for (const member of event.d.members) {
resources.push(
this.members.set(
member.user.id,
event.d.guild_id,
member
)
);
}
await Promise.all(resources);
break;
case 'VOICE_STATE_UPDATE':
if (!event.d.guild_id) {
return;
}
if (event.d.guild_id && event.d.member && event.d.user_id) {
await this.members.set(event.d.user_id, event.d.guild_id, {
guild_id: event.d.guild_id,
...event.d.member,
});
}
if (event.d.channel_id != null) {
await this.members.set(
event.d.user_id,
event.d.guild_id,
event.d
);
} else {
await this.voices.remove(event.d.user_id, event.d.guild_id);
}
break;
}
}
}

View File

@ -1,7 +0,0 @@
export { MemoryCacheAdapter } from './scheme/adapters/memory-cache-adapter';
export { RedisCacheAdapter } from './scheme/adapters/redis-cache-adapter';
export { CacheAdapter } from './scheme/adapters/cache-adapter';
export { RedisOptions, MemoryOptions, CacheOptions } from './types';
export { Cache } from './cache';

View File

@ -1,117 +0,0 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
/**
* Base class for all resources
* All Methods from BaseResource are also available on every class extends
*/
class Base<T> {
/**
* Resource name
*/
#namespace = 'base';
/**
* Adapter for storage processes and operations
*/
#adapter: CacheAdapter;
/**
* Guild linked and assigned to the current entity (resource)
*/
parent?: string;
/**
* Constructor
*/
constructor(namespace: string, adapter: CacheAdapter) {
this.#namespace = namespace;
this.#adapter = adapter;
}
/**
* Entity linked
*/
setEntity(entity: T): void {
Object.assign(this, entity);
}
/**
* Parent linked
*/
setParent(parent: string): void {
// rename
this.parent = parent;
}
/**
* Count how many resources there are in the relationships
*/
async count(to: string): Promise<number> {
return await this.#adapter.count(this.hashId(to));
}
/**
* Check if the resource is in the relationships
*/
async contains(
id: string,
guild: string = this.parent as string
): Promise<boolean> {
return await this.#adapter.contains(this.hashId(guild), id);
}
/**
* Gets the resource relationships
*/
async getToRelationship(
id: string = this.parent as string
): Promise<string[]> {
return await this.#adapter.getToRelationship(this.hashId(id));
}
/**
* Adds the resource to relationships
*/
async addToRelationship(
id: string,
guild: string = this.parent as string
): Promise<void> {
await this.#adapter.addToRelationship(this.hashId(guild), id);
}
/**
* Removes the relationship resource
*/
async removeToRelationship(
id: string,
guild: string = this.parent as string
): Promise<void> {
await this.#adapter.removeToRelationship(this.hashId(guild), id);
}
/**
* Construct an id consisting of namespace.id
*/
protected hashId(id: string): string {
return `${this.#namespace}.${id}`;
}
}
export const BaseResource = Base as new <T>(
data: string,
adapter: CacheAdapter
) => Base<T> & T;

View File

@ -1,139 +0,0 @@
/**
* refactor
*/
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordChannel } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
import { UserResource } from './user-resource';
/**
* Resource represented by an channel of discord
*/
export class ChannelResource extends BaseResource<DiscordChannel> {
#namespace = 'channel';
#adapter: CacheAdapter;
#users: UserResource;
constructor(adapter: CacheAdapter, entity?: DiscordChannel | null) {
super('channel', adapter);
this.#adapter = adapter;
this.#users = new UserResource(adapter);
if (entity) {
this.setEntity(entity);
}
}
/**
* @inheritDoc
*/
async get(id: string): Promise<ChannelResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashId(id));
if (kv) {
return new ChannelResource(this.#adapter, kv);
}
return null;
}
/**
* @inheritDoc
*/
async set(id: string, data: any): Promise<void> {
if (data.recipients) {
const recipients = [];
for (const recipient of data.recipients) {
recipients.push(this.#users.set(recipient.id, recipient));
}
await Promise.all(recipients);
}
delete data.recipients;
delete data.permission_overwrites;
await this.addToRelationship(id);
await this.#adapter.set(this.hashId(id), data);
}
/**
* @inheritDoc
*/
async items(): Promise<ChannelResource[]> {
const data = await this.#adapter.items(this.#namespace);
if (data) {
return data.map(dt => {
const resource = new ChannelResource(this.#adapter, dt);
resource.setParent(resource.id);
return resource;
});
}
return [];
}
/**
* @inheritDoc
*/
async count(): Promise<number> {
return await this.#adapter.count(this.#namespace);
}
/**
* @inheritDoc
*/
async remove(id: string): Promise<void> {
await this.#adapter.remove(this.hashId(id));
}
/**
* @inheritDoc
*/
async contains(id: string): Promise<boolean> {
return await this.#adapter.contains(this.#namespace, id);
}
/**
* @inheritDoc
*/
async getToRelationship(): Promise<string[]> {
return await this.#adapter.getToRelationship(this.#namespace);
}
/**
* @inheritDoc
*/
async addToRelationship(id: string): Promise<void> {
await this.#adapter.addToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async removeToRelationship(id: string): Promise<void> {
await this.#adapter.removeToRelationship(this.#namespace, id);
}
}

View File

@ -1,128 +0,0 @@
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordEmoji } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
import { UserResource } from './user-resource';
/**
* Resource represented by an emoji of discord
*/
export class GuildEmojiResource extends BaseResource<DiscordEmoji> {
#namespace = 'emoji' as const;
#adapter: CacheAdapter;
#users: UserResource;
constructor(
adapter: CacheAdapter,
entity?: DiscordEmoji | null,
parent?: string
) {
super('emoji', adapter);
this.#adapter = adapter;
this.#users = new UserResource(adapter);
if (entity) {
this.setEntity(entity);
}
if (parent) {
this.setParent(parent);
}
}
/**
* @inheritDoc
*/
async get(
id: string,
guild: string | undefined = this.parent
): Promise<GuildEmojiResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
return new GuildEmojiResource(this.#adapter, kv, guild);
}
return null;
}
/**
* @inheritDoc
*/
async set(
id: string,
guild: string | undefined = this.parent,
data: any
): Promise<void> {
if (data.user) {
await this.#users.set(data.user.id, data.user);
}
delete data.user;
delete data.roles;
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id, guild);
await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
async items(to: string): Promise<GuildEmojiResource[]> {
if (!to && this.parent) {
to = this.parent;
}
const data = await this.#adapter.items(this.hashId(to));
if (data) {
return data.map(dt => {
const resource = new GuildEmojiResource(this.#adapter, dt);
resource.setParent(to);
return resource;
});
}
return [];
}
/**
* @inheritDoc
*/
async remove(
id: string,
guild: string | undefined = this.parent
): Promise<void> {
await this.removeToRelationship(id, guild);
await this.#adapter.remove(this.hashGuildId(id, guild));
}
/**
* @inheritDoc
*/
protected hashGuildId(id: string, guild?: string): string {
if (!guild) {
return this.hashId(id);
}
return `${this.#namespace}.${guild}.${id}`;
}
}

View File

@ -1,128 +0,0 @@
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordMember } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
import { UserResource } from './user-resource';
/**
* Resource represented by an member of discord
*/
export class GuildMemberResource extends BaseResource<DiscordMember> {
#namespace = 'member' as const;
#adapter: CacheAdapter;
#users: UserResource;
constructor(
adapter: CacheAdapter,
entity?: DiscordMember | null,
parent?: string
) {
super('member', adapter);
this.#adapter = adapter;
this.#users = new UserResource(adapter);
if (entity) {
this.setEntity(entity);
}
if (parent) {
this.setParent(parent);
}
}
/**
* @inheritDoc
*/
async get(
id: string,
guild: string | undefined = this.parent
): Promise<GuildMemberResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
return new GuildMemberResource(this.#adapter, kv, guild);
}
return null;
}
/**
* @inheritDoc
*/
async set(
id: string,
guild: string | undefined = this.parent,
data: any
): Promise<void> {
if (data.user) {
await this.#users.set(data.user.id, data.user);
}
delete data.user;
delete data.roles;
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id, guild);
await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
async items(to: string): Promise<GuildMemberResource[]> {
if (!to && this.parent) {
to = this.parent;
}
const data = await this.#adapter.items(this.hashId(to));
if (data) {
return data.map(dt => {
const resource = new GuildMemberResource(this.#adapter, dt);
resource.setParent(to);
return resource;
});
}
return [];
}
/**
* @inheritDoc
*/
async remove(
id: string,
guild: string | undefined = this.parent
): Promise<void> {
await this.removeToRelationship(id, guild);
await this.#adapter.remove(this.hashGuildId(id, guild));
}
/**
* @inheritDoc
*/
protected hashGuildId(id: string, guild?: string): string {
if (!guild) {
return this.hashId(id);
}
return `${this.#namespace}.${guild}.${id}`;
}
}

View File

@ -1,339 +0,0 @@
/**
* refactor
*/
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordGuild } from '@biscuitland/api-types';
import { ChannelResource } from './channel-resource';
import { GuildEmojiResource } from './guild-emoji-resource';
import { GuildMemberResource } from './guild-member-resource';
import { GuildRoleResource } from './guild-role-resource';
import { GuildStickerResource } from './guild-sticker-resource';
import { GuildVoiceResource } from './guild-voice-resource';
import { PresenceResource } from './presence-resource';
import { BaseResource } from './base-resource';
/**
* Resource represented by an guild of discord
*/
export class GuildResource extends BaseResource<DiscordGuild> {
#namespace = 'guild' as const;
#adapter: CacheAdapter;
#channels: ChannelResource;
#emojis: GuildEmojiResource;
#members: GuildMemberResource;
#roles: GuildRoleResource;
#stickers: GuildStickerResource;
#voices: GuildVoiceResource;
#presences: PresenceResource;
constructor(
adapter: CacheAdapter,
entity?: DiscordGuild | null,
parent?: string,
channels?: ChannelResource,
emojis?: GuildEmojiResource,
members?: GuildMemberResource,
roles?: GuildRoleResource,
stickers?: GuildStickerResource,
voices?: GuildVoiceResource,
presences?: PresenceResource
) {
super('guild', adapter);
this.#adapter = adapter;
this.#channels = channels ?? new ChannelResource(adapter);
this.#emojis = emojis ?? new GuildEmojiResource(adapter);
this.#members = members ?? new GuildMemberResource(adapter);
this.#roles = roles ?? new GuildRoleResource(adapter);
this.#stickers = stickers ?? new GuildStickerResource(adapter);
this.#voices = voices ?? new GuildVoiceResource(adapter);
this.#presences = presences ?? new PresenceResource(adapter);
if (entity) {
this.setEntity(entity);
}
if (parent) {
this.setParent(parent);
}
}
/**
* @inheritDoc
*/
async get(id: string): Promise<GuildResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashId(id));
if (kv) {
return new GuildResource(
this.#adapter,
kv,
id,
new ChannelResource(this.#adapter),
new GuildEmojiResource(this.#adapter, null, id),
new GuildMemberResource(this.#adapter, null, id),
new GuildRoleResource(this.#adapter, null, id),
new GuildStickerResource(this.#adapter, null, id),
new GuildVoiceResource(this.#adapter, null, id),
new PresenceResource(this.#adapter)
);
}
return null;
}
/**
* @inheritDoc
*/
async set(id: string, data: any): Promise<void> {
if (data.channels) {
const channels: unknown[] = [];
for (const channel of data.channels) {
channel.guild_id = id;
await this.#channels.set(channel.id, channel);
}
await Promise.all(channels);
}
if (data.emojis) {
const emojis: unknown[] = [];
for (const emoji of data.emojis) {
emoji.guild_id = id;
await this.#emojis.set(emoji.id, id, emoji);
}
await Promise.all(emojis);
}
if (data.members) {
const members: unknown[] = [];
for (const member of data.members) {
member.guild_id = id;
await this.#members.set(member.user.id, id, member);
}
await Promise.all(members);
}
if (data.roles) {
const roles: unknown[] = [];
for (const role of data.roles) {
role.guild_id = id;
await this.#roles.set(role.id, id, role);
}
await Promise.all(roles);
}
if (data.stickers) {
const stickers: unknown[] = [];
for (const sticker of data.stickers) {
sticker.guild_id = id;
await this.#stickers.set(sticker.id, id, sticker);
}
await Promise.all(stickers);
}
if (data.voice_states) {
const voices: unknown[] = [];
for (const voice of data.voice_states) {
voice.guild_id = id;
voices.push(this.#voices.set(voice.user_id, id, voice));
}
await Promise.all(voices);
}
if (data.presences) {
const presences: unknown[] = [];
for (const presence of data.presences) {
await this.#presences.set(presence.user.id, presence);
}
await Promise.all(presences);
}
delete data.channels;
delete data.emojis;
delete data.members;
delete data.roles;
delete data.stickers;
delete data.voice_states;
delete data.guild_hashes;
delete data.presences;
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id);
await this.#adapter.set(this.hashId(id), data);
}
/**
* @inheritDoc
*/
async items(): Promise<GuildResource[]> {
const data = await this.#adapter.items(this.#namespace);
if (data) {
return data.map(dt => {
const resource = new GuildResource(this.#adapter, dt);
resource.setParent(resource.id);
return resource;
});
}
return [];
}
/**
* @inheritDoc
*/
async count(): Promise<number> {
return await this.#adapter.count(this.#namespace);
}
/**
* @inheritDoc
*/
async remove(id: string): Promise<void> {
const members = await this.#members.getToRelationship(id);
for (const member of members) {
await this.#members.remove(member, id);
}
const roles = await this.#roles.getToRelationship(id);
for (const role of roles) {
await this.#roles.remove(role, id);
}
const emojis = await this.#emojis.getToRelationship(id);
for (const emoji of emojis) {
await this.#emojis.remove(emoji, id);
}
const stickers = await this.#stickers.getToRelationship(id);
for (const sticker of stickers) {
await this.#stickers.remove(sticker, id);
}
await this.removeToRelationship(id);
await this.#adapter.remove(this.hashId(id));
}
/**
* @inheritDoc
*/
async contains(id: string): Promise<boolean> {
return await this.#adapter.contains(this.#namespace, id);
}
/**
* @inheritDoc
*/
async getToRelationship(): Promise<string[]> {
return await this.#adapter.getToRelationship(this.#namespace);
}
/**
* @inheritDoc
*/
async addToRelationship(id: string): Promise<void> {
await this.#adapter.addToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async removeToRelationship(id: string): Promise<void> {
await this.#adapter.removeToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async getEmojis(): Promise<GuildEmojiResource[]> {
return await this.#emojis.items(this.parent as string);
}
/**
* @inheritDoc
*/
async getMembers(): Promise<GuildMemberResource[]> {
return await this.#members.items(this.parent as string);
}
/**
* @inheritDoc
*/
async getRoles(): Promise<GuildRoleResource[]> {
return await this.#roles.items(this.parent as string);
}
/**
* @inheritDoc
*/
async getStickers(): Promise<GuildStickerResource[]> {
return await this.#stickers.items(this.parent as string);
}
/**
* @inheritDoc
*/
async getVoiceStates(): Promise<GuildVoiceResource[]> {
return await this.#voices.items(this.parent as string);
}
}

View File

@ -1,133 +0,0 @@
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordRole } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
/**
* Resource represented by an role of discord
*/
export class GuildRoleResource extends BaseResource<DiscordRole> {
#namespace = 'role' as const;
#adapter: CacheAdapter;
constructor(
adapter: CacheAdapter,
entity?: DiscordRole | null,
parent?: string
) {
super('role', adapter);
this.#adapter = adapter;
if (entity) {
this.setEntity(entity);
}
if (parent) {
this.setParent(parent);
}
}
/**
* @inheritDoc
*/
async get(
id: string,
guild: string | undefined = this.parent
): Promise<GuildRoleResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
return new GuildRoleResource(this.#adapter, kv, guild);
}
return null;
}
/**
* @inheritDoc
*/
async set(
id: string,
guild: string | undefined = this.parent,
data: any
): Promise<void> {
if (!data.id) {
data.id = id;
}
if (!data.guild_id) {
data.guild_id = guild;
}
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id, guild);
await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
async count(): Promise<number> {
return await this.#adapter.count(this.#namespace);
}
/**
* @inheritDoc
*/
async items(to: string): Promise<GuildRoleResource[]> {
if (!to && this.parent) {
to = this.parent;
}
const data = await this.#adapter.items(this.hashId(to));
if (data) {
return data.map(dt => {
const resource = new GuildRoleResource(this.#adapter, dt);
resource.setParent(to);
return resource;
});
}
return [];
}
/**
* @inheritDoc
*/
async remove(
id: string,
guild: string | undefined = this.parent
): Promise<void> {
await this.removeToRelationship(id, guild);
await this.#adapter.remove(this.hashGuildId(id, guild));
}
/**
* @inheritDoc
*/
protected hashGuildId(id: string, guild?: string): string {
if (!guild) {
return this.hashId(id);
}
return `${this.#namespace}.${guild}.${id}`;
}
}

View File

@ -1,124 +0,0 @@
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordSticker } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
import { UserResource } from './user-resource';
/**
* Resource represented by an sticker of discord
*/
export class GuildStickerResource extends BaseResource<DiscordSticker> {
#namespace = 'sticker' as const;
#adapter: CacheAdapter;
#users: UserResource;
constructor(
adapter: CacheAdapter,
entity?: DiscordSticker | null,
parent?: string
) {
super('sticker', adapter);
this.#adapter = adapter;
this.#users = new UserResource(adapter);
if (entity) {
this.setEntity(entity);
}
if (parent) {
this.setParent(parent);
}
}
/**
* @inheritDoc
*/
async get(id: string, guild: string): Promise<GuildStickerResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
return new GuildStickerResource(this.#adapter, kv, guild);
}
return null;
}
/**
* @inheritDoc
*/
async set(
id: string,
guild: string | undefined = this.parent,
data: any
): Promise<void> {
if (data.user) {
await this.#users.set(data.user.id, data.user);
}
delete data.user;
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id, guild);
await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
async items(to: string): Promise<GuildStickerResource[]> {
if (!to && this.parent) {
to = this.parent;
}
const data = await this.#adapter.items(this.hashId(to));
if (data) {
return data.map(dt => {
const resource = new GuildStickerResource(this.#adapter, dt);
resource.setParent(to);
return resource;
});
}
return [];
}
/**
* @inheritDoc
*/
async remove(
id: string,
guild: string | undefined = this.parent
): Promise<void> {
await this.removeToRelationship(id, guild);
await this.#adapter.remove(this.hashGuildId(id, guild));
}
/**
* @inheritDoc
*/
protected hashGuildId(id: string, guild?: string): string {
if (!guild) {
return this.hashId(id);
}
return `${this.#namespace}.${guild}.${id}`;
}
}

View File

@ -1,127 +0,0 @@
/**
* refactor
*/
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordVoiceState } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
/**
* Resource represented by an voice state of discord
*/
export class GuildVoiceResource extends BaseResource<DiscordVoiceState> {
#namespace = 'voice' as const;
#adapter: CacheAdapter;
constructor(
adapter: CacheAdapter,
entity?: DiscordVoiceState | null,
parent?: string
) {
super('voice', adapter);
this.#adapter = adapter;
if (entity) {
this.setEntity(entity);
}
if (parent) {
this.setParent(parent);
}
}
/**
* @inheritDoc
*/
async get(
id: string,
guild: string | undefined = this.parent
): Promise<GuildVoiceResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashGuildId(id, guild));
if (kv) {
return new GuildVoiceResource(this.#adapter, kv, guild);
}
return null;
}
/**
* @inheritDoc
*/
async set(
id: string,
guild: string | undefined = this.parent,
data: any
): Promise<void> {
if (!data.guild_id) {
data.guild_id = guild;
}
delete data.member;
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id, guild);
await this.#adapter.set(this.hashGuildId(id, guild), data);
}
/**
* @inheritDoc
*/
async items(to: string): Promise<GuildVoiceResource[]> {
if (!to && this.parent) {
to = this.parent;
}
const data = await this.#adapter.items(this.hashId(to));
if (data) {
return data.map(dt => {
const resource = new GuildVoiceResource(this.#adapter, dt);
resource.setParent(to);
return resource;
});
}
return [];
}
/**
* @inheritDoc
*/
async remove(
id: string,
guild: string | undefined = this.parent
): Promise<void> {
await this.removeToRelationship(id, guild);
await this.#adapter.remove(this.hashGuildId(id, guild));
}
/**
* @inheritDoc
*/
protected hashGuildId(id: string, guild?: string): string {
if (!guild) {
return this.hashId(id);
}
return `${this.#namespace}.${guild}.${id}`;
}
}

View File

@ -1,15 +0,0 @@
export { BaseResource } from './base-resource';
export { ChannelResource } from './channel-resource';
export { GuildEmojiResource } from './guild-emoji-resource';
export { GuildMemberResource } from './guild-member-resource';
export { GuildResource } from './guild-resource';
export { GuildRoleResource } from './guild-role-resource';
export { GuildStickerResource } from './guild-sticker-resource';
export { GuildVoiceResource } from './guild-voice-resource';
export { PresenceResource } from './presence-resource';
export { UserResource } from './user-resource';

View File

@ -1,135 +0,0 @@
/**
* refactor
*/
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordPresenceUpdate } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
import { UserResource } from './user-resource';
/**
* Resource represented by an presence of discord
*/
export class PresenceResource extends BaseResource<DiscordPresenceUpdate> {
#namespace = 'presence' as const;
#adapter: CacheAdapter;
#users: UserResource;
constructor(adapter: CacheAdapter, entity?: DiscordPresenceUpdate | null) {
super('presence', adapter);
this.#adapter = adapter;
this.#users = new UserResource(this.#adapter);
if (entity) {
this.setEntity(entity);
}
}
/**
* @inheritDoc
*/
async get(id: string): Promise<PresenceResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashId(id));
if (kv) {
return new PresenceResource(this.#adapter, kv);
}
return null;
}
/**
* @inheritDoc
*/
async set(id: string, data: any): Promise<void> {
if (data.user) {
await this.#users.set(data.user.id, data.user);
}
delete data.user;
delete data.roles;
delete data.guild_id;
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id);
await this.#adapter.set(this.hashId(id), data);
}
/**
* @inheritDoc
*/
async items(): Promise<PresenceResource[]> {
const data = await this.#adapter.items(this.#namespace);
if (data) {
return data.map(dt => new PresenceResource(this.#adapter, dt));
}
return [];
}
/**
* @inheritDoc
*/
async count(): Promise<number> {
return await this.#adapter.count(this.#namespace);
}
/**
* @inheritDoc
*/
async remove(id: string): Promise<void> {
await this.removeToRelationship(id);
await this.#adapter.remove(this.hashId(id));
}
/**
* @inheritDoc
*/
async contains(id: string): Promise<boolean> {
return await this.#adapter.contains(this.#namespace, id);
}
/**
* @inheritDoc
*/
async getToRelationship(): Promise<string[]> {
return await this.#adapter.getToRelationship(this.#namespace);
}
/**
* @inheritDoc
*/
async addToRelationship(id: string): Promise<void> {
await this.#adapter.addToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async removeToRelationship(id: string): Promise<void> {
await this.#adapter.removeToRelationship(this.#namespace, id);
}
}

View File

@ -1,122 +0,0 @@
/**
* refactor
*/
import type { CacheAdapter } from '../scheme/adapters/cache-adapter';
import type { DiscordUser } from '@biscuitland/api-types';
import { BaseResource } from './base-resource';
/**
* Resource represented by an user of discord
*/
export class UserResource extends BaseResource<DiscordUser> {
#namespace = 'user' as const;
#adapter: CacheAdapter;
constructor(adapter: CacheAdapter, entity?: DiscordUser | null) {
super('user', adapter);
this.#adapter = adapter;
if (entity) {
this.setEntity(entity);
}
}
/**
* @inheritDoc
*/
async get(id: string): Promise<UserResource | null> {
if (this.parent) {
return this;
}
const kv = await this.#adapter.get(this.hashId(id));
if (kv) {
return new UserResource(this.#adapter, kv);
}
return null;
}
/**
* @inheritDoc
*/
async set(id: string, data: any): Promise<void> {
if (this.parent) {
this.setEntity(data);
}
await this.addToRelationship(id);
await this.#adapter.set(this.hashId(id), data);
}
/**
* @inheritDoc
*/
async items(): Promise<UserResource[]> {
const data = await this.#adapter.items(this.#namespace);
if (data) {
return data.map(dt => new UserResource(this.#adapter, dt));
}
return [];
}
/**
* @inheritDoc
*/
async count(): Promise<number> {
return await this.#adapter.count(this.#namespace);
}
/**
* @inheritDoc
*/
async remove(id: string): Promise<void> {
await this.removeToRelationship(id);
await this.#adapter.remove(this.hashId(id));
}
/**
* @inheritDoc
*/
async contains(id: string): Promise<boolean> {
return await this.#adapter.contains(this.#namespace, id);
}
/**
* @inheritDoc
*/
async getToRelationship(): Promise<string[]> {
return await this.#adapter.getToRelationship(this.#namespace);
}
/**
* @inheritDoc
*/
async addToRelationship(id: string): Promise<void> {
await this.#adapter.addToRelationship(this.#namespace, id);
}
/**
* @inheritDoc
*/
async removeToRelationship(id: string): Promise<void> {
await this.#adapter.removeToRelationship(this.#namespace, id);
}
}

View File

@ -1,63 +0,0 @@
/**
* Base class for all adapters
* All Methods from CacheAdapter are also available on every class extends
*/
export interface CacheAdapter {
/**
* Gets the resource to adapter
*/
get(id: string): any | Promise<any>;
get(id: string, guild: string): string | Promise<string>;
/**
* Sets the resource to adapter
*/
set(id: string, data: any): void | Promise<void>;
set(id: string, guild: string, data: any): void | Promise<void>;
/**
* Get the items of a relationship
*/
items(to?: string): any[] | Promise<any[]>;
/**
* Count how many resources there are in the relationships
*/
count(to: string): number | Promise<number>;
/**
* Removes the adapter resource
*/
remove(id: string): void | Promise<void>;
remove(id: string, guild: string): void | Promise<void>;
/**
* Check if the resource is in the relationships
*/
contains(to: string, id: string): boolean | Promise<boolean>;
/**
* Gets the resource relationships
*/
getToRelationship(to: string): string[] | Promise<string[]>;
/**
* Adds the resource to relationships
*/
addToRelationship(to: string, id: string): void | Promise<void>;
/**
* Removes the relationship resource
*/
removeToRelationship(to: string, id: string): void | Promise<void>;
}

View File

@ -1,149 +0,0 @@
/**
* refactor
*/
import type { CacheAdapter } from './cache-adapter';
import type { MemoryOptions, MO } from '../../types';
import { Options } from '../../utils/options';
export class MemoryCacheAdapter implements CacheAdapter {
static readonly DEFAULTS = {
expire: 3600000,
};
readonly relationships = new Map<string, string[]>();
readonly storage = new Map<string, { data: any; expire?: number }>();
readonly options: MO;
constructor(options?: MemoryOptions) {
this.options = Options({}, MemoryCacheAdapter.DEFAULTS, options);
}
/**
* @inheritDoc
*/
get<T = any>(id: string): T | null {
const data = this.storage.get(id);
if (data) {
if (data.expire && data.expire < Date.now()) {
this.storage.delete(id);
} else {
return JSON.parse(data.data);
}
}
return null;
}
/**
* @inheritDoc
*/
set(id: string, data: any): void {
const expire = this.options.expire;
if (expire) {
this.storage.set(id, {
data: JSON.stringify(data),
expire: Date.now() + expire,
});
} else {
this.storage.set(id, { data: JSON.stringify(data) });
}
}
/**
* @inheritDoc
*/
items(to: string): any[] {
const array: unknown[] = [];
let data = this.getToRelationship(to);
data = data.map(id => `${to}.${id}`);
for (const key of data) {
const content = this.get(key);
if (content) {
array.push(content);
}
}
return array;
}
/**
* @inheritDoc
*/
count(to: string): number {
return this.getToRelationship(to).length;
}
/**
* @inheritDoc
*/
remove(id: string): void {
this.storage.delete(id);
}
/**
* @inheritDoc
*/
contains(to: string, id: string): boolean {
return this.getToRelationship(to).includes(id);
}
/**
* @inheritDoc
*/
getToRelationship(to: string): string[] {
return this.relationships.get(to) || [];
}
/**
* @inheritDoc
*/
addToRelationship(to: string, id: string): void {
const data = this.getToRelationship(to);
if (data.includes(id)) {
return;
}
data.push(id);
const has = !!this.relationships.get(to);
if (!has) {
this.relationships.set(to, data);
}
}
/**
* @inheritDoc
*/
removeToRelationship(to: string, id: string): void {
const data = this.getToRelationship(to);
if (data) {
const idx = data.indexOf(id);
if (idx === -1) {
return;
}
data.splice(idx, 1);
}
}
}

View File

@ -1,195 +0,0 @@
/**
* refactor
*/
import type { CacheAdapter } from './cache-adapter';
import type { RedisOptions } from 'ioredis';
import type Redis from 'ioredis';
import IORedis from 'ioredis';
interface BaseOptions {
namespace: string;
expire?: number;
}
interface BuildOptions extends BaseOptions, RedisOptions {}
interface ClientOptions extends BaseOptions {
client: Redis;
}
type Options = BuildOptions | ClientOptions;
export class RedisCacheAdapter implements CacheAdapter {
static readonly DEFAULTS = {
namespace: 'biscuitland',
};
readonly options: Options;
readonly client: Redis;
constructor(options?: Options) {
this.options = Object.assign(RedisCacheAdapter.DEFAULTS, options);
if ((this.options as ClientOptions).client) {
this.client = (this.options as ClientOptions).client;
} else {
const { ...redisOpt } = this.options as BuildOptions;
this.client = new IORedis(redisOpt);
}
}
/**
* @inheritDoc
*/
async get(id: string): Promise<any> {
const data = await this.client.get(this.build(id));
if (!data) {
return null;
}
return JSON.parse(data);
}
/**
* @inheritDoc
*/
async set(id: string, data: unknown): Promise<void> {
const expire = this.options.expire;
if (expire) {
await this.client.set(
this.build(id),
JSON.stringify(data),
'EX',
expire
);
} else {
await this.client.set(this.build(id), JSON.stringify(data));
}
}
/**
* @inheritDoc
*/
async items(to: string): Promise<any[]> {
const array: unknown[] = [];
let data = await this.getToRelationship(to);
data = data.map(id => this.build(`${to}.${id}`));
if (data && data.length > 0) {
const items = await this.client.mget(data);
for (const item of items) {
if (item) {
array.push(JSON.parse(item));
}
}
}
return array;
}
/**
* @inheritDoc
*/
async count(to: string): Promise<number> {
return new Promise((resolve, reject) => {
this.client.scard(this.build(to), (err, result) => {
if (err) {
return reject(err);
}
return resolve(result || 0);
});
});
}
/**
* @inheritDoc
*/
async remove(id: string): Promise<void> {
await this.client.del(this.build(id));
}
/**
* @inheritDoc
*/
async contains(to: string, id: string): Promise<boolean> {
return new Promise((resolve, reject) => {
this.client.sismember(this.build(to), id, (err, result) => {
if (err) {
return reject(err);
}
return resolve(result === 1);
});
});
}
/**
* @inheritDoc
*/
async getToRelationship(to: string): Promise<string[]> {
return new Promise((resolve, reject) => {
this.client.smembers(this.build(to), (err, result) => {
if (err) {
reject(err);
}
resolve(result || []);
});
});
}
/**
* @inheritDoc
*/
async addToRelationship(to: string, id: string): Promise<void> {
return new Promise((resolve, reject) => {
this.client.sadd(this.build(to), id, err => {
if (err) {
reject(err);
}
resolve();
});
});
}
/**
* @inheritDoc
*/
async removeToRelationship(to: string, id: string): Promise<void> {
return new Promise((resolve, reject) => {
this.client.srem(this.build(to), id, err => {
if (err) {
reject(err);
}
resolve();
});
});
}
/**
* @inheritDoc
*/
protected build(id: string): string {
return `${this.options.namespace}:${id}`;
}
}

View File

@ -1,7 +0,0 @@
/**
* future update
*/
export interface BaseTransporter {
//
}

View File

@ -1,3 +0,0 @@
import type { BaseTransporter } from './base-transporter';
export class RedisTransporter implements BaseTransporter {}

View File

@ -1,3 +0,0 @@
import type { BaseTransporter } from './base-transporter';
export class TcpTransporter implements BaseTransporter {}

View File

@ -1,55 +0,0 @@
import type { Cache } from './cache';
import type { CacheAdapter } from './scheme/adapters/cache-adapter';
import type { MemoryCacheAdapter } from './scheme/adapters/memory-cache-adapter';
//
export type CacheOptions = Pick<
CO,
Exclude<keyof CO, keyof typeof Cache.DEFAULTS>
> &
Partial<CO>;
export interface CO {
/**
* Adapter to be used for storing resources
* @default MemoryCacheAdapter
*/
adapter: CacheAdapter;
}
//
export type MemoryOptions = Pick<
MO,
Exclude<keyof MO, keyof typeof MemoryCacheAdapter.DEFAULTS>
> &
Partial<MO>;
export interface MO {
/**
* Time the resource will be stored
* @default 3600000
*/
expire: number;
}
//
export type RedisOptions = Pick<
RO,
Exclude<keyof RO, keyof typeof MemoryCacheAdapter.DEFAULTS>
> &
Partial<RO>;
export interface RO {
/**
* Time the resource will be stored
* @default 300
*/
expire: number;
}

View File

@ -1,48 +0,0 @@
/**
* Needs to be moved to a common location
* refactor
*/
const isPlainObject = (value: any) => {
return (
(value !== null &&
typeof value === 'object' &&
typeof value.constructor === 'function' &&
// eslint-disable-next-line no-prototype-builtins
(value.constructor.prototype.hasOwnProperty('isPrototypeOf') ||
Object.getPrototypeOf(value.constructor.prototype) === null)) ||
(value && Object.getPrototypeOf(value) === null)
);
};
const isObject = (o: any) => {
return !!o && typeof o === 'object' && !Array.isArray(o);
};
export const Options = (defaults: any, ...options: any[]): any => {
if (!options.length) {
return defaults;
}
const source = options.shift();
if (isObject(defaults) && isPlainObject(source)) {
Object.entries(source).forEach(([key, value]) => {
if (typeof value === 'undefined') {
return;
}
if (isPlainObject(value)) {
if (!(key in defaults)) {
Object.assign(defaults, { [key]: {} });
}
Options(defaults[key], value);
} else {
Object.assign(defaults, { [key]: value });
}
});
}
return Options(defaults, ...options);
};

View File

@ -1,7 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src/**/*"]
}

View File

@ -1,12 +0,0 @@
import { defineConfig } from 'tsup';
const isProduction = process.env.NODE_ENV === 'production';
export default defineConfig({
clean: true,
dts: true,
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
minify: isProduction,
sourcemap: false,
});

20
packages/common/README.MD Normal file
View File

@ -0,0 +1,20 @@
# @biscuitland/common
## Most importantly, biscuit's common is:
Custom types, functions and utility classes, a feature-rich package for sharing in the biscuit libraries
[<img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white">](https://github.com/oasisjs/biscuit)
[<img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white">](https://discord.gg/XNw2RZFzaP)
<img align="right" src="https://raw.githubusercontent.com/oasisjs/biscuit/main/assets/icon.svg" alt="biscuit"/>
## Install (for [node18](https://nodejs.org/en/download/))
```sh-session
npm install @biscuitland/common
yarn add @biscuitland/common
```
## Links
* [Website](https://biscuitjs.com/)
* [Documentation](https://docs.biscuitjs.com/)
* [Discord](https://discord.gg/XNw2RZFzaP)
* [core](https://www.npmjs.com/package/@biscuitland/core) | [rest](https://www.npmjs.com/package/@biscuitland/rest) | [ws](https://www.npmjs.com/package/@biscuitland/ws) | [helpers](https://www.npmjs.com/package/@biscuitland/helpers)

View File

@ -0,0 +1,75 @@
{
"name": "@biscuitland/common",
"version": "0.0.1",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"type": "module",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"build": "tsup",
"clean": "rm -rf dist && rm -rf .turbo",
"dev": "tsup --watch"
},
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": "./dist/index.cjs"
}
},
"devDependencies": {
"tsup": "^6.1.3"
},
"license": "Apache-2.0",
"author": "Yuzuru <yuzuru@programmer.net>",
"contributors": [
{
"name": "Yuzuru",
"url": "https://github.com/yuzudev"
},
{
"name": "miia",
"url": "https://github.com/dragurimu"
},
{
"name": "n128",
"url": "https://github.com/nicolito128"
},
{
"name": "socram03",
"url": "https://github.com/socram03",
"author": true
},
{
"name": "Drylozu",
"url": "https://github.com/Drylozu"
}
],
"homepage": "https://biscuitjs.com",
"repository": {
"type": "git",
"url": "git+https://github.com/oasisjs/biscuit.git"
},
"bugs": {
"url": "https://github.com/oasisjs/biscuit"
},
"keywords": [
"api",
"discord",
"bots",
"typescript",
"botdev"
],
"publishConfig": {
"access": "public"
},
"dependencies": {
"discord-api-types": "^0.37.39"
}
}

View File

@ -0,0 +1,163 @@
// https://github.com/discordeno/discordeno/blob/main/packages/utils/src/Collection.ts
export class Collection<K, V> extends Map<K, V> {
/**
* The maximum amount of items allowed in this collection. To disable cache, set it 0, set to undefined to make it infinite.
* @default undefined
*/
maxSize: number | undefined;
/** Handler to remove items from the collection every so often. */
sweeper: (CollectionSweeper<K, V> & { intervalId?: NodeJS.Timer }) | undefined;
constructor(entries?: (ReadonlyArray<readonly [K, V]> | null) | Map<K, V>, options?: CollectionOptions<K, V>) {
super(entries ?? []);
this.maxSize = options?.maxSize;
if (!options?.sweeper) return;
this.startSweeper(options.sweeper);
}
startSweeper(options: CollectionSweeper<K, V>): NodeJS.Timer {
if (this.sweeper?.intervalId) clearInterval(this.sweeper.intervalId);
this.sweeper = options;
this.sweeper.intervalId = setInterval(() => {
this.forEach((value, key) => {
if (!this.sweeper?.filter(value, key)) return;
this.delete(key);
return key;
});
}, options.interval);
return this.sweeper.intervalId;
}
stopSweeper(): void {
clearInterval(this.sweeper?.intervalId);
}
changeSweeperInterval(newInterval: number): void {
if (this.sweeper == null) return;
this.startSweeper({ filter: this.sweeper.filter, interval: newInterval });
}
changeSweeperFilter(newFilter: (value: V, key: K) => boolean): void {
if (this.sweeper == null) return;
this.startSweeper({ filter: newFilter, interval: this.sweeper.interval });
}
/** Add an item to the collection. Makes sure not to go above the maxSize. */
set(key: K, value: V): this {
// When this collection is maxSized make sure we can add first
if ((this.maxSize !== undefined || this.maxSize === 0) && this.size >= this.maxSize) {
return this;
}
return super.set(key, value);
}
/** Add an item to the collection, no matter what the maxSize is. */
forceSet(key: K, value: V): this {
return super.set(key, value);
}
/** Convert the collection to an array. */
array(): V[] {
return [...this.values()];
}
/** Retrieve the value of the first element in this collection. */
first(): V | undefined {
return this.values().next().value;
}
/** Retrieve the value of the last element in this collection. */
last(): V | undefined {
return [...this.values()][this.size - 1];
}
/** Retrieve the value of a random element in this collection. */
random(): V | undefined {
const array = [...this.values()];
return array[Math.floor(Math.random() * array.length)];
}
/** Find a specific element in this collection. */
find(callback: (value: V, key: K) => boolean): NonNullable<V> | undefined {
for (const key of this.keys()) {
const value = this.get(key)!;
if (callback(value, key)) return value;
}
return undefined;
}
/** Find all elements in this collection that match the given pattern. */
filter(callback: (value: V, key: K) => boolean): Collection<K, V> {
const relevant = new Collection<K, V>();
this.forEach((value, key) => {
if (callback(value, key)) relevant.set(key, value);
});
return relevant;
}
/** Converts the collection into an array by running a callback on all items in the collection. */
map<T>(callback: (value: V, key: K) => T): T[] {
const results = [];
for (const key of this.keys()) {
const value = this.get(key)!;
results.push(callback(value, key));
}
return results;
}
/** Check if one of the items in the collection matches the pattern. */
some(callback: (value: V, key: K) => boolean): boolean {
for (const key of this.keys()) {
const value = this.get(key)!;
if (callback(value, key)) return true;
}
return false;
}
/** Check if all of the items in the collection matches the pattern. */
every(callback: (value: V, key: K) => boolean): boolean {
for (const key of this.keys()) {
const value = this.get(key)!;
if (!callback(value, key)) return false;
}
return true;
}
/** Runs a callback on all items in the collection, merging them into a single value. */
reduce<T>(callback: (accumulator: T, value: V, key: K) => T, initialValue?: T): T {
let accumulator: T = initialValue!;
for (const key of this.keys()) {
const value = this.get(key)!;
accumulator = callback(accumulator, value, key);
}
return accumulator;
}
}
export interface CollectionOptions<K, V> {
/** Handler to clean out the items in the collection every so often. */
sweeper?: CollectionSweeper<K, V>;
/** The maximum number of items allowed in the collection. */
maxSize?: number;
}
export interface CollectionSweeper<K, V> {
/** The filter to determine whether an element should be deleted or not */
filter: (value: V, key: K, ...args: any[]) => boolean;
/** The interval in which the sweeper should run */
interval: number;
}

View File

@ -0,0 +1,20 @@
export const DiscordEpoch = 14200704e5;
export const API_VERSION = '10';
export const BASE_URL = `/api/v${API_VERSION}`;
export const BASE_HOST = 'https://discord.com';
export const CDN_URL = 'https://cdn.discordapp.com';
export const GATEWAY_BASE_URL = 'wss://gateway.discord.gg/?v=10&encoding=json';
export const OK_STATUS_CODES = [200, 201, 204, 304];
export enum HTTPMethods {
Delete = 'DELETE',
Get = 'GET',
Patch = 'PATCH',
Post = 'POST',
Put = 'PUT'
}

View File

@ -0,0 +1,128 @@
import { Options, bgBrightWhite, black, bold, cyan, gray, italic, red, yellow } from './Util';
export enum LogLevels {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
Fatal = 4
}
export enum LogDepth {
Minimal = 0,
Full = 1
}
export type LoggerOptions = {
logLevel?: LogLevels;
name?: string;
active?: boolean;
};
export class Logger {
readonly options: Required<LoggerOptions>;
constructor(options: LoggerOptions) {
this.options = Options(Logger.DEFAULT_OPTIONS, options);
}
set level(level: LogLevels) {
this.options.logLevel = level;
}
get level(): LogLevels {
return this.options.logLevel;
}
set active(active: boolean) {
this.options.active = active;
}
get active(): boolean {
return this.options.active;
}
set name(name: string) {
this.options.name = name;
}
get name(): string {
return this.options.name;
}
rawLog(level: LogLevels, ...args: unknown[]) {
if (!this.active) return;
if (level < this.level) return;
const color = Logger.colorFunctions.get(level) ?? Logger.noColor;
const date = new Date();
const log = [
bgBrightWhite(black(`[${date.toLocaleDateString()} ${date.toLocaleTimeString()}]`)),
color(Logger.prefixes.get(level) ?? 'DEBUG'),
this.name ? `${this.name} >` : '>',
...args
];
switch (level) {
case LogLevels.Debug:
return console.debug(...log);
case LogLevels.Info:
return console.info(...log);
case LogLevels.Warn:
return console.warn(...log);
case LogLevels.Error:
return console.error(...log);
case LogLevels.Fatal:
return console.error(...log);
default:
return console.log(...log);
}
}
debug(...args: any[]) {
this.rawLog(LogLevels.Debug, ...args);
}
info(...args: any[]) {
this.rawLog(LogLevels.Info, ...args);
}
warn(...args: any[]) {
this.rawLog(LogLevels.Warn, ...args);
}
error(...args: any[]) {
this.rawLog(LogLevels.Error, ...args);
}
fatal(...args: any[]) {
this.rawLog(LogLevels.Fatal, ...args);
}
static DEFAULT_OPTIONS: Required<LoggerOptions> = {
logLevel: LogLevels.Info,
name: 'BISCUIT',
active: true
};
static noColor(msg: string) {
return msg;
}
static colorFunctions = new Map<LogLevels, (str: string) => string>([
[LogLevels.Debug, gray],
[LogLevels.Info, cyan],
[LogLevels.Warn, yellow],
[LogLevels.Error, (str: string) => red(str)],
[LogLevels.Fatal, (str: string) => red(bold(italic(str)))]
]);
static prefixes = new Map<LogLevels, string>([
[LogLevels.Debug, 'DEBUG'],
[LogLevels.Info, 'INFO'],
[LogLevels.Warn, 'WARN'],
[LogLevels.Error, 'ERROR'],
[LogLevels.Fatal, 'FATAL']
]);
}

View File

@ -0,0 +1,39 @@
export type Tail<A> = A extends [unknown, ...infer rest] ? rest : A extends [unknown] ? [] : A extends (infer first)[] ? first[] : never;
export type ValueOf<T> = T[keyof T];
export type ArrayFirsElement<A> = A extends [...infer arr] ? arr[0] : never;
export type RestToKeys<T extends unknown[]> = T extends [infer V, ...infer Keys] ? { [K in Extract<Keys[number], string>]: V } : never;
export type Identify<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
export type TypeArray<T> = T | T[];
export type When<T extends boolean, A, B = never> = T extends true ? A : B;
export type PickPartial<T, K extends keyof T> = {
[P in keyof T]?: T[P] | undefined;
} & {
[P in K]: T[P];
};
export type MakeRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
export type CamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
: Lowercase<S>;
export type SnakeCase<S extends string> = S extends `${infer A}${infer Rest}`
? A extends Uppercase<A>
? `_${Lowercase<A>}${SnakeCase<Rest>}`
: `${A}${SnakeCase<Rest>}`
: Lowercase<S>;
export type ObjectToLower<T> = Identify<{
[K in keyof T as CamelCase<Exclude<K, symbol | number>>]: T[K] extends object ? Identify<ObjectToLower<T[K]>> : T[K];
}>;
export type ObjectToSnake<T> = Identify<{
[K in keyof T as SnakeCase<Exclude<K, symbol | number>>]: T[K] extends object ? Identify<ObjectToSnake<T[K]>> : T[K];
}>;

596
packages/common/src/Util.ts Normal file
View File

@ -0,0 +1,596 @@
import { setTimeout } from 'node:timers/promises';
import { ObjectToSnake, ObjectToLower } from './Types';
const isPlainObject = (value: any) => {
return (
(value !== null &&
typeof value === 'object' &&
typeof value.constructor === 'function' &&
// eslint-disable-next-line no-prototype-builtins
(value.constructor.prototype.hasOwnProperty('isPrototypeOf') || Object.getPrototypeOf(value.constructor.prototype) === null)) ||
(value && Object.getPrototypeOf(value) === null)
);
};
const isObject = (o: any) => {
return !!o && typeof o === 'object' && !Array.isArray(o);
};
export const Options = <T = any>(defaults: any, ...options: any[]): T => {
if (!options.length) {
return defaults;
}
const source = options.shift();
if (isObject(defaults) && isPlainObject(source)) {
Object.entries(source).forEach(([key, value]) => {
if (typeof value === 'undefined') {
return;
}
if (isPlainObject(value)) {
if (!(key in defaults)) {
Object.assign(defaults, { [key]: {} });
}
Options(defaults[key], value);
} else {
Object.assign(defaults, { [key]: value });
}
});
}
return Options(defaults, ...options);
};
/**
* Convert a camelCase object to snake_case.
* @param target The object to convert.
* @returns The converted object.
*/
export function toSnakeCase<Obj extends { [k: string]: unknown }>(target: Obj): ObjectToSnake<Obj> {
const result = {};
for (const [key, value] of Object.entries(target)) {
switch (typeof value) {
case 'string':
case 'bigint':
case 'boolean':
case 'function':
case 'symbol':
case 'undefined':
result[ReplaceRegex.camel(key)] = value;
break;
case 'object':
if (Array.isArray(value)) {
result[ReplaceRegex.camel(key)] = Promise.all(value.map((prop) => toSnakeCase(prop)));
break;
}
if (!Number.isNaN(value)) {
result[ReplaceRegex.camel(key)] = null;
break;
}
result[ReplaceRegex.camel(key)] = toSnakeCase({ ...value });
break;
}
}
return result as ObjectToSnake<Obj>;
}
/**
* Convert a snake_case object to camelCase.
* @param target The object to convert.
* @returns The converted object.
*/
export function toCamelCase<Obj extends { [k: string]: unknown }>(target: Obj): ObjectToLower<Obj> {
const result = {};
for (const [key, value] of Object.entries(target)) {
switch (typeof value) {
case 'string':
case 'bigint':
case 'boolean':
case 'function':
case 'symbol':
case 'undefined':
result[ReplaceRegex.snake(key)] = value;
break;
case 'object':
if (Array.isArray(value)) {
result[ReplaceRegex.snake(key)] = Promise.all(value.map((prop) => toCamelCase(prop)));
break;
}
if (!Number.isNaN(value)) {
result[ReplaceRegex.snake(key)] = null;
break;
}
result[ReplaceRegex.snake(key)] = toCamelCase({ ...value });
break;
}
}
return result as ObjectToLower<Obj>;
}
export const ReplaceRegex = {
snake: (s: string) => {
return s.replace(/(_\S)/gi, (a) => a[1].toUpperCase());
},
camel: (s: string) => {
return s.replace(/[A-Z]/g, (a) => `_${a.toLowerCase()}`);
}
};
// https://github.com/discordeno/discordeno/blob/main/packages/utils/src/colors.ts
export interface Code {
open: string;
close: string;
regexp: RegExp;
}
/** RGB 8-bits per channel. Each in range `0->255` or `0x00->0xff` */
export interface Rgb {
r: number;
g: number;
b: number;
}
let enabled = true;
/**
* Set changing text color to enabled or disabled
* @param value
*/
export function setColorEnabled(value: boolean) {
enabled = value;
}
/** Get whether text color change is enabled or disabled. */
export function getColorEnabled(): boolean {
return enabled;
}
/**
* Builds color code
* @param open
* @param close
*/
function code(open: number[], close: number): Code {
return {
open: `\x1b[${open.join(';')}m`,
close: `\x1b[${close}m`,
regexp: new RegExp(`\\x1b\\[${close}m`, 'g')
};
}
/**
* Applies color and background based on color code and its associated text
* @param str text to apply color settings to
* @param code color code to apply
*/
function run(str: string, code: Code): string {
return enabled ? `${code.open}${str.replace(code.regexp, code.open)}${code.close}` : str;
}
/**
* Reset the text modified
* @param str text to reset
*/
export function reset(str: string): string {
return run(str, code([0], 0));
}
/**
* Make the text bold.
* @param str text to make bold
*/
export function bold(str: string): string {
return run(str, code([1], 22));
}
/**
* The text emits only a small amount of light.
* @param str text to dim
*/
export function dim(str: string): string {
return run(str, code([2], 22));
}
/**
* Make the text italic.
* @param str text to make italic
*/
export function italic(str: string): string {
return run(str, code([3], 23));
}
/**
* Make the text underline.
* @param str text to underline
*/
export function underline(str: string): string {
return run(str, code([4], 24));
}
/**
* Invert background color and text color.
* @param str text to invert its color
*/
export function inverse(str: string): string {
return run(str, code([7], 27));
}
/**
* Make the text hidden.
* @param str text to hide
*/
export function hidden(str: string): string {
return run(str, code([8], 28));
}
/**
* Put horizontal line through the center of the text.
* @param str text to strike through
*/
export function strikethrough(str: string): string {
return run(str, code([9], 29));
}
/**
* Set text color to black.
* @param str text to make black
*/
export function black(str: string): string {
return run(str, code([30], 39));
}
/**
* Set text color to red.
* @param str text to make red
*/
export function red(str: string): string {
return run(str, code([31], 39));
}
/**
* Set text color to green.
* @param str text to make green
*/
export function green(str: string): string {
return run(str, code([32], 39));
}
/**
* Set text color to yellow.
* @param str text to make yellow
*/
export function yellow(str: string): string {
return run(str, code([33], 39));
}
/**
* Set text color to blue.
* @param str text to make blue
*/
export function blue(str: string): string {
return run(str, code([34], 39));
}
/**
* Set text color to magenta.
* @param str text to make magenta
*/
export function magenta(str: string): string {
return run(str, code([35], 39));
}
/**
* Set text color to cyan.
* @param str text to make cyan
*/
export function cyan(str: string): string {
return run(str, code([36], 39));
}
/**
* Set text color to white.
* @param str text to make white
*/
export function white(str: string): string {
return run(str, code([37], 39));
}
/**
* Set text color to gray.
* @param str text to make gray
*/
export function gray(str: string): string {
return brightBlack(str);
}
/**
* Set text color to bright black.
* @param str text to make bright-black
*/
export function brightBlack(str: string): string {
return run(str, code([90], 39));
}
/**
* Set text color to bright red.
* @param str text to make bright-red
*/
export function brightRed(str: string): string {
return run(str, code([91], 39));
}
/**
* Set text color to bright green.
* @param str text to make bright-green
*/
export function brightGreen(str: string): string {
return run(str, code([92], 39));
}
/**
* Set text color to bright yellow.
* @param str text to make bright-yellow
*/
export function brightYellow(str: string): string {
return run(str, code([93], 39));
}
/**
* Set text color to bright blue.
* @param str text to make bright-blue
*/
export function brightBlue(str: string): string {
return run(str, code([94], 39));
}
/**
* Set text color to bright magenta.
* @param str text to make bright-magenta
*/
export function brightMagenta(str: string): string {
return run(str, code([95], 39));
}
/**
* Set text color to bright cyan.
* @param str text to make bright-cyan
*/
export function brightCyan(str: string): string {
return run(str, code([96], 39));
}
/**
* Set text color to bright white.
* @param str text to make bright-white
*/
export function brightWhite(str: string): string {
return run(str, code([97], 39));
}
/**
* Set background color to black.
* @param str text to make its background black
*/
export function bgBlack(str: string): string {
return run(str, code([40], 49));
}
/**
* Set background color to red.
* @param str text to make its background red
*/
export function bgRed(str: string): string {
return run(str, code([41], 49));
}
/**
* Set background color to green.
* @param str text to make its background green
*/
export function bgGreen(str: string): string {
return run(str, code([42], 49));
}
/**
* Set background color to yellow.
* @param str text to make its background yellow
*/
export function bgYellow(str: string): string {
return run(str, code([43], 49));
}
/**
* Set background color to blue.
* @param str text to make its background blue
*/
export function bgBlue(str: string): string {
return run(str, code([44], 49));
}
/**
* Set background color to magenta.
* @param str text to make its background magenta
*/
export function bgMagenta(str: string): string {
return run(str, code([45], 49));
}
/**
* Set background color to cyan.
* @param str text to make its background cyan
*/
export function bgCyan(str: string): string {
return run(str, code([46], 49));
}
/**
* Set background color to white.
* @param str text to make its background white
*/
export function bgWhite(str: string): string {
return run(str, code([47], 49));
}
/**
* Set background color to bright black.
* @param str text to make its background bright-black
*/
export function bgBrightBlack(str: string): string {
return run(str, code([100], 49));
}
/**
* Set background color to bright red.
* @param str text to make its background bright-red
*/
export function bgBrightRed(str: string): string {
return run(str, code([101], 49));
}
/**
* Set background color to bright green.
* @param str text to make its background bright-green
*/
export function bgBrightGreen(str: string): string {
return run(str, code([102], 49));
}
/**
* Set background color to bright yellow.
* @param str text to make its background bright-yellow
*/
export function bgBrightYellow(str: string): string {
return run(str, code([103], 49));
}
/**
* Set background color to bright blue.
* @param str text to make its background bright-blue
*/
export function bgBrightBlue(str: string): string {
return run(str, code([104], 49));
}
/**
* Set background color to bright magenta.
* @param str text to make its background bright-magenta
*/
export function bgBrightMagenta(str: string): string {
return run(str, code([105], 49));
}
/**
* Set background color to bright cyan.
* @param str text to make its background bright-cyan
*/
export function bgBrightCyan(str: string): string {
return run(str, code([106], 49));
}
/**
* Set background color to bright white.
* @param str text to make its background bright-white
*/
export function bgBrightWhite(str: string): string {
return run(str, code([107], 49));
}
/* Special Color Sequences */
/**
* Clam and truncate color codes
* @param n
* @param max number to truncate to
* @param min number to truncate from
*/
function clampAndTruncate(n: number, max = 255, min = 0): number {
return Math.trunc(Math.max(Math.min(n, max), min));
}
/**
* Set text color using paletted 8bit colors.
* https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
* @param str text color to apply paletted 8bit colors to
* @param color code
*/
export function rgb8(str: string, color: number): string {
return run(str, code([38, 5, clampAndTruncate(color)], 39));
}
/**
* Set background color using paletted 8bit colors.
* https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
* @param str text color to apply paletted 8bit background colors to
* @param color code
*/
export function bgRgb8(str: string, color: number): string {
return run(str, code([48, 5, clampAndTruncate(color)], 49));
}
/**
* Set text color using 24bit rgb.
* `color` can be a number in range `0x000000` to `0xffffff` or
* an `Rgb`.
*
* To produce the color magenta:
*
* ```ts
* import { rgb24 } from "./colors.ts";
* rgb24("foo", 0xff00ff);
* rgb24("foo", {r: 255, g: 0, b: 255});
* ```
* @param str text color to apply 24bit rgb to
* @param color code
*/
export function rgb24(str: string, color: number | Rgb): string {
if (typeof color === 'number') {
return run(str, code([38, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff], 39));
}
return run(str, code([38, 2, clampAndTruncate(color.r), clampAndTruncate(color.g), clampAndTruncate(color.b)], 39));
}
/**
* Set background color using 24bit rgb.
* `color` can be a number in range `0x000000` to `0xffffff` or
* an `Rgb`.
*
* To produce the color magenta:
*
* ```ts
* import { bgRgb24 } from "./colors.ts";
* bgRgb24("foo", 0xff00ff);
* bgRgb24("foo", {r: 255, g: 0, b: 255});
* ```
* @param str text color to apply 24bit rgb to
* @param color code
*/
export function bgRgb24(str: string, color: number | Rgb): string {
if (typeof color === 'number') {
return run(str, code([48, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff], 49));
}
return run(str, code([48, 2, clampAndTruncate(color.r), clampAndTruncate(color.g), clampAndTruncate(color.b)], 49));
}
// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js
const ANSI_PATTERN = new RegExp(
[
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))'
].join('|'),
'g'
);
/**
* Remove ANSI escape codes from the string.
* @param string to remove ANSI escape codes from
*/
export function stripColor(string: string): string {
return string.replace(ANSI_PATTERN, '');
}
export function delay<T>(time: number, result?: T) {
return setTimeout(time, result);
}

View File

@ -0,0 +1,29 @@
export function applyToClass<
T extends new (
..._args: ConstructorParameters<T>
) => InstanceType<T>,
U extends new (
..._args: ConstructorParameters<U>
) => InstanceType<U>
// @ts-expect-error
>(structToApply: T, struct: U, ignore?: (keyof T['prototype'])[]) {
const props = Object.getOwnPropertyNames(structToApply.prototype);
for (const prop of props) {
if (ignore?.includes(prop as keyof T) || prop === 'constructor') continue;
Object.defineProperty(struct.prototype, prop, Object.getOwnPropertyDescriptor(structToApply.prototype, prop)!);
}
return struct as unknown as Struct<T, U>;
}
// rome-ignore lint/nursery/noBannedTypes: fix applyToClass typing
export type Struct<ToMix = {}, Final = {}> = Final extends new (
..._args: never[]
) => infer F
? ToMix extends new (
..._args: never[]
) => infer TM
? new (
..._args: ConstructorParameters<Final>
) => F & TM
: never
: never;

View File

@ -0,0 +1,7 @@
export * from './Constants';
export * from './Util';
export * from './Types';
export * from 'discord-api-types/v10';
export * from './applyToClass';
export * from './Collection';
export * from './Logger';

View File

@ -8,5 +8,5 @@ export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
minify: isProduction,
sourcemap: false,
sourcemap: false
});

View File

@ -1,5 +1,5 @@
# @biscuitland/core
Classes, functions and main structures to create an application with biscuit. Core contains the essentials to launch you to develop your own customized and scalable bot.
Core contains the essentials to launch you to develop your own customized and scalable bot.
[<img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white">](https://github.com/oasisjs/biscuit)
[<img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white">](https://discord.gg/XNw2RZFzaP)
@ -17,29 +17,12 @@ yarn add @biscuitland/core
`project/index.js`:
```js
import { Session } from '@biscuitland/core';
import { GatewayIntents } from '@biscuitland/api-types';
import { GatewayIntentBits } from "discord-api-types/v10";
const session = new Session({ token: 'your token', intents: GatewayIntents.Guilds });
const session = new Session({ token: 'your token', intents: GatewayIntentBits.Guilds });
const commands = [
{
name: 'ping',
description: 'Replies with pong!'
}
];
session.events.on('ready', ({ user }) => {
console.log('Logged in as:', user.tag);
session.upsertApplicationCommands(commands, 'GUILD_ID');
});
session.events.on('interactionCreate', (interaction) => {
if (interaction.isCommand()) {
// your commands go here
if (interaction.commandName === 'ping') {
interaction.respondWith({ content: 'pong!' });
}
}
session.events.on('READY', (payload) => {
console.log('Logged in as:', payload.user.username);
});
session.start();
@ -60,4 +43,4 @@ B:\project> node --experimental-fetch index.js
* [Website](https://biscuitjs.com/)
* [Documentation](https://docs.biscuitjs.com/)
* [Discord](https://discord.gg/XNw2RZFzaP)
* [api-types](https://www.npmjs.com/package/@biscuitland/api-types) | [cache](https://www.npmjs.com/package/@biscuitland/cache) | [rest](https://www.npmjs.com/package/@biscuitland/rest) | [ws](https://www.npmjs.com/package/@biscuitland/ws) | [helpers](https://www.npmjs.com/package/@biscuitland/helpers)
* [rest](https://www.npmjs.com/package/@biscuitland/rest) | [ws](https://www.npmjs.com/package/@biscuitland/ws) | [helpers](https://www.npmjs.com/package/@biscuitland/helpers)

View File

@ -1,6 +1,6 @@
{
"name": "@biscuitland/core",
"version": "2.3.0",
"version": "3.0.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@ -23,9 +23,10 @@
}
},
"dependencies": {
"@biscuitland/api-types": "^2.3.0",
"@biscuitland/rest": "^2.3.0",
"@biscuitland/ws": "^2.3.0"
"@biscuitland/common": "^0.0.1",
"@biscuitland/rest": "^3.0.0",
"@biscuitland/ws": "^3.0.0",
"eventemitter2": "^6.4.9"
},
"devDependencies": {
"@types/node": "^18.7.14",
@ -36,8 +37,7 @@
"contributors": [
{
"name": "Yuzuru",
"url": "https://github.com/yuzudev",
"author": true
"url": "https://github.com/yuzudev"
},
{
"name": "miia",
@ -49,7 +49,8 @@
},
{
"name": "socram03",
"url": "https://github.com/socram03"
"url": "https://github.com/socram03",
"author": true
},
{
"name": "Drylozu",

View File

@ -1,25 +0,0 @@
import type { EventAdapter } from './event-adapter';
import type { Events } from './events';
import EventEmitter from 'node:events';
export class DefaultEventAdapter extends EventEmitter implements EventAdapter {
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 keyof Events>(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);
}
}

View File

@ -1,25 +0,0 @@
import type { Events } from './events';
export interface EventAdapter extends Omit<NodeJS.EventEmitter, 'emit' | 'on' | 'off' | 'once'> {
options?: any;
emit<K extends keyof Events>(
event: K,
...params: Parameters<Events[K]>
): boolean;
on<K extends keyof Events>(
event: K,
func: Events[K]
): unknown;
off<K extends keyof Events>(
event: K,
func: Events[K]
): unknown;
once<K extends keyof Events>(
event: K,
func: Events[K]
): unknown;
}

View File

@ -1,837 +0,0 @@
import type {
DiscordAutoModerationActionExecution,
DiscordAutoModerationRule,
DiscordChannel,
DiscordChannelPinsUpdate,
DiscordEmoji,
DiscordGuild,
DiscordGuildBanAddRemove,
DiscordGuildEmojisUpdate,
DiscordGuildMemberAdd,
DiscordGuildMemberRemove,
DiscordGuildMemberUpdate,
DiscordGuildMembersChunk,
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,
DiscordVoiceState,
DiscordVoiceServerUpdate,
} from '@biscuitland/api-types';
import type { Session } from '../biscuit';
import type { Interaction } from '../structures/interactions';
import type { Snowflake } from '../snowflakes';
import {
AutoModerationRule,
AutoModerationExecution,
} from '../structures/automod';
import type { Channel } from '../structures/channels';
import {
ChannelFactory,
GuildChannel,
ThreadChannel,
} from '../structures/channels';
import type { DiscordStageInstanceB } from '../structures/stage-instance';
import { StageInstance } from '../structures/stage-instance';
import { ScheduledEvent } from '../structures/scheduled-events';
import { Presence } from '../structures/presence';
import { Member, ThreadMember } from '../structures/members';
import { Message } from '../structures/message';
import { User } from '../structures/user';
import { Integration } from '../structures/integration';
import { Guild } from '../structures/guilds';
import { InteractionFactory } from '../structures/interactions';
import type { InviteCreate } from '../structures/invite';
import { NewInviteCreate } from '../structures/invite';
import type {
MessageReactionAdd,
MessageReactionRemove,
MessageReactionRemoveAll,
MessageReactionRemoveEmoji,
} from '../structures/message-reaction';
import { NewMessageReactionAdd } from '../structures/message-reaction';
import { Util, PartialMessage } from '../utils/util';
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.events.emit(
'ready',
{ ...payload, user: new User(session, payload.user) },
shardId
);
};
export const MESSAGE_CREATE: RawHandler<DiscordMessage> = (
session,
_shardId,
message
) => {
session.events.emit('messageCreate', new Message(session, message));
};
export const MESSAGE_UPDATE: RawHandler<PartialMessage> = (
session,
_shardId,
new_message
) => {
// message is partial
if (Util.isPartialMessage(new_message)) {
const message = {
session,
id: new_message.id,
guildId: new_message.guild_id,
channelId: new_message.channel_id,
fields: new_message,
}; //satisfies Partial<Message>;
// 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.events.emit('messageUpdate', message);
return;
}
if (Util.isFullMessage(new_message)) {
session.events.emit('messageUpdate', {
...new Message(session, new_message),
fields: {} as PartialMessage
});
}
};
export const MESSAGE_DELETE: RawHandler<DiscordMessageDelete> = (
session,
_shardId,
{ id, channel_id, guild_id }
) => {
session.events.emit('messageDelete', {
id,
channelId: channel_id,
guildId: guild_id,
});
};
export const GUILD_CREATE: RawHandler<DiscordGuild> = (
session,
_shardId,
guild
) => {
session.events.emit('guildCreate', new Guild(session, guild));
};
export const GUILD_DELETE: RawHandler<DiscordGuild> = (
session,
_shardId,
guild
) => {
session.events.emit('guildDelete', { id: guild.id, unavailable: true });
};
export const GUILD_MEMBER_ADD: RawHandler<DiscordGuildMemberAdd> = (
session,
_shardId,
member
) => {
session.events.emit(
'guildMemberAdd',
new Member(session, member, member.guild_id)
);
};
export const GUILD_MEMBER_UPDATE: RawHandler<DiscordGuildMemberUpdate> = (
session,
_shardId,
member
) => {
session.events.emit(
'guildMemberUpdate',
new Member(session, member, member.guild_id)
);
};
export const GUILD_MEMBER_REMOVE: RawHandler<DiscordGuildMemberRemove> = (
session,
_shardId,
member
) => {
session.events.emit(
'guildMemberRemove',
new User(session, member.user),
member.guild_id
);
};
export const GUILD_BAN_ADD: RawHandler<DiscordGuildBanAddRemove> = (
session,
_shardId,
data
) => {
session.events.emit('guildBanAdd', {
guildId: data.guild_id,
user: data.user,
});
};
export const GUILD_BAN_REMOVE: RawHandler<DiscordGuildBanAddRemove> = (
session,
_shardId,
data
) => {
session.events.emit('guildBanRemove', {
guildId: data.guild_id,
user: data.user,
});
};
export const GUILD_EMOJIS_UPDATE: RawHandler<DiscordGuildEmojisUpdate> = (
session,
_shardId,
data
) => {
session.events.emit('guildEmojisUpdate', {
guildId: data.guild_id,
emojis: data.emojis,
});
};
export const GUILD_ROLE_CREATE: RawHandler<DiscordGuildRoleCreate> = (
session,
_shardId,
data
) => {
session.events.emit('guildRoleCreate', {
guildId: data.guild_id,
role: data.role,
});
};
export const GUILD_ROLE_UPDATE: RawHandler<DiscordGuildRoleUpdate> = (
session,
_shardId,
data
) => {
session.events.emit('guildRoleUpdate', {
guildId: data.guild_id,
role: data.role,
});
};
export const GUILD_ROLE_DELETE: RawHandler<DiscordGuildRoleDelete> = (
session,
_shardId,
data
) => {
session.events.emit('guildRoleDelete', {
guildId: data.guild_id,
roleId: data.role_id,
});
};
export const TYPING_START: RawHandler<DiscordTypingStart> = (
session,
_shardId,
payload
) => {
session.events.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.events.emit(
'interactionCreate',
InteractionFactory.from(session, interaction)
);
};
export const CHANNEL_CREATE: RawHandler<DiscordChannel> = (
session,
_shardId,
channel
) => {
session.events.emit('channelCreate', ChannelFactory.from(session, channel));
};
export const CHANNEL_UPDATE: RawHandler<DiscordChannel> = (
session,
_shardId,
channel
) => {
session.events.emit('channelUpdate', ChannelFactory.from(session, channel));
};
export const CHANNEL_DELETE: RawHandler<DiscordChannel> = (
session,
_shardId,
channel
) => {
if (!channel.guild_id) {
return;
}
session.events.emit(
'channelDelete',
new GuildChannel(session, channel, channel.guild_id)
);
};
export const THREAD_CREATE: RawHandler<DiscordChannel> = (
session,
_shardId,
channel
) => {
if (!channel.guild_id) {
return;
}
session.events.emit(
'threadCreate',
new ThreadChannel(session, channel, channel.guild_id)
);
};
export const THREAD_UPDATE: RawHandler<DiscordChannel> = (
session,
_shardId,
channel
) => {
if (!channel.guild_id) {
return;
}
session.events.emit(
'threadUpdate',
new ThreadChannel(session, channel, channel.guild_id)
);
};
export const THREAD_DELETE: RawHandler<DiscordChannel> = (
session,
_shardId,
channel
) => {
if (!channel.guild_id) {
return;
}
session.events.emit(
'threadDelete',
new ThreadChannel(session, channel, channel.guild_id)
);
};
export const THREAD_MEMBER_UPDATE: RawHandler<DiscordThreadMemberUpdate> = (
session,
_shardId,
payload
) => {
session.events.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.events.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.events.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.events.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.events.emit('userUpdate', new User(session, payload));
};
export const PRESENCE_UPDATE: RawHandler<DiscordPresenceUpdate> = (
session,
_shardId,
payload
) => {
session.events.emit('presenceUpdate', new Presence(session, payload));
};
export const WEBHOOKS_UPDATE: RawHandler<DiscordWebhookUpdate> = (
session,
_shardId,
webhook
) => {
session.events.emit('webhooksUpdate', {
guildId: webhook.guild_id,
channelId: webhook.channel_id,
});
};
export const INTEGRATION_CREATE: RawHandler<
DiscordIntegration & { guildId?: Snowflake }
> = (session, _shardId, payload) => {
session.events.emit('integrationCreate', new Integration(session, payload));
};
export const INTEGRATION_UPDATE: RawHandler<
DiscordIntegration & { guildId?: Snowflake }
> = (session, _shardId, payload) => {
session.events.emit('integrationCreate', new Integration(session, payload));
};
export const INTEGRATION_DELETE: RawHandler<DiscordIntegrationDelete> = (
session,
_shardId,
payload
) => {
session.events.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.events.emit(
'autoModerationRuleCreate',
new AutoModerationRule(session, payload)
);
};
export const AUTO_MODERATION_RULE_UPDATE: RawHandler<
DiscordAutoModerationRule
> = (session, _shardId, payload) => {
session.events.emit(
'autoModerationRuleUpdate',
new AutoModerationRule(session, payload)
);
};
export const AUTO_MODERATION_RULE_DELETE: RawHandler<
DiscordAutoModerationRule
> = (session, _shardId, payload) => {
session.events.emit(
'autoModerationRuleDelete',
new AutoModerationRule(session, payload)
);
};
export const AUTO_MODERATION_ACTION_EXECUTE: RawHandler<
DiscordAutoModerationActionExecution
> = (session, _shardId, payload) => {
session.events.emit(
'autoModerationActionExecution',
new AutoModerationExecution(session, payload)
);
};
export const MESSAGE_REACTION_ADD: RawHandler<DiscordMessageReactionAdd> = (
session,
_shardId,
reaction
) => {
session.events.emit(
'messageReactionAdd',
NewMessageReactionAdd(session, reaction)
);
};
export const MESSAGE_REACTION_REMOVE: RawHandler<
DiscordMessageReactionRemove
> = (session, _shardId, reaction) => {
session.events.emit(
'messageReactionRemove',
NewMessageReactionAdd(session, reaction)
);
};
export const MESSAGE_REACTION_REMOVE_ALL: RawHandler<
DiscordMessageReactionRemoveAll
> = (session, _shardId, reaction) => {
session.events.emit(
'messageReactionRemoveAll',
NewMessageReactionAdd(session, reaction as DiscordMessageReactionAdd)
);
};
export const MESSAGE_REACTION_REMOVE_EMOJI: RawHandler<
DiscordMessageReactionRemoveEmoji
> = (session, _shardId, reaction) => {
session.events.emit(
'messageReactionRemoveEmoji',
NewMessageReactionAdd(session, reaction as DiscordMessageReactionAdd)
);
};
export const INVITE_CREATE: RawHandler<DiscordInviteCreate> = (
session,
_shardId,
invite
) => {
session.events.emit('inviteCreate', NewInviteCreate(session, invite));
};
export const INVITE_DELETE: RawHandler<DiscordInviteDelete> = (
session,
_shardId,
data
) => {
session.events.emit('inviteDelete', {
channelId: data.channel_id,
guildId: data.guild_id,
code: data.code,
});
};
export const STAGE_INSTANCE_CREATE: RawHandler<DiscordStageInstanceB> = (
session,
_shardId,
payload
) => {
session.events.emit(
'stageInstanceCreate',
new StageInstance(session, payload)
);
};
export const STAGE_INSTANCE_UPDATE: RawHandler<DiscordStageInstanceB> = (
session,
_shardId,
payload
) => {
session.events.emit(
'stageInstanceUpdate',
new StageInstance(session, payload)
);
};
export const STAGE_INSTANCE_DELETE: RawHandler<DiscordStageInstanceB> = (
session,
_shardId,
payload
) => {
session.events.emit(
'stageInstanceDelete',
new StageInstance(session, payload)
);
};
export const GUILD_SCHEDULED_EVENT_CREATE: RawHandler<DiscordScheduledEvent> = (
session,
_shardId,
payload
) => {
session.events.emit(
'guildScheduledEventCreate',
new ScheduledEvent(session, payload)
);
};
export const GUILD_SCHEDULED_EVENT_UPDATE: RawHandler<DiscordScheduledEvent> = (
session,
_shardId,
payload
) => {
session.events.emit(
'guildScheduledEventUpdate',
new ScheduledEvent(session, payload)
);
};
export const GUILD_SCHEDULED_EVENT_DELETE: RawHandler<DiscordScheduledEvent> = (
session,
_shardId,
payload
) => {
session.events.emit(
'guildScheduledEventDelete',
new ScheduledEvent(session, payload)
);
};
export const GUILD_SCHEDULED_EVENT_USER_ADD: RawHandler<
DiscordScheduledEventUserAdd
> = (session, _shardId, payload) => {
session.events.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.events.emit('guildScheduledEventUserRemove', {
scheduledEventId: payload.guild_scheduled_event_id,
userId: payload.user_id,
guildId: payload.guild_id,
});
};
export const VOICE_STATE_UPDATE: RawHandler<DiscordVoiceState> = (session, _shardId, payload) => {
if (!payload.guild_id) { return; }
session.events.emit('voiceStateUpdate', payload);
};
export const VOICE_SERVER_UPDATE: RawHandler<DiscordVoiceServerUpdate> = (session, _shardId, payload) => {
session.events.emit('voiceServerUpdate', {
token: payload.token,
guildId: payload.guild_id,
endpoint: payload.endpoint ? payload.endpoint : undefined
});
};
export const GUILD_MEMBERS_CHUNK: RawHandler<DiscordGuildMembersChunk> = (session, _shardId, payload) => {
session.events.emit('guildMembersChunk', {
guildId: payload.guild_id,
members: new Map(payload.members.map(m => [m.user.id, new Member(session, m, payload.guild_id)])),
chunkIndex: payload.chunk_index,
chunkCount: payload.chunk_count,
notFound: payload.not_found,
presences: payload.presences?.map(p => new Presence(session, p)) ?? [],
});
};
export const raw: RawHandler<unknown> = (session, shardId, data) => {
session.events.emit('raw', data as { t: string; d: unknown }, shardId);
};
export interface Ready extends Omit<DiscordReady, 'user'> {
user: User;
}
export interface GuildMembersChunk {
guildId: Snowflake;
members: Map<Snowflake, Member>;
chunkIndex: number;
chunkCount: number;
notFound?: string[];
presences: Presence[];
nonce?: string;
}
/**
* Alias of `keyof Events`. List of all events that can be emitted by the session.
* Ex: ready, messageCreate, guildCreate, etc.
* @see {@link Events}
*/
export type AllEvents = keyof Events;
export interface Events {
ready: Handler<[Ready, number]>;
messageCreate: Handler<[Message]>;
messageUpdate: Handler<[Partial<Message> & { fields: PartialMessage }]>;
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]>;
guildMembersChunk: Handler<[GuildMembersChunk]>;
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]>;
voiceStateUpdate: Handler<[DiscordVoiceState]>;
voiceServerUpdate: Handler<
[{ token: string; guildId: Snowflake; endpoint?: string }]
>;
}

View File

@ -1,496 +0,0 @@
import type {
AtLeastOne,
ApplicationCommandPermissionTypes,
DiscordApplicationCommand,
// DiscordGatewayPayload,
DiscordGuildApplicationCommandPermissions,
DiscordUser,
DiscordApplicationCommandOption,
GatewayIntents,
Localization,
Snowflake,
DiscordGetGatewayBot,
DiscordGatewayPayload
} from '@biscuitland/api-types';
import { ApplicationCommandTypes, GatewayOpcodes ,
APPLICATION_COMMANDS,
GUILD_APPLICATION_COMMANDS,
GUILD_APPLICATION_COMMANDS_PERMISSIONS,
GUILD_APPLICATION_COMMANDS_LOCALIZATIONS,
USER
} from '@biscuitland/api-types';
// routes
import type { PermissionResolvable } from './structures/special/permissions';
import type { Activities, StatusTypes } from './structures/presence';
// structs
import { User } from './structures/user';
// DiscordGetGatewayBot;
import type { RestAdapter } from '@biscuitland/rest';
import { DefaultRestAdapter } from '@biscuitland/rest';
import type { Shard } from '@biscuitland/ws';
import { ShardManager, Options as mergeOptions } from '@biscuitland/ws';
import type { EventAdapter } from './adapters/event-adapter';
import { DefaultEventAdapter } from './adapters/default-event-adapter';
import { Util } from './utils/util';
// PRESENCE
/**
* @link https://discord.com/developers/docs/topics/gateway#update-status
*/
export interface StatusUpdate {
activities: Activities[];
status: keyof typeof StatusTypes;
}
// END PRESENCE
// 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;
name_localizations?: Localization;
description: string;
description_localizations?: Localization;
type?: ApplicationCommandTypes;
options?: DiscordApplicationCommandOption[];
default_member_permissions?: PermissionResolvable;
dm_permission?: 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 type DiscordRawEventHandler = (
shard: Shard,
data: MessageEvent<any>
) => unknown;
export type PickOptions = Pick<
BiscuitOptions,
Exclude<keyof BiscuitOptions, keyof typeof Session.DEFAULTS>
> &
Partial<BiscuitOptions>;
export interface BiscuitOptions {
intents?: GatewayIntents;
token: string;
precense?: {
status: keyof typeof StatusTypes;
afk: boolean;
since: number | null;
activities: Activities[];
};
events?: {
adapter?: { new (...args: any[]): EventAdapter };
options: any;
};
rest: {
adapter?: { new (...args: any[]): RestAdapter };
options: any;
};
ws: {
adapter?: { new (...args: any[]): ShardManager };
options: any;
};
}
import * as Actions from './adapters/events';
export class Session {
private _applicationId?: Snowflake;
private _botId?: Snowflake;
token: string;
set botId(snowflake: Snowflake) {
this._botId = snowflake;
}
get botId(): Snowflake {
return this._botId ?? Util.getBotIdFromToken(this.token);
}
set applicationId(snowflake: Snowflake) {
this._applicationId = snowflake;
}
get applicationId(): Snowflake {
return this._applicationId ?? this.botId;
}
static readonly DEFAULTS = {
intents: 0,
rest: {
adapter: DefaultRestAdapter,
options: null,
},
ws: {
adapter: ShardManager,
options: null,
}
};
options: BiscuitOptions;
readonly events: EventAdapter;
readonly rest: RestAdapter;
readonly ws: ShardManager;
private adapters = new Map<string, any>();
constructor(options: PickOptions) {
this.options = mergeOptions(Session.DEFAULTS, options);
// makeRest
if (!this.options.rest.options) {
this.options.rest.options = {
intents: this.options.intents,
token: this.options.token,
};
}
this.rest = this.getRest();
// makeWs
const defHandler = (shard: Shard, payload: DiscordGatewayPayload) => {
Actions.raw(this, shard.options.id, payload);
if (!payload.t || !payload.d) {
return;
}
Actions[payload.t as keyof typeof Actions]?.(
this,
shard.options.id,
payload.d as any
);
};
if (!this.options.ws.options) {
this.options.ws.options = {
handleDiscordPayload: defHandler,
gateway: {
url: '',
shards: '',
session_start_limit: {
total: 1000,
remaining: 1000,
reset_after: 3600000,
max_concurrency: 1
}
},
config: {
token: this.options.token,
intents: this.options.intents,
},
makePresence: this.options.precense
};
}
// makeEvents
this.events = this.options.events?.adapter ? new this.options.events.adapter() : new DefaultEventAdapter();
this.ws = this.getWs();
this.token = options.token;
}
/**
* @inheritDoc
*/
private getAdapter<T extends { new (...args: any[]): InstanceType<T> }>(
adapter: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
if (!this.adapters.has(adapter.name)) {
const Class = adapter as { new (...args: any[]): T };
this.adapters.set(adapter.name, new Class(...args));
}
return this.adapters.get(adapter.name);
}
/**
* @inheritDoc
*/
private getRest(): RestAdapter {
return this.getAdapter(
this.options.rest.adapter!,
this.options.rest.options
);
}
/**
* @inheritDoc
*/
private getWs(): ShardManager {
return this.getAdapter(
this.options.ws.adapter!,
this.options.ws.options
);
}
/**
* @inheritDoc
*/
async start(): Promise<void> {
const gateway = await this.rest.get<DiscordGetGatewayBot>('/gateway/bot');
this.ws.options.gateway = gateway;
this.ws.spawns();
}
// USEFUL METHODS
async editProfile(nick?: string, avatar?: string): Promise<User> {
const user = await this.rest.patch<DiscordUser>(USER(), {
username: nick ?? null,
avatar: avatar ?? null,
});
return new User(this, user);
}
// END USEFUL METHODS
// PRESENCE
/**
* 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, prio = true): void {
const shard = this.ws.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: this.makePresenceActivites(status.activities),
},
}, prio);
}
private makePresenceActivites(act: Activities[]): Record<string, unknown>[] {
return act.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,
};
});
}
// END PRESENCE
// INTERACTIONS
updateApplicationCommandPermissions(
guildId: Snowflake,
id: Snowflake,
bearerToken: string,
options: ApplicationCommandPermissions[],
): Promise<DiscordGuildApplicationCommandPermissions> {
return this.rest.post<DiscordGuildApplicationCommandPermissions>(
GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id),
{
permissions: options,
},
{
headers: { Authorization: `Bearer ${bearerToken}` },
},
);
}
fetchApplicationCommand(id: Snowflake, options?: GetApplicationCommand): Promise<DiscordApplicationCommand> {
return this.rest.get<DiscordApplicationCommand>(
options?.guildId
? GUILD_APPLICATION_COMMANDS_LOCALIZATIONS(
this.applicationId,
options.guildId,
id,
options?.withLocalizations,
)
: APPLICATION_COMMANDS(this.applicationId, id),
);
}
fetchApplicationCommandPermissions(guildId: Snowflake): Promise<DiscordGuildApplicationCommandPermissions[]> {
return this.rest.get<DiscordGuildApplicationCommandPermissions[]>(
GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId),
);
}
fetchApplicationCommandPermission(
guildId: Snowflake,
id: Snowflake,
): Promise<DiscordGuildApplicationCommandPermissions> {
return this.rest.get<DiscordGuildApplicationCommandPermissions>(
GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id),
);
}
upsertApplicationCommand(
id: Snowflake,
options: UpsertDataApplicationCommands,
guildId?: Snowflake,
): Promise<DiscordApplicationCommand> {
return this.rest.patch<DiscordApplicationCommand>(
guildId
? GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
: 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.put<DiscordApplicationCommand[]>(
guildId
? GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
: 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.get<DiscordApplicationCommand[]>(
guildId
? GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
: APPLICATION_COMMANDS(this.applicationId),
);
}
isContextApplicationCommand(cmd: LastCreateApplicationCommands): cmd is AtLeastOne<CreateContextApplicationCommand> {
return cmd.type === ApplicationCommandTypes.Message || cmd.type === ApplicationCommandTypes.User;
}
// END INTERACTIONS
}

View File

@ -0,0 +1,15 @@
import type { Session } from '../index';
import type { GatewayEvents } from '@biscuitland/ws';
export function actionHandler([session, payload, shardId]: Parameters<ActionHandler>) {
// @ts-expect-error At this point, typescript sucks
session.emit(payload.t, payload.d, shardId);
}
export type ActionHandler<G extends keyof GatewayEvents = keyof GatewayEvents,> = (
...args: [Session<true>, { t: G; d: GatewayEvents[G] }, number]
) => unknown;
export type Handler = {
[K in keyof GatewayEvents]: (...args: [GatewayEvents[K], number]) => unknown;
};

View File

@ -1,21 +1,6 @@
// SESSION
export * as Actions from './adapters/events';
export { Session as Biscuit } from './biscuit';
export * from './biscuit';
// STRUCTURES
export * from './structures';
// EVENTS
export * from './adapters/events';
export * from './adapters/event-adapter';
export * from './adapters/default-event-adapter';
// ETC
export * from './snowflakes';
// UTIL
export * from './utils/calculate-shard';
export * from './utils/url-to-base-64';
export * from './utils/util';
export * from './utils/types';
export * from './utils/utils';
export * from './session';
export { Session as Biscuit } from './session';
export * from './managers/MainManager';
export * from './events/handler';

View File

@ -0,0 +1,121 @@
import {
APIApplicationCommand,
Identify,
MakeRequired,
RESTGetAPIApplicationCommandsQuery,
RESTGetAPIApplicationGuildCommandsQuery,
RESTPatchAPIApplicationCommandJSONBody,
RESTPatchAPIApplicationGuildCommandJSONBody,
RESTPostAPIApplicationCommandsJSONBody,
RESTPostAPIApplicationGuildCommandsJSONBody,
RESTPutAPIApplicationCommandPermissionsJSONBody,
RESTPutAPIApplicationCommandsJSONBody,
RESTPutAPIApplicationGuildCommandsJSONBody,
RESTPutAPIApplicationRoleConnectionMetadataJSONBody
} from '@biscuitland/common';
import { Session } from '..';
export class ApplicationManager {
readonly session!: Session;
constructor(session: Session) {
Object.defineProperty(this, 'session', {
value: session,
writable: false
});
}
getBotGateway() {
return this.session.api.gateway.bot.get();
}
getGateway() {
return this.session.api.gateway.get();
}
getNitroStickerPacks() {
return this.session.api['sticker-packs'].get();
}
getRoleConnectionMetadata(applicationId: string) {
return this.session.api.applications(applicationId)['role-connections'].metadata.get();
}
editRoleConnectionMetadata(applicationId: string, body: RESTPutAPIApplicationRoleConnectionMetadataJSONBody) {
return this.session.api.applications(applicationId)['role-connections'].metadata.put({ body });
}
getCommands(
applicationId: string,
query: RESTGetAPIApplicationCommandsQuery
): Promise<RESTGetAPIApplicationCommandsWithLocalizationsResult>;
getCommands(applicationId: string, query?: RESTGetAPIApplicationCommandsQuery) {
return this.session.api.applications(applicationId).commands.get({ query });
}
createCommand(applicationId: string, body: RESTPostAPIApplicationCommandsJSONBody) {
return this.session.api.applications(applicationId).commands.post({ body });
}
getCommand(applicationId: string, commandId: string) {
return this.session.api.applications(applicationId).commands(commandId).get();
}
editCommand(applicationId: string, commandId: string, body: RESTPatchAPIApplicationCommandJSONBody) {
return this.session.api.applications(applicationId).commands(commandId).patch({ body });
}
deleteCommand(applicationId: string, commandId: string) {
return this.session.api.applications(applicationId).commands(commandId).delete();
}
bulkCommands(applicationId: string, body: RESTPutAPIApplicationCommandsJSONBody) {
return this.session.api.applications(applicationId).commands.put({ body });
}
getCommandPermissions(applicationId: string, guildId: string, commandId: string) {
return this.session.api.applications(applicationId).guilds(guildId).commands(commandId).permissions.get();
}
editCommandPermissions(applicationId: string, guildId: string, commandId: string, body: RESTPutAPIApplicationCommandPermissionsJSONBody) {
return this.session.api.applications(applicationId).guilds(guildId).commands(commandId).permissions.put({ body });
}
getGuildCommands(
applicationId: string,
guildId: string,
query: RESTGetAPIApplicationGuildCommandsQuery
): Promise<RESTGetAPIApplicationGuildCommandsWithLocalizationsResult>;
getGuildCommands(applicationId: string, guildId: string, query?: RESTGetAPIApplicationGuildCommandsQuery) {
return this.session.api.applications(applicationId).guilds(guildId).commands.get({ query });
}
createGuildCommand(applicationId: string, guildId: string, body: RESTPostAPIApplicationGuildCommandsJSONBody) {
return this.session.api.applications(applicationId).guilds(guildId).commands.post({ body });
}
getGuildCommand(applicationId: string, guildId: string, commandId: string) {
return this.session.api.applications(applicationId).guilds(guildId).commands(commandId).get();
}
editGuildCommand(applicationId: string, guildId: string, commandId: string, body: RESTPatchAPIApplicationGuildCommandJSONBody) {
return this.session.api.applications(applicationId).guilds(guildId).commands(commandId).patch({ body });
}
deleteGuildCommand(applicationId: string, guildId: string, commandId: string) {
return this.session.api.applications(applicationId).guilds(guildId).commands(commandId).delete();
}
bulkGuildCommands(applicationId: string, guildId: string, body: RESTPutAPIApplicationGuildCommandsJSONBody) {
return this.session.api.applications(applicationId).guilds(guildId).commands.put({ body });
}
getGuildCommandPermissions(applicationId: string, guildId: string) {
return this.session.api.applications(applicationId).guilds(guildId).commands.permissions.get();
}
}
export type RESTGetAPIApplicationCommandsWithLocalizationsResult = Identify<
MakeRequired<APIApplicationCommand, 'name_localizations' | 'description_localizations'>
>[];
export type RESTGetAPIApplicationGuildCommandsWithLocalizationsResult = RESTGetAPIApplicationCommandsWithLocalizationsResult;

View File

@ -0,0 +1,214 @@
import type {
APIChannel,
RESTPostAPIChannelMessageJSONBody,
RESTPatchAPIChannelJSONBody,
RESTGetAPIChannelThreadsArchivedQuery,
RESTGetAPIChannelMessageReactionUsersQuery,
RESTPatchAPIChannelMessageJSONBody,
RESTPostAPIChannelMessagesBulkDeleteJSONBody,
RESTPutAPIChannelPermissionJSONBody,
RESTPostAPIChannelInviteJSONBody,
RESTPostAPIChannelFollowersJSONBody,
RESTPutAPIChannelRecipientJSONBody,
RESTPostAPIChannelMessagesThreadsJSONBody,
RESTPostAPIChannelThreadsJSONBody,
RESTPostAPIChannelThreadsResult,
RESTPostAPIGuildForumThreadsJSONBody,
RESTGetAPIChannelThreadMembersQuery,
RESTGetAPIChannelThreadMemberQuery,
RESTPostAPIChannelWebhookJSONBody,
RESTPatchAPIStageInstanceJSONBody
} from '@biscuitland/common';
import type { RawFile } from '@biscuitland/rest';
import type { Session } from '../session';
export class ChannelManager {
readonly session!: Session;
constructor(session: Session) {
Object.defineProperty(this, 'session', {
value: session,
writable: false
});
}
get<T extends APIChannel = APIChannel>(id: string) {
return this.session.api.channels(id).get() as Promise<T>;
}
getWebhooks(channelId: string) {
return this.session.api.channels(channelId).webhooks.get();
}
createWebhook(channelId: string, body: RESTPostAPIChannelWebhookJSONBody) {
return this.session.api.channels(channelId).webhooks.post({ body });
}
edit(id: string, data: RESTPatchAPIChannelJSONBody) {
return this.session.api.channels(id).patch({ body: data });
}
delete(id: string) {
return this.session.api.channels(id).delete();
}
getMessages(id: string, limit = 50) {
return this.session.api.channels(id).messages.get({
query: { limit }
});
}
getMessage(id: string, messageId: string) {
return this.session.api.channels(id).messages(messageId).get();
}
createMessage(id: string, data: RESTPostAPIChannelMessageJSONBody) {
return this.session.api.channels(id).messages.post({ body: data });
}
sendTyping(id: string) {
return this.session.api.channels(id).typing.post();
}
getArchivedThreads(channelId: string, options: RESTGetAPIChannelThreadsArchivedOptions) {
const { type, ...query } = options;
if (type === 'private') {
return this.session.api.channels(channelId).threads.archived.private.get({ query });
}
return this.session.api.channels(channelId).threads.archived.public.get({ query });
}
crosspostMessage(channelId: string, messageId: string) {
return this.session.api.channels(channelId).messages(messageId).crosspost.post({});
}
createReaction(channelId: string, messageId: string, emoji: string) {
return this.session.api.channels(channelId).messages(messageId).reactions(emoji)('@me').put({});
}
deleteReaction(channelId: string, messageId: string, emoji: string, user = '@me') {
return this.session.api.channels(channelId).messages(messageId).reactions(emoji)(user).delete();
}
getReactions(channelId: string, messageId: string, emoji: string, query?: RESTGetAPIChannelMessageReactionUsersQuery) {
return this.session.api.channels(channelId).messages(messageId).reactions(emoji).get({ query });
}
deleteAllReactions(channelId: string, messageId: string, emoji?: string) {
if (emoji?.length) return this.session.api.channels(channelId).messages(messageId).reactions(emoji).delete();
return this.session.api.channels(channelId).messages(messageId).reactions.delete();
}
editMessage(channelId: string, messageId: string, body: RESTPatchAPIChannelMessageJSONBody, files?: RawFile[]) {
return this.session.api.channels(channelId).messages(messageId).patch({
body,
files
});
}
deleteMessage(channelId: string, messageId: string, reason?: string) {
return this.session.api.channels(channelId).messages(messageId).delete({ reason });
}
bulkMessages(channelId: string, body: RESTPostAPIChannelMessagesBulkDeleteJSONBody, reason?: string) {
return this.session.api.channels(channelId).messages['bulk-delete'].post({ body, reason });
}
editPermissions(channelId: string, overwriteId: string, body: RESTPutAPIChannelPermissionJSONBody, reason?: string) {
return this.session.api.channels(channelId).permissions(overwriteId).put({ body, reason });
}
deletePermission(channelId: string, overwriteId: string, reason?: string) {
return this.session.api.channels(channelId).permissions(overwriteId).delete({ reason });
}
getInvites(channelId: string) {
return this.session.api.channels(channelId).invites.get();
}
createInvite(channelId: string, body: RESTPostAPIChannelInviteJSONBody) {
return this.session.api.channels(channelId).invites.post({ body });
}
followAnnoucement(channelId: string, body: RESTPostAPIChannelFollowersJSONBody) {
return this.session.api.channels(channelId).followers.post({ body });
}
getPinnedMessages(channelId: string) {
return this.session.api.channels(channelId).pins.get();
}
pinMessage(channelId: string, messageId: string, reason?: string) {
return this.session.api.channels(channelId).pins(messageId).put({ reason });
}
unpinMessage(channelId: string, messageId: string, reason?: string) {
return this.session.api.channels(channelId).pins(messageId).delete({ reason });
}
groupDMAddRecipient(channelId: string, userId: string, body: RESTPutAPIChannelRecipientJSONBody) {
return this.session.api.channels(channelId).recipients(userId).put({ body });
}
groupDMRemoveRecipient(channelId: string, userId: string) {
return this.session.api.channels(channelId).recipients(userId).delete();
}
startThreadFromMessage(channelId: string, messageId: string, body: RESTPostAPIChannelMessagesThreadsJSONBody, reason?: string) {
return this.session.api.channels(channelId).messages(messageId).threads.post({ body, reason });
}
startThread(channelId: string, body: RESTPostAPIChannelThreadsJSONBody, reason?: string): Promise<RESTPostAPIChannelThreadsResult>;
startThread(channelId: string, body: RESTPostAPIGuildForumThreadsJSONBody, reason?: string) {
return this.session.api.channels(channelId).threads.post({ body, reason });
}
getListJoinedPrivateArchivedThreads(channelId: string, query?: RESTGetAPIChannelThreadsArchivedQuery) {
return this.session.api.channels(channelId).users('@me').threads.archived.private.get({ query });
}
getThreadMembers(channelId: string, query?: RESTGetAPIChannelThreadMembersQuery) {
return this.session.api.channels(channelId)['thread-members'].get({ query });
}
getThreadMember(channelId: string, userId: string, query?: RESTGetAPIChannelThreadMemberQuery) {
return this.session.api.channels(channelId)['thread-members'](userId).get({ query });
}
addThreadMember(channelId: string, userId: string) {
return this.session.api.channels(channelId)['thread-members'](userId).put({});
}
removeThreadMember(channelId: string, userId: string) {
return this.session.api.channels(channelId)['thread-members'](userId).delete();
}
leaveThread(channelId: string) {
return this.session.api.channels(channelId)['thread-members']('@me').delete();
}
joinThread(channelId: string) {
return this.session.api.channels(channelId)['thread-members']('@me').put({});
}
getVoiceRegions() {
return this.session.api.voice.region.get();
}
getStageInstance(channelId: string) {
return this.session.api['stage-instances'](channelId).get();
}
editStageInstance(channelId: string, body: RESTPatchAPIStageInstanceJSONBody, reason?: string) {
return this.session.api['stage-instances'](channelId).patch({ body, reason });
}
deleteStageInstance(channelId: string, reason?: string) {
return this.session.api['stage-instances'](channelId).delete({ reason });
}
}
export type RESTGetAPIChannelThreadsArchivedOptions = {
type: 'private' | 'public';
} & RESTGetAPIChannelThreadsArchivedQuery;

View File

@ -0,0 +1,348 @@
import type { Session } from '../session';
import type {
GuildMFALevel,
APIGuildChannel,
GuildChannelType,
RESTPostAPIGuildPruneJSONBody,
RESTPostAPIGuildsJSONBody,
RESTPatchAPIGuildJSONBody,
RESTPostAPIGuildChannelJSONBody,
RESTPatchAPIGuildChannelPositionsJSONBody,
RESTGetAPIGuildBansQuery,
RESTPutAPIGuildBanJSONBody,
RESTPostAPIGuildRoleJSONBody,
RESTPatchAPIGuildRolePositionsJSONBody,
RESTPatchAPIGuildRoleJSONBody,
RESTPatchAPIGuildWidgetSettingsJSONBody,
RESTPatchAPIGuildWelcomeScreenJSONBody,
RESTGetAPIGuildPruneCountQuery,
RESTGetAPIAuditLogQuery,
RESTPostAPIAutoModerationRuleJSONBody,
RESTPatchAPIAutoModerationRuleJSONBody,
RESTPostAPITemplateCreateGuildJSONBody,
RESTGetAPIGuildMembersQuery,
RESTGetAPIGuildMembersSearchQuery,
RESTPatchAPICurrentGuildMemberJSONBody,
RESTPutAPIGuildMemberJSONBody,
RESTPatchAPIGuildMemberJSONBody,
RESTGetAPIGuildWidgetImageQuery,
RESTPatchAPIGuildEmojiJSONBody,
RESTPostAPIGuildEmojiJSONBody,
RESTPatchAPIGuildVoiceStateUserJSONBody,
RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody,
RESTPatchAPIGuildStickerJSONBody,
RESTPostAPIGuildStickerFormDataBody,
RESTGetAPIGuildScheduledEventsQuery,
RESTPatchAPIGuildScheduledEventJSONBody,
RESTPostAPIGuildScheduledEventJSONBody,
RESTGetAPIGuildScheduledEventQuery,
RESTGetAPIGuildScheduledEventUsersQuery,
RESTPatchAPIGuildTemplateJSONBody,
RESTPostAPIGuildTemplatesJSONBody
} from '@biscuitland/common';
export class GuildManager {
readonly session!: Session;
constructor(session: Session) {
Object.defineProperty(this, 'session', {
value: session,
writable: false
});
}
get(guildId: string) {
return this.session.api.guilds(guildId).get();
}
create(options: RESTPostAPIGuildsJSONBody) {
return this.session.api.guilds.post({ body: options });
}
delete(guildId: string) {
return this.session.api.guilds(guildId).delete();
}
edit(guildId: string, options: RESTPatchAPIGuildJSONBody) {
return this.session.api.guilds(guildId).patch({ body: options });
}
getChannels(guildId: string) {
return this.session.api.guilds(guildId).channels.get();
}
createChannel<T extends APIGuildChannel<GuildChannelType>>(guildId: string, body: RESTPostAPIGuildChannelJSONBody) {
return this.session.api.guilds(guildId).channels.post({ body }) as Promise<T>;
}
editChannelPositions(guildId: string, body: RESTPatchAPIGuildChannelPositionsJSONBody): Promise<void> {
return this.session.api.guilds(guildId).channels.patch({ body });
}
getThreads(guildId: string) {
return this.session.api.guilds(guildId).threads.active.get();
}
getBans(guildId: string, query: RESTGetAPIGuildBansQuery = {}) {
return this.session.api.guilds(guildId).bans.get({
query
});
}
getBan(guildId: string, userId: string) {
return this.session.api.guilds(guildId).bans(userId).get();
}
createBan(guildId: string, userId: string, body: RESTPutAPIGuildBanJSONBody) {
return this.session.api.guilds(guildId).bans(userId).put({ body });
}
removeBan(guildId: string, userId: string) {
return this.session.api.guilds(guildId).bans(userId).delete();
}
getRoles(guildId: string) {
return this.session.api.guilds(guildId).roles.get();
}
createRole(guildId: string, options: RESTPostAPIGuildRoleJSONBody) {
return this.session.api.guilds(guildId).roles.post({ body: options });
}
editRolePositions(guildId: string, options: RESTPatchAPIGuildRolePositionsJSONBody) {
return this.session.api.guilds(guildId).roles.patch({ body: options });
}
editRole(guildId: string, roleId: string, options: RESTPatchAPIGuildRoleJSONBody) {
return this.session.api.guilds(guildId).roles(roleId).patch({ body: options });
}
deleteRole(guildId: string, roleId: string) {
return this.session.api.guilds(guildId).roles(roleId).delete();
}
editGuildMFALevel(guildId: string, level: GuildMFALevel) {
return this.session.api.guilds(guildId).mfa.post({ body: { level } });
}
getPruneCount(guildId: string, query: RESTGetAPIGuildPruneCountQuery) {
return this.session.api.guilds(guildId).prune.get({
query
});
}
beginGuildPrune(guildId: string, options: RESTPostAPIGuildPruneJSONBody) {
return this.session.api.guilds(guildId).prune.post({ body: options });
}
getVoiceRegions(guildId: string) {
return this.session.api.guilds(guildId).regions.get();
}
getInvites(guildId: string) {
return this.session.api.guilds(guildId).invites.get();
}
getIntegrations(guildId: string) {
return this.session.api.guilds(guildId).integrations.get();
}
deleteIntegration(guildId: string, integrationId: string) {
return this.session.api.guilds(guildId).integrations(integrationId).delete();
}
getWidget(guildId: string) {
return this.session.api.guilds(guildId).widget.get();
}
editWidget(guildId: string, options: RESTPatchAPIGuildWidgetSettingsJSONBody) {
return this.session.api.guilds(guildId).widget.patch({ body: options });
}
getVanityUrl(guildId: string) {
return this.session.api.guilds(guildId)['vanity-url'].get();
}
getWelcomeScreen(guildId: string) {
return this.session.api.guilds(guildId)['welcome-screen'].get();
}
editWelcomeScreen(guildId: string, options: RESTPatchAPIGuildWelcomeScreenJSONBody) {
return this.session.api.guilds(guildId)['welcome-screen'].patch({ body: options });
}
getAuditLog(guildId: string, query?: RESTGetAPIAuditLogQuery) {
return this.session.api.guilds(guildId)['audit-logs'].get({ query });
}
getAutoModerationRules(guildId: string) {
return this.session.api.guilds(guildId)['auto-moderation'].rules.get();
}
getAutoModerationRule(guildId: string, ruleId: string) {
return this.session.api.guilds(guildId)['auto-moderation'].rules(ruleId).get();
}
createAutoModerationRule(guildId: string, body: RESTPostAPIAutoModerationRuleJSONBody, reason?: string) {
return this.session.api.guilds(guildId)['auto-moderation'].rules.post({ body, reason });
}
editAutoModerationRule(guildId: string, body: RESTPatchAPIAutoModerationRuleJSONBody, reason?: string) {
return this.session.api.guilds(guildId)['auto-moderation'].rules.patch({ body, reason });
}
deleteAutoModerationRule(guildId: string, ruleId: string, reason?: string) {
return this.session.api.guilds(guildId)['auto-moderation'].rules(ruleId).delete({ reason });
}
getTemplate(code: string) {
return this.session.api.guilds.templates(code).get();
}
createTemplate(code: string, body: RESTPostAPITemplateCreateGuildJSONBody) {
return this.session.api.guilds.templates(code).post({ body });
}
getWebhooks(guildId: string) {
return this.session.api.guilds(guildId).webhooks.get();
}
getPreview(guildId: string) {
return this.session.api.guilds(guildId).preview.get();
}
getMembers(guildId: string, query?: RESTGetAPIGuildMembersQuery) {
return this.session.api.guilds(guildId).members.get({ query });
}
searchMembers(guildId: string, query?: RESTGetAPIGuildMembersSearchQuery) {
return this.session.api.guilds(guildId).members.search.get({ query });
}
editCurrentMember(guildId: string, body: RESTPatchAPICurrentGuildMemberJSONBody) {
return this.session.api.guilds(guildId).members['@me'].patch({ body });
}
getMember(guildId: string, memberId: string) {
return this.session.api.guilds(guildId).members(memberId).get();
}
addMember(guildId: string, memberId: string, body: RESTPutAPIGuildMemberJSONBody) {
return this.session.api.guilds(guildId).members(memberId).put({ body });
}
editMember(guildId: string, memberId: string, body: RESTPatchAPIGuildMemberJSONBody) {
return this.session.api.guilds(guildId).members(memberId).patch({ body });
}
removeMember(guildId: string, memberId: string) {
return this.session.api.guilds(guildId).members(memberId).delete();
}
addRoleMember(guildId: string, memberId: string, roleId: string) {
return this.session.api.guilds(guildId).members(memberId).roles(roleId).put({});
}
removeRoleMember(guildId: string, memberId: string, roleId: string) {
return this.session.api.guilds(guildId).members(memberId).roles(roleId).delete({});
}
getWidgetJson(guildId: string) {
return this.session.api.guilds(guildId)['widget.json'].get();
}
getWidgetPng(guildId: string, query?: RESTGetAPIGuildWidgetImageQuery) {
return this.session.api.guilds(guildId)['widget.png'].get({ query });
}
getEmojis(guildId: string) {
return this.session.api.guilds(guildId).emojis.get();
}
createEmoji(guildId: string, body: RESTPostAPIGuildEmojiJSONBody) {
return this.session.api.guilds(guildId).emojis.post({ body });
}
getEmoji(guildId: string, emojiId: string) {
return this.session.api.guilds(guildId).emojis(emojiId).get();
}
editEmoji(guildId: string, emojiId: string, body: RESTPatchAPIGuildEmojiJSONBody) {
return this.session.api.guilds(guildId).emojis(emojiId).patch({ body });
}
deleteEmoji(guildId: string, emojiId: string) {
return this.session.api.guilds(guildId).emojis(emojiId).delete();
}
editCurrentVoiceState(guildId: string, body: RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody) {
return this.session.api.guilds(guildId)['voice-states']['@me'].patch({ body });
}
editMemberVoiceState(guildId: string, memberId: string, body: RESTPatchAPIGuildVoiceStateUserJSONBody) {
return this.session.api.guilds(guildId)['voice-states'](memberId).patch({ body });
}
getStickers(guildId: string) {
return this.session.api.guilds(guildId).stickers.get();
}
createSticker(guildId: string, body: RESTPostAPIGuildStickerFormDataBody) {
return this.session.api.guilds(guildId).stickers.post({ body });
}
getSticker(guildId: string, stickerId: string) {
return this.session.api.guilds(guildId).stickers(stickerId).get();
}
editSticker(guildId: string, stickerId: string, body: RESTPatchAPIGuildStickerJSONBody) {
return this.session.api.guilds(guildId).stickers(stickerId).patch({ body });
}
deleteSticker(guildId: string, stickerId: string) {
return this.session.api.guilds(guildId).stickers(stickerId).delete();
}
getScheduledEvents(guildId: string, query?: RESTGetAPIGuildScheduledEventsQuery) {
return this.session.api.guilds(guildId)['scheduled-events'].get({ query });
}
createScheduledEvent(guildId: string, body: RESTPostAPIGuildScheduledEventJSONBody) {
return this.session.api.guilds(guildId)['scheduled-events'].post({ body });
}
getScheduledEvent(guildId: string, scheduledEvent: string, query?: RESTGetAPIGuildScheduledEventQuery) {
return this.session.api.guilds(guildId)['scheduled-events'](scheduledEvent).get({ query });
}
editScheduledEvent(guildId: string, scheduledEvent: string, body: RESTPatchAPIGuildScheduledEventJSONBody) {
return this.session.api.guilds(guildId)['scheduled-events'](scheduledEvent).patch({ body });
}
deleteScheduledEvent(guildId: string, scheduledEvent: string) {
return this.session.api.guilds(guildId)['scheduled-events'](scheduledEvent).delete();
}
getUsersScheduledEvent(guildId: string, scheduledEvent: string, query: RESTGetAPIGuildScheduledEventUsersQuery) {
return this.session.api.guilds(guildId)['scheduled-events'](scheduledEvent).users.get({ query });
}
getGuildTemplates(guildId: string) {
return this.session.api.guilds(guildId).templates.get();
}
createGuildTemplate(guildId: string, body: RESTPostAPIGuildTemplatesJSONBody) {
return this.session.api.guilds(guildId).templates.post({ body });
}
syncGuildTemplate(guildId: string, code: string) {
return this.session.api.guilds(guildId).templates(code).put({});
}
modifyGuildTemaplte(guildId: string, code: string, body: RESTPatchAPIGuildTemplateJSONBody) {
return this.session.api.guilds(guildId).templates(code).patch({ body });
}
deleteCodeTemplate(guildId: string, code: string) {
return this.session.api.guilds(guildId).templates(code).delete();
}
}

View File

@ -0,0 +1,57 @@
import type {
RESTPatchAPIWebhookWithTokenMessageJSONBody,
RESTPostAPIInteractionCallbackJSONBody,
RESTPostAPIInteractionFollowupJSONBody
} from '@biscuitland/common';
import type { Session } from '..';
import type { RawFile } from '@biscuitland/rest';
export class InteractionManager {
readonly session!: Session;
constructor(session: Session) {
Object.defineProperty(this, 'session', {
value: session,
writable: false
});
}
reply<T extends RESTPostAPIInteractionCallbackJSONBody = RESTPostAPIInteractionCallbackJSONBody>(
interactionId: string,
token: string,
body: T,
files?: RawFile[]
) {
return this.session.api.interactions(interactionId)(token).callback.post({
body,
files
});
}
getResponse(applicationId: string, token: string, messageId = '@original') {
return this.session.api.webhooks(applicationId)(token).messages(messageId).get();
}
editResponse(
applicationId: string,
token: string,
messageId: string,
body: RESTPatchAPIWebhookWithTokenMessageJSONBody,
files?: RawFile[]
) {
return this.session.api.webhooks(applicationId)(token).messages(messageId).patch({
body,
files
});
}
deleteResponse(applicationId: string, token: string, messageId = '@original') {
return this.session.api.webhooks(applicationId)(token).messages(messageId).delete();
}
followUp(applicationId: string, token: string, body: RESTPostAPIInteractionFollowupJSONBody, files?: RawFile[]) {
return this.session.api.webhooks(applicationId)(token).post({
body,
files
});
}
}

View File

@ -0,0 +1,19 @@
import type { Session } from '../session';
import { ChannelManager } from './ChannelManager';
import { GuildManager } from './GuildManager';
import { MemberManager } from './MemberManager';
import { UserManager } from './UserManager';
export class MainManager {
constructor(private readonly session: Session) {
this.users = new UserManager(this.session);
this.guilds = new GuildManager(this.session);
this.members = new MemberManager(this.session);
this.channels = new ChannelManager(this.session);
}
users: UserManager;
guilds: GuildManager;
members: MemberManager;
channels: ChannelManager;
}

View File

@ -0,0 +1,19 @@
import type { APIGuildMember, MakeRequired } from '@biscuitland/common';
import type { Session, ImageOptions } from '../index';
import { formatImageURL } from '../index';
export class MemberManager {
constructor(private readonly session: Session) {}
dynamicAvatarURL({ avatar, guild_id, user }: DynamicMember, { size, format }: ImageOptions): string {
if (avatar?.length) {
return formatImageURL(this.session.cdn.guilds(guild_id).users(user.id).avatars(avatar).get(), size, format);
}
return this.session.managers.users.avatarURL(user, { size, format });
}
}
export type DynamicMember = MakeRequired<APIGuildMember, 'user'> & {
guild_id: string;
};

View File

@ -0,0 +1,64 @@
import type {
APIUser,
RESTGetAPICurrentUserGuildsQuery,
RESTPatchAPICurrentUserJSONBody,
RESTPutAPICurrentUserApplicationRoleConnectionJSONBody
} from '@biscuitland/common';
import type { Session, ImageOptions } from '../index';
import { formatImageURL } from '../index';
export class UserManager {
readonly session!: Session;
constructor(session: Session) {
Object.defineProperty(this, 'session', {
value: session,
writable: false
});
}
get(userId = '@me') {
return this.session.api.users(userId).get();
}
avatarURL(user: APIUser, { size, format }: ImageOptions) {
if (user.avatar?.length) {
return formatImageURL(this.session.cdn.avatars(user.id).get(user.avatar), size, format);
}
return formatImageURL(this.session.cdn.embed.avatars.get(Number(user.discriminator) % 5));
}
createDM(userId: string) {
return this.session.api.users('@me').channels.post({ body: { recipient_id: userId } });
}
editCurrent(body: RESTPatchAPICurrentUserJSONBody) {
return this.session.api.users('@me').patch({
body
});
}
getGuilds(query?: RESTGetAPICurrentUserGuildsQuery) {
return this.session.api.users('@me').guilds.get({ query });
}
getGuildMember(guildId: string) {
return this.session.api.users('@me').guilds(guildId).member.get();
}
leaveGuild(guildId: string) {
return this.session.api.users('@me').guilds(guildId).delete();
}
getConnections() {
return this.session.api.users('@me').connections.get();
}
getRoleConnections(applicationId: string) {
return this.session.api.users('@me').applications(applicationId)['role-connection'].get();
}
updateRoleConnection(applicationId: string, body: RESTPutAPICurrentUserApplicationRoleConnectionJSONBody) {
return this.session.api.users('@me').applications(applicationId)['role-connection'].put({ body });
}
}

View File

@ -0,0 +1,124 @@
import {
Identify,
RESTPatchAPIWebhookJSONBody,
RESTPatchAPIWebhookResult,
RESTPatchAPIWebhookWithTokenJSONBody,
RESTPatchAPIWebhookWithTokenMessageJSONBody,
RESTPatchAPIWebhookWithTokenResult,
RESTPostAPIChannelWebhookJSONBody,
RESTPostAPIWebhookWithTokenGitHubQuery,
RESTPostAPIWebhookWithTokenGitHubWaitResult,
RESTPostAPIWebhookWithTokenJSONBody,
RESTPostAPIWebhookWithTokenQuery,
RESTPostAPIWebhookWithTokenSlackQuery,
RESTPostAPIWebhookWithTokenSlackWaitResult,
RESTPostAPIWebhookWithTokenWaitResult
} from '@biscuitland/common';
import type { Session } from '..';
export class WebhookManager {
readonly session!: Session;
constructor(session: Session) {
Object.defineProperty(this, 'session', {
value: session,
writable: false
});
}
create(channelId: string, body: RESTPostAPIChannelWebhookJSONBody) {
return this.session.api.channels(channelId).webhooks.post({ body });
}
getChannelWebhooks(channelId: string) {
return this.session.api.channels(channelId).webhooks.get();
}
getGuildWebhooks(guildId: string) {
return this.session.api.guilds(guildId).webhooks.get();
}
get(webhookdId: string, token?: string) {
if (!token?.length) return this.session.api.webhooks(webhookdId).get();
return this.session.api.webhooks(webhookdId)(token).get();
}
edit(webhookId: string, body: RESTPatchAPIWebhookJSONBody): Promise<RESTPatchAPIWebhookResult>;
edit(webhookId: string, body: RESTPatchAPIWebhookWithTokenJSONBody, token: string): Promise<RESTPatchAPIWebhookWithTokenResult>;
edit(webhookId: string, body: RESTPatchAPIWebhookJSONBody, token?: string) {
if (!token?.length) {
return this.session.api.webhooks(webhookId).patch({ body });
}
return this.session.api.webhooks(webhookId)(token).patch({ body });
}
delete(webhookdId: string, token?: string) {
if (!token?.length) return this.session.api.webhooks(webhookdId).delete();
return this.session.api.webhooks(webhookdId)(token).delete();
}
execute(
webhookId: string,
token: string,
body: RESTPostAPIWebhookWithTokenJSONBody,
query: RESTPostAPIWebhookWithTokenWaitQuery
): Promise<RESTPostAPIWebhookWithTokenWaitResult>;
execute(webhookId: string, token: string, body: RESTPostAPIWebhookWithTokenJSONBody, query?: RESTPostAPIWebhookWithTokenQuery) {
return this.session.api.webhooks(webhookId)(token).post({
body,
query
});
}
executeGithub(
webhookId: string,
token: string,
body: RESTPostAPIWebhookWithTokenJSONBody,
query: Identify<RESTPostAPIWebhookWithTokenGitHubQuery & { wait: true }>
): Promise<RESTPostAPIWebhookWithTokenGitHubWaitResult>;
executeGithub(
webhookId: string,
token: string,
body: RESTPostAPIWebhookWithTokenJSONBody,
query?: RESTPostAPIWebhookWithTokenGitHubQuery
) {
return this.session.api.webhooks(webhookId)(token).github.post({
body,
query
});
}
executeSlack(
webhookId: string,
token: string,
body: RESTPostAPIWebhookWithTokenJSONBody,
query: Identify<RESTPostAPIWebhookWithTokenSlackQuery & { wait: true }>
): Promise<RESTPostAPIWebhookWithTokenSlackWaitResult>;
executeSlack(webhookId: string, token: string, body: RESTPostAPIWebhookWithTokenJSONBody, query?: RESTPostAPIWebhookWithTokenSlackQuery) {
return this.session.api.webhooks(webhookId)(token).slack.post({
body,
query
});
}
getMessage(webhookId: string, token: string, messageId: string, query?: { thread_id?: string }) {
return this.session.api.webhooks(webhookId)(token).messages(messageId).get({
query
});
}
editMessage(
webhookId: string,
token: string,
messageId: string,
body: RESTPatchAPIWebhookWithTokenMessageJSONBody,
query?: { thread_id?: string }
) {
return this.session.api.webhooks(webhookId)(token).messages(messageId).patch({ query, body });
}
deleteMessage(webhookId: string, token: string, messageId: string, query?: { thread_id?: string }) {
return this.session.api.webhooks(webhookId)(token).messages(messageId).delete({ query });
}
}
export type RESTPostAPIWebhookWithTokenWaitQuery = Identify<RESTPostAPIWebhookWithTokenQuery & { wait: true }>;

View File

@ -0,0 +1,110 @@
import type { BiscuitRESTOptions, CDNRoutes, Routes } from '@biscuitland/rest';
import { CDN, BiscuitREST, Router } from '@biscuitland/rest';
import { When } from '@biscuitland/common';
import { EventEmitter2 } from 'eventemitter2';
import { MainManager, getBotIdFromToken } from '.';
import { GatewayManager, CreateGatewayManagerOptions, GatewayEvents } from '@biscuitland/ws';
import { GatewayIntentBits } from '@biscuitland/common';
import { actionHandler, Handler } from './events/handler';
export class Session<On extends boolean = boolean> extends EventEmitter2 {
constructor(public options: BiscuitOptions) {
super();
this.rest = this.createRest(this.options.rest);
this.api = new Router(this.rest).createProxy();
this.cdn = CDN.createProxy();
this.managers = new MainManager(this);
}
rest: BiscuitREST;
api: Routes;
cdn: CDNRoutes;
managers: MainManager;
gateway!: When<On, GatewayManager>;
private _applicationId?: string;
private _botId?: string;
override on<K extends keyof GatewayEvents>(event: `${K}`, func: Handler[K]): this;
override on<K extends string>(event: `${K}`, func: (...args: unknown[]) => unknown): this {
const ev = super.on(event, func);
// @ts-expect-error Eventemitter can sometimes return a listener
return ev.emitter ? ev.emitter : ev;
}
override off<K extends keyof GatewayEvents>(event: `${K}`, func: Handler[K]): this;
override off<K extends keyof GatewayEvents>(event: `${K}`, func: (...args: unknown[]) => unknown): this {
return super.off(event, func);
}
override once<K extends keyof GatewayEvents>(event: `${K}`, func: Handler[K]): this;
override once<K extends string>(event: `${K}`, func: (...args: unknown[]) => unknown): this {
const ev = super.on(event, func);
// @ts-expect-error Eventemitter can sometimes return a listener
return ev.emitter ? ev.emitter : ev;
}
override emit<K extends keyof GatewayEvents>(event: `${K}`, ...params: Parameters<Handler[K]>): boolean;
override emit<K extends string>(event: `${K}`, ...params: unknown[]): boolean {
return super.emit(event, ...params);
}
set botId(id: string) {
this._botId = id;
}
set applicationId(id: string) {
this._applicationId = id;
}
get botId() {
return this._botId ?? getBotIdFromToken(this.options.token);
}
get applicationId() {
return this._applicationId ?? this.botId;
}
private createRest(rest: any) {
if (!rest) {
return new BiscuitREST({
...this.options.defaultRestOptions,
token: this.options.token
});
}
if (rest instanceof BiscuitREST || rest.cRest) {
return rest;
}
throw new Error('[CORE] REST not found');
}
async start() {
// alias fixed `this` on handlePayload
const ctx = this as Session<true>;
ctx.gateway = new GatewayManager({
token: this.options.token,
intents: this.options.intents ?? 0,
connection: this.options.defaultGatewayOptions?.connection ?? (await this.rest.get('/gateway/bot')),
async handlePayload(shard, data) {
const { t, d } = data;
if (!(t && d)) return;
actionHandler([ctx, { t, d }, shard]);
},
...this.options.defaultGatewayOptions
});
await ctx.gateway.spawnShards();
}
}
export type HandlePayload = Pick<CreateGatewayManagerOptions, 'handlePayload'>['handlePayload'];
export interface BiscuitOptions {
token: string;
intents: number | GatewayIntentBits;
rest?: BiscuitREST;
defaultRestOptions?: Partial<BiscuitRESTOptions>;
defaultGatewayOptions?: Omit<CreateGatewayManagerOptions, 'token' | 'intents'>;
}

View File

@ -1,13 +0,0 @@
/** snowflake type */
export type Snowflake = string;
/** Discord epoch */
export const DiscordEpoch = 14200704e5;
/** utilities for Snowflakes */
// eslint-disable-next-line @typescript-eslint/no-redeclare
export const Snowflake = {
snowflakeToTimestamp(id: Snowflake): number {
return (Number(id) >> 22) + DiscordEpoch;
},
};

View File

@ -1,34 +0,0 @@
// STRUCTURES
export * from './structures/application';
export * from './structures/attachment';
export * from './structures/automod';
export * from './structures/base';
export * from './structures/embed';
export * from './structures/emojis';
export * from './structures/scheduled-events';
export * from './structures/integration';
export * from './structures/invite';
export * from './structures/members';
export * from './structures/message';
export * from './structures/message-reaction';
export * from './structures/special/interaction-options';
export * from './structures/special/permissions';
export * from './structures/presence';
export * from './structures/role';
export * from './structures/stage-instance';
export * from './structures/sticker';
export * from './structures/user';
export * from './structures/webhook';
export * from './structures/welcome';
// INTERACTIONS
export * from './structures/interactions';
// CHANNELS
export * from './structures/channels';
// COMPONENTS
export * from './structures/components';
// GUILDS
export * from './structures/guilds';

View File

@ -1,112 +0,0 @@
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type { Session } from '../biscuit';
import type {
DiscordApplication,
DiscordInstallParams,
DiscordTeam,
DiscordUser,
TeamMembershipStates,
} from '@biscuitland/api-types';
import { User } from './user';
/**
* @internal
*/
export type SummaryDeprecated = '';
/**
* Discord team that holds members
*/
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;
}

View File

@ -1,36 +0,0 @@
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type { Session } from '../biscuit';
import type { DiscordAttachment } from '@biscuitland/api-types';
/**
* 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;
}

View File

@ -1,215 +0,0 @@
import type { Model } from './base';
import type { Session } from '../biscuit';
import type { Snowflake } from '../snowflakes';
import type {
AutoModerationActionType,
AutoModerationEventTypes,
AutoModerationTriggerTypes,
DiscordAutoModerationRule,
DiscordAutoModerationRuleTriggerMetadataPresets,
DiscordAutoModerationActionExecution } from '@biscuitland/api-types';
import {
AUTO_MODERATION_RULES
} from '@biscuitland/api-types';
export interface AutoModerationRuleTriggerMetadata {
keywordFilter?: string[];
presets?: DiscordAutoModerationRuleTriggerMetadataPresets[];
allowList?: string[];
}
export interface ActionMetadata {
channelId: Snowflake;
durationSeconds: number;
}
export interface AutoModerationAction {
type: AutoModerationActionType;
metadata: ActionMetadata;
}
/** @link https://discord.com/developers/docs/resources/auto-moderation#create-auto-moderation-rule-json-params */
export interface CreateAutoModerationRule {
name: string;
eventType: 1;
triggerType: AutoModerationTriggerTypes;
triggerMetadata?: AutoModerationRuleTriggerMetadata;
actions: AutoModerationAction[];
enabled?: boolean;
exemptRoles?: Snowflake[];
exemptChannels?: Snowflake[];
}
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,
allowList: data.trigger_metadata.allow_list
};
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[];
async getRules(
ruleId?: Snowflake
): Promise<AutoModerationRule | AutoModerationRule[]> {
const request = await this.session.rest.get<
DiscordAutoModerationRule | DiscordAutoModerationRule[]
>(AUTO_MODERATION_RULES(this.guildId, ruleId));
if (Array.isArray(request)) {
return request.map(
amr => new AutoModerationRule(this.session, amr)
);
}
return new AutoModerationRule(this.session, request);
}
async createRule(options: CreateAutoModerationRule) {
const request = await this.session.rest.post<DiscordAutoModerationRule>(
AUTO_MODERATION_RULES(this.guildId),
{
name: options.name,
event_type: options.eventType,
trigger_type: options.triggerType,
trigger_metadata: options.triggerMetadata,
actions: options.actions
? options.actions.map(x =>
Object.assign(
{},
{
type: x.type,
metadata: {
channel_id: x.metadata.channelId,
duration_seconds:
x.metadata.durationSeconds
}
}
)
)
: undefined,
enabled: !!options.enabled,
exempt_roles: options.exemptRoles,
exempt_channels: options.exemptChannels
}
);
return new AutoModerationRule(this.session, request);
}
async editRule(
ruleId = this.id,
options: Partial<CreateAutoModerationRule>
) {
const request = await this.session.rest.patch<
DiscordAutoModerationRule
>(AUTO_MODERATION_RULES(this.guildId, ruleId), {
name: options.name,
event_type: options.eventType,
trigger_type: options.triggerType,
trigger_metadata: options.triggerMetadata,
actions: options.actions
? options.actions.map(x =>
Object.assign(
{},
{
type: x.type,
metadata: {
channel_id: x.metadata.channelId,
duration_seconds: x.metadata.durationSeconds
}
}
)
)
: undefined,
enabled: !!options.enabled,
exempt_roles: options.exemptRoles,
exempt_channels: options.exemptChannels
});
return new AutoModerationRule(this.session, request);
}
async deleteRule(ruleId = this.id): Promise<void> {
await this.session.rest.delete(
AUTO_MODERATION_RULES(this.guildId, ruleId)
);
return;
}
}
export class AutoModerationExecution {
constructor(session: Session, data: DiscordAutoModerationActionExecution) {
this.session = session;
this.guildId = data.guild_id;
this.action = {
type: data.action.type,
metadata: {
channelId: data.action.metadata.channel_id as string,
durationSeconds: data.action.metadata.duration_seconds as number
}
};
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;
}

View File

@ -1,12 +0,0 @@
import type { Snowflake } from '../snowflakes';
import type { Session } from '../biscuit';
/**
* Represents a Discord data model
*/
export interface Model {
/** id of the model */
id: Snowflake;
/** reference to the client that instantiated the model */
session: Session;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,280 +0,0 @@
import type { Session } from '../biscuit';
import type {
DiscordComponent,
DiscordInputTextComponent,
TextStyles,
} from '@biscuitland/api-types';
import { Emoji } from './emojis';
import { ButtonStyles, MessageComponentTypes } from '@biscuitland/api-types';
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: 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 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 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 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!;
if ('options' in data) {
this.options = data.options?.map(option => {
return {
label: option.label,
description: option.description,
emoji: option.emoji ? new Emoji(session, option.emoji) : undefined,
value: option.value,
} as SelectMenuOption;
});
}
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 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 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:
case MessageComponentTypes.RoleSelect:
case MessageComponentTypes.UserSelect:
case MessageComponentTypes.MentionableSelect:
case MessageComponentTypes.ChannelSelect:
return new SelectMenu(session, component);
case MessageComponentTypes.InputText:
return new TextInput(
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 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:
case MessageComponentTypes.RoleSelect:
case MessageComponentTypes.UserSelect:
case MessageComponentTypes.MentionableSelect:
case MessageComponentTypes.ChannelSelect:
return new SelectMenu(session, component);
case MessageComponentTypes.InputText:
return new TextInput(
session,
component as DiscordInputTextComponent
);
}
}
}

View File

@ -1,155 +0,0 @@
import type { DiscordEmbed, EmbedTypes } from '@biscuitland/api-types';
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?: {
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 NewEmbed(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 const embed = NewEmbed;
export function NewEmbedR(data: DiscordEmbed): Embed {
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,
iconURL: data.author.icon_url,
proxyIconURL: data.author.proxy_icon_url,
},
footer: data.footer && {
text: data.footer.text,
iconURL: data.footer.icon_url,
proxyIconURL: data.footer.proxy_icon_url,
},
fields: data.fields?.map(f => {
return {
name: f.name,
value: f.value,
inline: f.inline,
};
}),
thumbnail: data.thumbnail && {
url: data.thumbnail.url,
proxyURL: data.thumbnail.proxy_url,
width: data.thumbnail.width,
height: data.thumbnail.height,
},
video: {
url: data.video?.url,
proxyURL: data.video?.proxy_url,
width: data.video?.width,
height: data.video?.height,
},
image: data.image && {
url: data.image.url,
proxyURL: data.image.proxy_url,
width: data.image.width,
height: data.image.height,
},
provider: {
url: data.provider?.url,
name: data.provider?.name,
},
};
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export const embed_ = NewEmbedR;

View File

@ -1,85 +0,0 @@
import type { Session } from '../biscuit';
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type { ModifyGuildEmoji } from './guilds';
import { Guild } from './guilds';
import { User } from './user';
import type { DiscordEmoji } from '@biscuitland/api-types';
import { EMOJI_URL, GUILD_EMOJIS } from '@biscuitland/api-types';
export class Emoji implements Partial<Model> {
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 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;
}
async fetchAuthor(): Promise<User | null> {
const emoji = await this.session.rest.get<DiscordEmoji>(GUILD_EMOJIS(this.guildId, this.id));
if (emoji.user) { return new User(this.session, emoji.user); }
return null;
}
setName(name: string): Promise<GuildEmoji> {
return this.edit({ name });
}
get url(): string {
return EMOJI_URL(this.id, this.animated);
}
toString(): string {
return `<${this.animated ? 'a' : ''}:${this.name}:${this.id}>`;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +0,0 @@
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type { Session } from '../biscuit';
import type {
DiscordIntegration,
IntegrationExpireBehaviors,
} from '@biscuitland/api-types';
import { User } from './user';
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;

View File

@ -1,669 +0,0 @@
import type { Model } from './base';
import type { Session } from '../biscuit';
import type {
ApplicationCommandTypes,
DiscordInteraction,
DiscordMessage,
DiscordMessageComponents,
DiscordMemberWithUser,
DiscordMessageInteraction,
Locales
} from '@biscuitland/api-types';
import type { CreateMessage } from './message';
import type { MessageFlags } from '../utils/util';
import type { EditWebhookMessage } from './webhook';
import {
InteractionResponseTypes,
InteractionTypes,
MessageComponentTypes,
INTERACTION_ID_TOKEN,
WEBHOOK_MESSAGE,
WEBHOOK_MESSAGE_ORIGINAL
} from '@biscuitland/api-types';
import { Role } from './role';
import { Attachment } from './attachment';
import { Snowflake } from '../snowflakes';
import { User } from './user';
import { Member } from './members';
import { Message } from './message';
import { Permissions } from './special/permissions';
import { Webhook } from './webhook';
import { InteractionOptions } from './special/interaction-options';
import { NewEmbed } from './embed';
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;
this.locale = data.locale as Locales;
this.guildLocale = data.guild_locale as Locales;
const perms = data.app_permissions;
if (perms) {
this.appPermissions = new Permissions(BigInt(perms));
}
if (!data.guild_id) {
this.user = new User(session, data.user!);
} else {
this.member = new Member(session, data.member!, data.guild_id);
// dangerous black magic be careful!
Object.defineProperty(this, 'user', {
get() {
return this.member.user;
}
});
}
}
readonly session: Session;
readonly id: Snowflake;
readonly token: string;
type: InteractionTypes;
guildId?: Snowflake;
channelId?: Snowflake;
applicationId?: Snowflake;
user!: User;
member?: Member;
appPermissions?: Permissions;
// must be implemented
locale: Locales;
guildLocale: Locales;
// readonly property according to docs
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.patch<
DiscordMessage | undefined
>(
options.messageId
? WEBHOOK_MESSAGE(this.session.applicationId, this.token, options.messageId)
: WEBHOOK_MESSAGE_ORIGINAL(this.session.applicationId, this.token),
{
content: options.content,
embeds: options.embeds?.map(NewEmbed),
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,
session: this.session,
token: this.token,
},
messageId,
options
);
return message;
}
async deleteEphemeral(messageId?: Snowflake): Promise<void> {
await Webhook.prototype.deleteFollowUp.call(
{
id: this.session.applicationId,
session: this.session,
token: this.token,
},
messageId
);
}
async deleteFollowUp(
messageId: Snowflake,
threadId?: Snowflake
): Promise<void> {
await Webhook.prototype.deleteMessage.call(
{
id: this.session.applicationId,
session: this.session,
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,
session: this.session,
token: this.token,
},
messageId,
threadId
);
return message;
}
// end webhook methods
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?.map(NewEmbed),
title: options?.title,
components: options?.components,
};
if (!this.responded) {
await this.session.rest.post<undefined>(
INTERACTION_ID_TOKEN(this.id, this.token),
{
file: options?.files,
type,
data,
}
);
this.responded = true;
return;
}
return this.sendFollowUp(data);
}
/**
* internal usage only, same as respond but doesn't tries to follow up
* */
async respond_(resp: InteractionResponse): Promise<void> {
if (!this.responded) return this.respond(resp) as Promise<undefined>;
}
// 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,
});
}
/**
* taken from Detritus
* try respond, try edit, try follow up
* */
async editOrReply(resp: InteractionResponseWithData & EditWebhookMessage) {
if (this.responded) {
return this.editReply(resp);
}
let type: InteractionResponseTypes = InteractionResponseTypes.ChannelMessageWithSource;
switch (this.type) {
case InteractionTypes.ApplicationCommand:
type = InteractionResponseTypes.ChannelMessageWithSource;
break;
case InteractionTypes.MessageComponent:
type = InteractionResponseTypes.UpdateMessage;
break;
}
const result = await this.respond({ type, data: resp });
return result;
}
// end custom methods
}
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;
this.options = new InteractionOptions(
data.data!.options ?? []
);
}
override type: InteractionTypes.ApplicationCommandAutocomplete;
commandId: Snowflake;
commandName: string;
commandType: ApplicationCommandTypes;
commandGuildId?: Snowflake;
options: InteractionOptions;
async respondWithChoices(
choices: ApplicationCommandOptionChoice[]
): Promise<void> {
await this.session.rest.post<undefined>(
INTERACTION_ID_TOKEN(this.id, this.token),
{
data: { choices },
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
}
);
}
}
export interface CommandInteractionDataResolved {
users: Map<Snowflake, User>;
members: Map<Snowflake, Member>;
roles: Map<Snowflake, Role>;
messages: Map<Snowflake, Message>;
attachments: Map<Snowflake, Attachment>;
}
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 InteractionOptions(
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: InteractionOptions;
}
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 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;
override locale = undefined as never;
override guildLocale = undefined as never;
async pong(): Promise<void> {
await this.session.rest.post<undefined>(
INTERACTION_ID_TOKEN(this.id, this.token),
{
type: InteractionResponseTypes.Pong,
}
);
}
}
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;
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,
});
}
}
/**
* @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;
}
}

View File

@ -1,206 +0,0 @@
import type { Session } from '../biscuit';
import type { Snowflake } from '../snowflakes';
import type {
DiscordApplication,
DiscordChannel,
DiscordInvite,
DiscordInviteCreate,
DiscordMemberWithUser,
DiscordScheduledEventEntityMetadata,
ScheduledEventEntityType,
ScheduledEventPrivacyLevel,
ScheduledEventStatus,
TargetTypes,
} from '@biscuitland/api-types';
import { GuildChannel } from './channels';
import { Member } from './members';
import { Guild, InviteGuild } from './guilds';
import { User } from './user';
import { Application } from './application';
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
? Date.parse(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?.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?.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;
}
}

View File

@ -1,239 +0,0 @@
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type { Session } from '../biscuit';
import type {
DiscordMemberWithUser,
DiscordThreadMember,
} from '@biscuitland/api-types';
import type { CreateGuildBan, ModifyGuildMember } from './guilds';
import type { AvatarOptions } from './user';
import { User } from './user';
import { Guild } from './guilds';
import { Util } from '../utils/util';
import { USER_AVATAR, THREAD_USER } from '@biscuitland/api-types';
import { Permissions } from './special/permissions';
/**
* Represents a guild member
* @link https://discord.com/developers/docs/resources/guild#guild-member-object
*/
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
? data.avatar
: undefined;
this.nickname = data.nick ? data.nick : undefined;
this.premiumSince = data.premium_since
? Date.parse(data.premium_since)
: undefined;
this.channelPermissions = data.permissions ? new Permissions(BigInt(data.permissions)) : undefined;
this.joinedTimestamp = Date.parse(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
? Date.parse(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?: string;
/** this user's guild nickname */
nickname?: string;
/** when the user started boosting the guild */
premiumSince?: number;
/** total permissions of the member in the channel, including overwrites, returned when in the interaction object */
channelPermissions?: Permissions;
/** 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;
}
/** calls {@link Member#edit} which calls {@link Guild#editMember} under the hood */
async timeout(time: number | null) {
await this.edit({ communicationDisabledUntil: time });
}
/** 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
);
}
async fetch(): Promise<Member> {
const member = await Guild.prototype.fetchMember.call({ session: this.session, id: this.guildId }, this.id);
return member;
}
/** gets the members's guild avatar if the user has one, gets the user's avatar instead */
avatarURL(options: AvatarOptions): string {
if (!this.avatarHash) {
return this.user.avatarURL(options);
}
return Util.formatImageURL(USER_AVATAR(
this.user.id,
this.avatarHash
), options.size ?? 128, options.format);
}
/**
* Sets a new nickname for this member. Same as Member.edit({ nick: ... })
* @param nick - The new nickname for the member.
*/
async setNickname(nick: string): Promise<Member> {
return this.edit({ nick });
}
toString(): string {
return `<@!${this.user.id}>`;
}
}
/**
* 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.delete<undefined>(
THREAD_USER(this.id, memberId ?? this.session.botId),
{}
);
}
async fetchMember(memberId?: Snowflake): Promise<ThreadMember> {
const member = await this.session.rest.get<DiscordThreadMember>(
THREAD_USER(this.id, memberId ?? this.session.botId)
);
return new ThreadMember(this.session, member);
}
}

View File

@ -1,97 +0,0 @@
import type { Session } from '../biscuit';
import type {
DiscordMemberWithUser,
DiscordMessageReactionAdd,
DiscordReaction,
} from '@biscuitland/api-types';
import { Emoji } from './emojis';
import { Member } from './members';
/**
* 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;

View File

@ -1,644 +0,0 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import type { Model } from './base';
import type { Session } from '../biscuit';
import type {
AllowedMentionsTypes,
DiscordMessage,
DiscordMessageComponents,
DiscordUser,
FileContent,
MessageActivityTypes,
MessageTypes,
GetReactions,
} from '@biscuitland/api-types';
import type { Channel } from './channels';
import type { Component } from './components';
import type { MessageInteraction } from './interactions';
import type { StickerItem } from './sticker';
import type { Embed } from './embed';
import { NewEmbed, NewEmbedR } from './embed';
import { MessageFlags } from '../utils/util';
import { Snowflake } from '../snowflakes';
import { ChannelFactory, ThreadChannel } from './channels';
import { User } from './user';
import { Member } from './members';
import { Attachment } from './attachment';
import { ComponentFactory } from './components';
import { MessageReaction } from './message-reaction';
import { Application, NewTeam } from './application';
import { InteractionFactory } from './interactions';
import {
CHANNEL_PIN,
CHANNEL_MESSAGE,
CHANNEL_MESSAGES,
CHANNEL_MESSAGE_REACTION_ME,
CHANNEL_MESSAGE_REACTION_USER,
CHANNEL_MESSAGE_REACTION,
CHANNEL_MESSAGE_REACTIONS,
CHANNEL_MESSAGE_CROSSPOST,
} from '@biscuitland/api-types';
export interface GuildMessage extends 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://discord.com/developers/docs/resources/channel#message-reference-object-message-reference-structure
* 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?: Embed[];
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;
attachments?: Attachment[];
}
/**
* 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?: string;
}
/**
* @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.map(NewEmbedR);
if (data.position) { this.position = data.position; }
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 ? 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: Embed[];
/** 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;
/** Represents the approximate position of the message in a thread */
position?: number;
/** 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}`;
}
/**
* Whether the author is bot.
* same as Message.author.bot
*/
get isBot(): boolean {
return this.author.bot;
}
/**
* Pins this message
*/
async pin(): Promise<void> {
await this.session.rest.put<undefined>(
CHANNEL_PIN(this.channelId, this.id),
{}
);
}
/**
* Unpins this message
*/
async unpin(): Promise<void> {
await this.session.rest.delete<undefined>(
CHANNEL_PIN(this.channelId, this.id),
{}
);
}
/** Edits the current message */
async edit(options: EditMessage): Promise<Message> {
const message = await this.session.rest.patch<DiscordMessage>(
CHANNEL_MESSAGE(this.channelId, this.id),
{
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?.map(NewEmbed),
components: options.components,
files: options.files,
attachments: options.attachments
}
);
return new Message(this.session, 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(): Promise<Message> {
await this.session.rest.delete<void>(
CHANNEL_MESSAGE(this.channelId, this.id)
);
return this;
}
/** Replies directly in the channel where the message was sent */
async reply(options: CreateMessage | string | Embed[]): Promise<Message> {
// Options is plain content
if (typeof options === 'string') {
const message = await this.session.rest.post<DiscordMessage>(
CHANNEL_MESSAGES(this.channelId),
{ content: options }
);
return new Message(this.session, message);
}
// Opptions are multiple embeds
if (Array.isArray(options)) {
const message = await this.session.rest.post<DiscordMessage>(
CHANNEL_MESSAGES(this.channelId),
{ embeds: options.map(NewEmbed) }
);
return new Message(this.session, message);
}
// Options is of type CreateMessage
const message = await this.session.rest.post<DiscordMessage>(
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?.map(NewEmbed),
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.put<undefined>(
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.delete<undefined>(
options?.userId
? CHANNEL_MESSAGE_REACTION_USER(
this.channelId,
this.id,
r,
options.userId
)
: 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.get<DiscordUser[]>(
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.delete<undefined>(
CHANNEL_MESSAGE_REACTION(this.channelId, this.id, r),
{}
);
}
/** nukes every reaction on the message */
async nukeReactions(): Promise<void> {
await this.session.rest.delete<undefined>(
CHANNEL_MESSAGE_REACTIONS(this.channelId, this.id),
{}
);
}
/** publishes/crossposts a message to all followers */
async crosspost(): Promise<Message> {
const message = await this.session.rest.post<DiscordMessage>(
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.get<DiscordMessage>(
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;
}
}

View File

@ -1,93 +0,0 @@
import type {
ActivityTypes,
DiscordActivityButton,
DiscordActivitySecrets,
DiscordClientStatus,
DiscordPresenceUpdate,
} from '@biscuitland/api-types';
import type { Session } from '../biscuit';
import type { Snowflake } from '../snowflakes';
import type { ComponentEmoji } from '../utils/util';
import { User } from './user';
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({
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;
}

View File

@ -1,95 +0,0 @@
import type { Model } from './base';
import type { Session } from '../biscuit';
import type { DiscordRole } from '@biscuitland/api-types';
import type { ModifyGuildRole } from './guilds';
import { Snowflake } from '../snowflakes';
import { Guild } from './guilds';
import { Util } from '../utils/util';
import { Permissions } from './special/permissions';
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}>`;
}
}
}

View File

@ -1,53 +0,0 @@
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type { Session } from '../biscuit';
import type {
DiscordScheduledEvent,
DiscordScheduledEventEntityMetadata,
ScheduledEventEntityType,
ScheduledEventStatus,
} from '@biscuitland/api-types';
import { PrivacyLevels } from './stage-instance';
import { User } from './user';
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;
}

View File

@ -1,267 +0,0 @@
import type {
DiscordInteractionDataOption,
DiscordInteractionDataResolved,
Snowflake,
} from '@biscuitland/api-types';
import { ApplicationCommandOptionTypes } from '@biscuitland/api-types';
/**
* Utility class to get the resolved options for a command
* @example const option = ctx.options.getStringOption("name");
*/
export class InteractionOptions {
private _subcommand?: string;
private _group?: string;
hoistedOptions: DiscordInteractionDataOption[];
resolved?: DiscordInteractionDataResolved;
constructor(
options?: DiscordInteractionDataOption[],
resolved?: DiscordInteractionDataResolved
) {
this.hoistedOptions = options ?? [];
// 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 ?? [];
}
if (
this.hoistedOptions[0]?.type ===
ApplicationCommandOptionTypes.SubCommand
) {
this._subcommand = this.hoistedOptions[0].name;
this.hoistedOptions = this.hoistedOptions[0].options ?? [];
}
this.resolved = resolved;
}
private getTypedOption(
name: string | number,
type: ApplicationCommandOptionTypes,
properties: (keyof DiscordInteractionDataOption)[],
required: boolean
): DiscordInteractionDataOption | void {
const option: DiscordInteractionDataOption | 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): DiscordInteractionDataOption;
get(
name: string | number,
required: boolean
): DiscordInteractionDataOption | undefined;
get(name: string | number, required?: boolean) {
const option: DiscordInteractionDataOption | 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: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.String,
['value'],
required
);
return option?.value ?? 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: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.Number,
['value'],
required
);
return option?.value ?? 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: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.Integer,
['value'],
required
);
return option?.value ?? 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: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.Boolean,
['value'],
required
);
return option?.value ?? undefined;
}
/** searches for a user option */
getUser(name: string | number, required: true): Snowflake;
getUser(name: string | number, required?: boolean): Snowflake | undefined;
getUser(name: string | number, required = false) {
const option: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.User,
['value'],
required
);
return option?.value ?? undefined;
}
/** searches for a channel option */
getChannel(name: string | number, required: true): Snowflake;
getChannel(name: string | number, required?: boolean): Snowflake | undefined;
getChannel(name: string | number, required = false) {
const option: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.Channel,
['value'],
required
);
return option?.value ?? 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: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.Mentionable,
['value'],
required
);
return option?.value ?? undefined;
}
/** searches for a mentionable-based option */
getRole(name: string | number, required: true): Snowflake;
getRole(name: string | number, required?: boolean): Snowflake | undefined;
getRole(name: string | number, required = false) {
const option: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.Role,
['value'],
required
);
return option?.value ?? undefined;
}
/** searches for an attachment option */
getAttachment(name: string | number, required: true): Snowflake;
getAttachment(name: string | number, required?: boolean): Snowflake | undefined;
getAttachment(name: string | number, required = false) {
const option: DiscordInteractionDataOption | void = this.getTypedOption(
name,
ApplicationCommandOptionTypes.Attachment,
['value'],
required
);
return option?.value ?? undefined;
}
/** searches for the focused option */
getFocused(full: true): DiscordInteractionDataOption;
getFocused(full: false): DiscordInteractionDataOption['value'];
getFocused(full = false) {
const focusedOption: DiscordInteractionDataOption | void =
this.hoistedOptions.find(option => option.focused);
if (!focusedOption) {
throw new TypeError('No option found');
}
return full ? focusedOption : focusedOption.value;
}
getSubCommand(
required = true
): [string | undefined, DiscordInteractionDataOption[]] {
if (required && !this._subcommand) {
throw new TypeError('Option marked as required was undefined');
}
return [this._subcommand, this.hoistedOptions];
}
getSubCommandGroup(
required = false
): [string | undefined, DiscordInteractionDataOption[]] {
if (required && !this._group) {
throw new TypeError('Option marked as required was undefined');
}
return [this._group, this.hoistedOptions];
}
}

View File

@ -1,147 +0,0 @@
import { BitwisePermissionFlags } from '@biscuitland/api-types';
export interface BitField<T extends number | bigint> {
add(...bits: T[]): this;
remove(...bits: T[]): this;
has(bit: T): boolean;
any(bit: T): boolean;
equals(bit: T): boolean;
}
export type PermissionString = keyof typeof BitwisePermissionFlags;
export type PermissionResolvable =
| bigint
| PermissionString
| PermissionString[]
| BitwisePermissionFlags
| BitwisePermissionFlags[];
export class Permissions implements BitField<bigint> {
/** Stores a reference to BitwisePermissionFlags */
static Flags = BitwisePermissionFlags;
/** Falsy; Stores the lack of permissions*/
static None = 0n;
/** Stores all entity permissions */
bitfield: bigint;
/**
* Wheter to grant all other permissions to the administrator
* **Not to get confused with Permissions#admin**
*/
__admin__ = true;
constructor(bitfield: PermissionResolvable) {
this.bitfield = Permissions.resolve(bitfield);
}
/** Wheter the bitfield has the administrator flag */
get admin(): boolean {
return this.has(Permissions.Flags.ADMINISTRATOR);
}
get array(): PermissionString[] {
// unsafe cast, do not edit
const permissions = Object.keys(Permissions.Flags) as PermissionString[];
return permissions.filter(bit => this.has(bit));
}
add(...bits: PermissionResolvable[]): this {
let reduced = 0n;
for (const bit of bits) {
reduced |= Permissions.resolve(bit);
}
this.bitfield |= reduced;
return this;
}
remove(...bits: PermissionResolvable[]): this {
let reduced = 0n;
for (const bit of bits) {
reduced |= Permissions.resolve(bit);
}
this.bitfield &= ~reduced;
return this;
}
has(bit: PermissionResolvable): boolean {
const bbit = Permissions.resolve(bit);
if (this.__admin__ && this.bitfield & BigInt(Permissions.Flags.ADMINISTRATOR)) {
return true;
}
return (this.bitfield & bbit) === bbit;
}
any(bit: PermissionResolvable): boolean {
const bbit = Permissions.resolve(bit);
if (this.__admin__ && this.bitfield & BigInt(Permissions.Flags.ADMINISTRATOR)) {
return true;
}
return (this.bitfield & bbit) !== Permissions.None;
}
equals(bit: PermissionResolvable): boolean {
return !!(this.bitfield & Permissions.resolve(bit));
}
/** Gets all permissions */
static get All(): bigint {
let reduced = 0n;
for (const key in BitwisePermissionFlags) {
const perm = BitwisePermissionFlags[key];
if (typeof perm === 'number') {
reduced += perm;
}
}
return reduced;
}
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 => typeof p === 'string' ? BigInt(Permissions.Flags[p]) : BigInt(p))
.reduce((acc, cur) => acc | cur, Permissions.None)
);
default:
throw new TypeError(`Cannot resolve permission: ${bit}`);
}
}
static sum(permissions: (bigint | number)[]) {
return permissions.reduce((y, x) => BigInt(y) | BigInt(x), Permissions.None);
}
static reduce(permissions: PermissionResolvable[]): Permissions {
const solved = permissions.map(Permissions.resolve);
return new Permissions(solved.reduce((y, x) => y | x, Permissions.None));
}
*[Symbol.iterator]() {
yield* this.array;
}
valueOf() {
return this.bitfield;
}
toJSON(): { fields: string[] } {
const fields = Object.keys(Permissions.Flags).filter(bit => typeof bit === 'number' && this.has(bit));
return { fields };
}
}

View File

@ -1,63 +0,0 @@
import type { Model } from './base';
import type { Session } from '../biscuit';
import type { Snowflake } from '../snowflakes';
import type { DiscordStageInstance as DiscordAutoClosingStageInstance } from '@biscuitland/api-types';
import { STAGE_INSTANCE } from '@biscuitland/api-types';
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.patch<DiscordStageInstanceB>(
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.delete<undefined>(STAGE_INSTANCE(this.id), {});
}
}

View File

@ -1,73 +0,0 @@
import type {
DiscordSticker,
DiscordStickerPack,
StickerFormatTypes,
StickerTypes,
} from '@biscuitland/api-types';
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type { Session } from '../biscuit';
import { User } from './user';
import { STICKER_PACKS } from '@biscuitland/api-types';
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.get<DiscordStickerPack>(
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,
};
}
}

View File

@ -1,123 +0,0 @@
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type { Session } from '../biscuit';
import type { DiscordUser, PremiumTypes, UserFlags } from '@biscuitland/api-types';
import type { ImageFormat, ImageSize } from '../utils/util';
import { USER, USER_AVATAR, USER_DEFAULT_AVATAR } from '@biscuitland/api-types';
import { Util } from '../utils/util';
import { DMChannel } from './channels';
export type AvatarOptions = {
format?: ImageFormat;
size?: ImageSize;
};
/**
* Represents a user
* @link https://discord.com/developers/docs/resources/user#user-object
*/
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
? data.avatar
: undefined;
this.accentColor = data.accent_color;
this.bot = !!data.bot;
this.system = !!data.system;
this.banner = data.banner
? 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 */
avatarHash?: string;
/** 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 */
banner?: string;
/** 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 */
async fetch(): Promise<User> {
const user = await this.session.rest.get<DiscordUser>(USER(this.id));
return new User(this.session, user);
}
/** gets the user's avatar */
avatarURL(options: AvatarOptions): string {
if (!this.avatarHash) {
return USER_DEFAULT_AVATAR(Number(this.discriminator) % 5);
}
return Util.formatImageURL(USER_AVATAR(
this.id,
this.avatarHash
), options.size ?? 128, options.format);
}
openDM(): Promise<DMChannel> {
return DMChannel.prototype.open.call({ session: this.session }, this.id);
}
toString(): string {
return `<@${this.id}>`;
}
}

View File

@ -1,211 +0,0 @@
import type { Model } from './base';
import type { Session } from '../biscuit';
import type { Snowflake } from '../snowflakes';
import type {
DiscordEmbed,
DiscordMessage,
DiscordMessageComponents,
DiscordWebhook,
FileContent,
WebhookTypes,
WebhookOptions,
} from '@biscuitland/api-types';
import type { Attachment } from './attachment';
import type { AllowedMentions, CreateMessage } from './message';
import { User } from './user';
import { Message } from './message';
import { Util } from '../utils/util';
import {
WEBHOOK,
WEBHOOK_TOKEN,
WEBHOOK_MESSAGE,
WEBHOOK_MESSAGE_ORIGINAL,
} from '@biscuitland/api-types';
import { NewEmbed } from './embed';
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?.map(NewEmbed),
tts: options?.tts,
allowed_mentions: options?.allowedMentions,
components: options?.components,
file: options?.files,
};
const message = await this.session.rest.post<DiscordMessage>(
WEBHOOK(this.id, this.token, {
wait: options?.wait,
threadId: options?.threadId,
}),
data
);
return options?.wait ?? true
? new Message(this.session, message)
: undefined;
}
async fetch(): Promise<Webhook> {
const message = await this.session.rest.get<DiscordWebhook>(
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.get<DiscordMessage>(
WEBHOOK_MESSAGE(this.id, this.token, messageId, { threadId })
);
return new Message(this.session, message);
}
/**
* @deprecated you might want to delete an ephemeral message (deleteFollowUp)
* */
deleteMessage(
messageId: Snowflake,
threadId?: Snowflake
): Promise<void> {
return this.deleteThreadMessage(messageId, threadId);
}
async deleteThreadMessage(
messageId: Snowflake,
threadId?: Snowflake
): Promise<void> {
if (!this.token) {
throw new Error('No token found');
}
await this.session.rest.delete<undefined>(
WEBHOOK_MESSAGE(this.id, this.token, messageId, { threadId }),
{}
);
}
async deleteFollowUp(messageId?: Snowflake): Promise<void> {
if (!this.token) {
throw new Error('No token found');
}
await this.session.rest.delete(
messageId
? WEBHOOK_MESSAGE(this.id, this.token, messageId)
: WEBHOOK_MESSAGE_ORIGINAL(this.id, this.token)
);
}
async editMessage(
messageId?: Snowflake,
options?: EditMessageWithThread
): Promise<Message> {
if (!this.token) {
throw new Error('No token found');
}
const message = await this.session.rest.patch<DiscordMessage>(
messageId
? WEBHOOK_MESSAGE(this.id, this.token, messageId)
: WEBHOOK_MESSAGE_ORIGINAL(this.id, this.token),
{
content: options?.content,
embeds: options?.embeds?.map(NewEmbed),
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);
}
}

View File

@ -1,55 +0,0 @@
import type { Session } from '../biscuit';
import type { Model } from './base';
import type { Snowflake } from '../snowflakes';
import type {
DiscordWelcomeScreen,
DiscordWelcomeScreenChannel,
} from '@biscuitland/api-types';
import { Emoji } from './emojis';
/**
* 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;
}
}
/**
* @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[];
}

View File

@ -1,41 +0,0 @@
import type { Model } from './base';
import type { Session } from '../biscuit';
import type { Snowflake } from '../snowflakes';
import type { DiscordGuildWidget } from '@biscuitland/api-types';
import type { PartialChannel } from './channels';
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;
}

View File

@ -1,5 +0,0 @@
export function calculateShardId(totalShards: number, guildId: bigint) {
if (totalShards === 1) { return 0; }
return Number((guildId >> 22n) % BigInt(totalShards - 1));
}

View File

@ -1,68 +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.
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;
}

View File

@ -1,108 +0,0 @@
import type { SelectMenuBuilder, InputTextBuilder, ButtonBuilder } from '@biscuitland/helpers';
import type { Permissions } from '../structures/special/permissions';
import type { Snowflake } from '../snowflakes';
import type { DiscordMessage, MakeRequired } from '@biscuitland/api-types';
/*
* @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 abstract 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);
}
/** Removes the Bot before the token. */
static removeTokenPrefix(token?: string, type: 'GATEWAY' | 'REST' = 'REST'): string {
// If no token is provided, throw an error
if (!token) { throw new Error(`The ${type} was not given a token. Please provide a token and try again.`); }
// If the token does not have a prefix just return token
if (!token.startsWith('Bot ')) { return token; }
// Remove the prefix and return only the token.
return token.substring(token.indexOf(' ') + 1);
}
/** Get the bot id from the bot token. WARNING: Discord staff has mentioned this may not be stable forever. Use at your own risk. However, note for over 5 years this has never broken. */
static getBotIdFromToken(token: string): string {
return atob(token.split('.')[0]);
}
static isFullMessage(m: Partial<DiscordMessage> | DiscordMessage): m is DiscordMessage {
return !!m.edited_timestamp;
}
static isPartialMessage(m: Partial<DiscordMessage> | DiscordMessage): m is PartialMessage {
return !m.edited_timestamp;
}
}
export type PartialMessage = MakeRequired<DiscordMessage, "id" | "channel_id">;

View File

@ -3,5 +3,5 @@
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src/**/*"]
"include": ["src/**/*", "../../bot/Components", "../common/src/applyToClass.ts"]
}

Some files were not shown because too many files have changed in this diff Show More