Merge branch 'master' of https://github.com/Skillz4Killz/Discordeno into snake-case

This commit is contained in:
Skillz
2020-05-19 12:05:53 -04:00
31 changed files with 804 additions and 539 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true

View File

@@ -3,7 +3,7 @@ name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: Skillz4Killz, resynth1943
assignees: Skillz4Killz
---

View File

@@ -2,13 +2,13 @@
name: Testing/Linting
# Controls when the action will run. Triggers the workflow on push or pull request
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
@@ -19,14 +19,17 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Setup project
run: cp configs.ts.example configs.ts
- name: Setup Deno environment
uses: denolib/setup-deno@master
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Deno Fetch
run: deno cache mod.ts
- name: Setup project
run: cp configs.ts.example configs.ts
- name: Setup Deno environment
uses: denolib/setup-deno@master
- name: Deno Fetch
run: deno cache mod.ts
- name: Deno Format Check
run: deno fmt --check

View File

@@ -6,8 +6,8 @@ jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Thank you for helping contribute to Discordeno. I really do appreciate any and all contributions! Hopefully, together we will be able to make the very best gaming bot in the world. Since, this is your very first issue, feel free to look around the repository and then hop on into the Discord server, where you can chat with me directly. https://discord.gg/J4NqJ72'
pr-message: 'Thank you for helping contribute to Discordeno. I really do appreciate any and all contributions! Hopefully, together we will be able to make the very best gaming bot in the world. Since, this is your very first pull request, feel free to look around the repository and then hop on into the Discord server, where you can chat with me directly. https://discord.gg/J4NqJ72'
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: "Thank you for helping contribute to Discordeno. I really do appreciate any and all contributions! Hopefully, together we will be able to make the very best bot Discord API module in the world. Since, this is your very first issue, feel free to look around the repository and then hop on into the Discord server, where you can chat with me directly. https://discord.gg/J4NqJ72"
pr-message: "Thank you for helping contribute to Discordeno. I really do appreciate any and all contributions! Hopefully, together we will be able to make the very best Discord API module in the world. Since, this is your very first pull request, feel free to look around the repository and then hop on into the Discord server, where you can chat with me directly. https://discord.gg/J4NqJ72"

View File

@@ -1,4 +0,0 @@
{
"semi": false,
"printWidth": 120
}

View File

@@ -1,5 +1,6 @@
{
"deno.enable": true,
"editor.formatOnSave": true,
"deno.autoFmtOnSave": true,
"deno.unstable": true
}

View File

@@ -10,7 +10,16 @@ Discord API library wrapper in Deno
If you are just starting out, you can use the Discordeno Template repo to get the base of your bot pre-built for you. As other developers create other command frameworks for this library, those frameworks will be listed here:
**[Official Boilerplate](https://github.com/Skillz4Killz/Discordeno-bot-template):** This is a very minimalistic design for a boilerplate for your bot to get you started.
| Bot Name | Developer | Links | Description |
|--------------------|--------------------|---------------------------------------------------------|-----|
| Official Boilerplate | Skillz4Killz#4500 | [Github](https://github.com/Skillz4Killz/Discordeno-bot-template), [Support Server](https://discord.gg/J4NqJ72) | This is a very minimalistic design for a boilerplate for your bot to get you started. |
| DenoBot | NTM Nathan#0001 | [Github](https://github.com/ntm-development/DenoBot), [Support Server](https://discord.com/invite/G2rb53z) | Another boilerplate example of the first one, with more commands and improvements. |
## Open Source Bots Using Discordeno
| Bot Name | Developer | Links |
|--------------------|--------------------|---------------------------------------------------------|
| discordeno-mattis | Mattis6666 | [Github](https://github.com/Mattis6666/discordeno-mattis/) |
## Motivations/Features
@@ -294,3 +303,10 @@ This section will list out all the available methods and functionality in the li
```ts
- .avatarURL(size, format)
```
## Utils
```ts
.editBotsStatus(status, name, type)
updateEventHandlers(eventHandlers)
```

View File

@@ -1,3 +1,3 @@
export const configs = {
token: 'BOT TOKEN HERE'
}
token: "BOT TOKEN HERE",
};

44
debug.ts Normal file
View File

@@ -0,0 +1,44 @@
/*
* This File will never run when you use it.
* It is only meant for easy debugging/adding features to the library.
* It allows me to easily run up a bot using the library itself without having to commit code
* and then reloading cache from another bot folder to then test each micro change.
* Especially since a lot of Deno is still unstable and we have to be able to adjust on the fly this is helpful.
* Don't worry this will never run and you should never touch this file.
* Review the official boilerplates to see how to start a bot!
*/
import Client from "./module/client.ts";
import { configs } from "./configs.ts";
import { Intents } from "./types/options.ts";
import { logYellow } from "./utils/logger.ts";
import { cache } from "./utils/cache.ts";
import { editBotsStatus } from "./utils/utils.ts";
import { StatusType } from "./types/discord.ts";
import { ActivityType } from "./types/activity.ts";
Client({
token: configs.token,
botID: "675412054529540107",
intents: [Intents.GUILDS, Intents.GUILD_MESSAGES, Intents.GUILD_MEMBERS],
eventHandlers: {
ready: () => {
logYellow("Bot ready emitted");
editBotsStatus(
StatusType.DoNotDisturb,
"Testing Name DND",
ActivityType.Listening,
);
},
// raw: (data) => logGreen("[RAW] => " + JSON.stringify(data)),
messageCreate: async (message) => {
if (message.author.id === "130136895395987456") {
if (message.content.startsWith("!test")) {
if (!message.guild_id) return;
const guild = cache.guilds.get(message.guild_id);
if (!guild) return logYellow("no guild");
}
}
},
},
});

View File

@@ -6,6 +6,10 @@ import { eventHandlers } from "../module/client.ts";
export const handleInternalChannelCreate = (data: ChannelCreatePayload) => {
const channel = createChannel(data);
cache.channels.set(channel.id, channel);
if (channel.guildID) {
const guild = cache.guilds.get(channel.guildID);
guild?.channels.set(channel.id, channel);
}
eventHandlers.channelCreate?.(channel);
};
@@ -15,6 +19,11 @@ export const handleInternalChannelUpdate = (data: ChannelCreatePayload) => {
cache.channels.set(channel.id, channel);
if (!cachedChannel) return;
if (channel.guildID) {
const guild = cache.guilds.get(channel.guildID);
guild?.channels.set(channel.id, channel);
}
eventHandlers.channelUpdate?.(channel, cachedChannel);
};
@@ -38,6 +47,8 @@ export const handleInternalChannelDelete = (data: ChannelCreatePayload) => {
eventHandlers.voiceChannelLeave?.(member, vs.channelID);
});
}
guild?.channels.delete(data.id);
}
cache.channels.delete(data.id);

51
mod.ts
View File

@@ -1,25 +1,30 @@
import Client from "./module/client.ts";
import { configs } from "./configs.ts";
import { Intents } from "./types/options.ts";
import { logYellow } from "./utils/logger.ts";
import { cache } from "./utils/cache.ts";
export * from "./module/client.ts";
export * from "./module/requestManager.ts";
export * from "./module/shardingManager.ts";
Client({
token: configs.token,
botID: "675412054529540107",
intents: [Intents.GUILDS, Intents.GUILD_MESSAGES, Intents.GUILD_MEMBERS],
eventHandlers: {
ready: () => logYellow("Bot ready emitted"),
// raw: (data) => logGreen("[RAW] => " + JSON.stringify(data)),
messageCreate: async (message) => {
if (message.author.id === "130136895395987456") {
if (message.content.startsWith("!test")) {
if (!message.guildID) return;
const guild = cache.guilds.get(message.guildID);
if (!guild) return logYellow("no guild");
export * from "./structures/channel.ts";
export * from "./structures/guild.ts";
export * from "./structures/member.ts";
export * from "./structures/message.ts";
export * from "./structures/role.ts";
export * from "./structures/user.ts";
}
}
},
},
});
export * from "./types/activity.ts";
export * from "./types/cdn.ts";
export * from "./types/channel.ts";
export * from "./types/discord.ts";
export * from "./types/errors.ts";
export * from "./types/fetch.ts";
export * from "./types/guild.ts";
export * from "./types/member.ts";
export * from "./types/message.ts";
export * from "./types/options.ts";
export * from "./types/permission.ts";
export * from "./types/presence.ts";
export * from "./types/role.ts";
export * from "./utils/cache.ts";
export * from "./utils/cdn.ts";
export * from "./utils/logger.ts";
export * from "./utils/permissions.ts";
export * from "./utils/utils.ts";

View File

@@ -32,7 +32,9 @@ export const createClient = async (data: ClientOptions) => {
authorization = `Bot ${data.token}`;
// Initial API connection to get info about bots connection
botGatewayData = await RequestManager.get(endpoints.GATEWAY_BOT);
botGatewayData = await RequestManager.get(
endpoints.GATEWAY_BOT,
) as DiscordBotGatewayData;
identifyPayload.token = data.token;
identifyPayload.intents = data.intents.reduce(
@@ -48,3 +50,7 @@ export default createClient;
export const updateChannelCache = (key: string, value: Channel) => {
cache.channels.set(key, value);
};
export function updateEventHandlers(newEventHandlers: EventHandlers) {
eventHandlers = newEventHandlers;
}

View File

@@ -1,22 +0,0 @@
import { WebSocket } from "https://deno.land/std@0.50.0/ws/mod.ts";
import { GatewayOpcode } from "../types/discord.ts";
import { delay } from "https://deno.land/std@0.50.0/async/mod.ts";
// Discord requests null if no number has yet been sent by discord
export let previousSequenceNumber: number | null = null;
// TODO: If a client does not receive a heartbeat ack between its attempts at sending heartbeats, it should immediately terminate the connection with a non-1000 close code, reconnect, and attempt to resume.
export const sendConstantHeartbeats = async (
socket: WebSocket,
interval: number,
) => {
await delay(interval);
socket.send(
JSON.stringify({ op: GatewayOpcode.Heartbeat, d: previousSequenceNumber }),
);
sendConstantHeartbeats(socket, interval);
};
export const updatePreviousSequenceNumber = (sequence: number) => {
previousSequenceNumber = sequence;
};

View File

@@ -2,21 +2,29 @@ import { RequestMethod } from "../types/fetch.ts";
import { authorization } from "./client.ts";
import { delay } from "https://deno.land/std@0.50.0/async/delay.ts";
import { Errors } from "../types/errors.ts";
import { HttpResponseCode } from "../types/discord.ts";
const queue: Array<() => Promise<unknown>> = [];
const queue: QueuedRequest[] = [];
const ratelimitedPaths = new Map<string, RateLimitedPath>();
let globallyRateLimited = false;
let queueInProcess = false;
export interface QueuedRequest {
callback: () => Promise<unknown>;
bucketID?: string | null;
url: string;
}
export interface RateLimitedPath {
url: string;
resetTimestamp: number;
bucketID: string | null;
}
async function processRateLimitedPaths() {
const now = Date.now();
ratelimitedPaths.forEach((value, key) => {
if (value.resetTimestamp > now) return;
if (value.resetTimestamp < now) return;
ratelimitedPaths.delete(key);
if (key === "global") globallyRateLimited = false;
});
@@ -27,8 +35,25 @@ async function processRateLimitedPaths() {
async function processQueue() {
if (queue.length && !globallyRateLimited) {
const callback = queue.shift();
if (callback) await callback();
const request = queue.shift();
if (request?.bucketID) {
const rateLimitResetIn = checkRatelimits(request.bucketID);
const rateLimitedURLResetIn = checkRatelimits(request.url);
if (rateLimitResetIn) {
// This request is still rate limited readd to queue
queue.push(request);
} else if (rateLimitedURLResetIn) {
// This URL is rate limited readd to queue
queue.push(request);
} else {
// This request is not rate limited so it should be run
await request.callback();
}
} else {
// This request has no bucket id so it should be processed
await request?.callback();
}
}
if (queue.length) processQueue();
@@ -38,13 +63,8 @@ async function processQueue() {
processRateLimitedPaths();
export const RequestManager = {
// Something off about using runMethod with get breaks when using fetch
get: async (url: string, body?: unknown) => {
await checkRatelimits(url);
const result = await fetch(url, createRequestBody(body));
processHeaders(url, result.headers);
return result.json();
return runMethod(RequestMethod.Get, url, body);
},
post: (url: string, body?: unknown) => {
return runMethod(RequestMethod.Post, url, body);
@@ -60,7 +80,7 @@ export const RequestManager = {
},
};
function createRequestBody (body: any, method?: RequestMethod) {
function createRequestBody(body: any, method: RequestMethod) {
return {
headers: {
Authorization: authorization,
@@ -70,21 +90,23 @@ function createRequestBody (body: any, method?: RequestMethod) {
"X-Audit-Log-Reason": body ? encodeURIComponent(body.reason) : "",
},
body: JSON.stringify(body),
method: method?.toUpperCase(),
method: method.toUpperCase(),
};
};
}
async function checkRatelimits(url: string) {
function checkRatelimits(url: string) {
const ratelimited = ratelimitedPaths.get(url);
const global = ratelimitedPaths.get("global");
const now = Date.now();
if (ratelimited && now < ratelimited.resetTimestamp) {
await delay(now - ratelimited.resetTimestamp);
return ratelimited.resetTimestamp - now;
}
if (global && now < global.resetTimestamp) {
await delay(now - global.resetTimestamp);
return global.resetTimestamp - now;
}
return false;
}
async function runMethod(
@@ -92,24 +114,40 @@ async function runMethod(
url: string,
body?: unknown,
retryCount = 0,
bucketID?: string | null,
) {
return new Promise((resolve, reject) => {
const callback = async () => {
try {
await checkRatelimits(url);
const rateLimitResetIn = checkRatelimits(url);
if (rateLimitResetIn) {
return setTimeout(
() => runMethod(method, url, body, retryCount++, bucketID),
rateLimitResetIn,
);
}
const response = await fetch(url, createRequestBody(body, method));
processHeaders(url, response.headers);
const bucketIDFromHeaders = processHeaders(url, response.headers);
handleStatusCode(response.status);
// Sometimes Discord returns an empty 204 response that can't be made to JSON.
if (response.status === 204) resolve();
const json = await response.json();
if (
json.retry_after || json.message === "You are being rate limited."
json.retry_after ||
json.message === "You are being rate limited."
) {
if (retryCount > 10) throw new Error(Errors.RATE_LIMIT_RETRY_MAXED);
await delay(json.retry_after);
return runMethod(method, url, body, retryCount++)
return runMethod(
method,
url,
body,
retryCount++,
bucketIDFromHeaders,
);
}
return resolve(json);
@@ -118,16 +156,40 @@ async function runMethod(
}
};
queue.push(callback);
queue.push({
callback,
bucketID,
url,
});
if (!queueInProcess) {
queueInProcess = true;
processQueue();
}
});
};
}
function handleStatusCode(status: number) {
if (status >= 200 && status < 400) {
return true;
}
switch (status) {
case HttpResponseCode.BadRequest:
case HttpResponseCode.Unauthorized:
case HttpResponseCode.Forbidden:
case HttpResponseCode.NotFound:
case HttpResponseCode.MethodNotAllowed:
case HttpResponseCode.TooManyRequests:
throw new Error(Errors.REQUEST_CLIENT_ERROR);
case HttpResponseCode.GatewayUnavailable:
throw new Error(Errors.REQUEST_SERVER_ERROR);
}
// left are all unknown
throw new Error(Errors.REQUEST_UNKNOWN_ERROR);
}
function processHeaders(url: string, headers: Headers) {
// If a rate limit response is encountered this will become true and returned
let ratelimited = false;
// Get all useful headers
@@ -135,7 +197,7 @@ function processHeaders(url: string, headers: Headers) {
const resetTimestamp = headers.get("x-ratelimit-reset");
const retryAfter = headers.get("retry-after");
const global = headers.get("x-ratelimit-global");
// const bucketID = headers.get("x-ratelimit-bucket");
const bucketID = headers.get("x-ratelimit-bucket");
// If there is no remaining rate limit for this endpoint, we save it in cache
if (remaining && remaining === "0") {
@@ -143,8 +205,17 @@ function processHeaders(url: string, headers: Headers) {
ratelimitedPaths.set(url, {
url,
resetTimestamp: Number(resetTimestamp),
resetTimestamp: Number(resetTimestamp) * 1000,
bucketID,
});
if (bucketID) {
ratelimitedPaths.set(bucketID, {
url,
resetTimestamp: Number(resetTimestamp) * 1000,
bucketID,
});
}
}
// If there is no remaining global limit, we save it in cache
@@ -155,9 +226,17 @@ function processHeaders(url: string, headers: Headers) {
ratelimitedPaths.set("global", {
url: "global",
resetTimestamp: Date.now() + Number(retryAfter),
bucketID,
});
if (bucketID) {
ratelimitedPaths.set(bucketID, {
url: "global",
resetTimestamp: Date.now() + Number(retryAfter),
bucketID,
});
}
}
// Returns a boolean to check if we need to request again once the rate limit resets
return ratelimited;
};
return ratelimited ? bucketID : undefined;
}

View File

@@ -9,99 +9,123 @@ import {
DiscordHeartbeatPayload,
ReadyPayload,
} from "../types/discord.ts";
import { logRed, logBlue } from "../utils/logger.ts";
import { sendConstantHeartbeats, previousSequenceNumber } from "./gateway.ts";
import { logRed } from "../utils/logger.ts";
import { FetchMembersOptions } from "../types/guild.ts";
import { delay } from "https://deno.land/std@0.50.0/async/delay.ts";
let shardSocket: WebSocket;
/** The session id is needed for RESUME functionality when discord disconnects randomly. */
let sessionID = "";
async function resumeConnection(payload: object, botGatewayData: DiscordBotGatewayData) {
return setInterval(async () => {
console.log("in resume interval");
shardSocket = await connectWebSocket(botGatewayData.url);
console.log("after connect");
await shardSocket.send(
JSON.stringify({
op: GatewayOpcode.Resume,
d: {
...payload,
session_id: sessionID,
seq: previousSequenceNumber,
},
}),
);
console.log("after sending resume");
}, 1000 * 15);
// Discord requests null if no number has yet been sent by discord
let previousSequenceNumber: number | null = null;
let needToResume = false;
// TODO: If a client does not receive a heartbeat ack between its attempts at sending heartbeats, it should immediately terminate the connection with a non-1000 close code, reconnect, and attempt to resume.
async function sendConstantHeartbeats(
interval: number,
) {
await delay(interval);
shardSocket.send(
JSON.stringify({ op: GatewayOpcode.Heartbeat, d: previousSequenceNumber }),
);
sendConstantHeartbeats(interval);
}
export const createShard = async (
async function resumeConnection(
botGatewayData: DiscordBotGatewayData,
identifyPayload: object,
) {
// Run it once
createShard(botGatewayData, identifyPayload, true);
// Then retry every 15 seconds
await delay(1000 * 15);
if (needToResume) resumeConnection(botGatewayData, identifyPayload);
}
const createShard = async (
botGatewayData: DiscordBotGatewayData,
identifyPayload: object,
resuming = false,
) => {
shardSocket = await connectWebSocket(botGatewayData.url);
let resumeInterval = 0;
// Intial identify with the gateway
await shardSocket.send(
JSON.stringify({ op: GatewayOpcode.Identify, d: identifyPayload }),
);
if (!resuming) {
// Intial identify with the gateway
await shardSocket.send(
JSON.stringify({ op: GatewayOpcode.Identify, d: identifyPayload }),
);
} else {
await shardSocket.send(JSON.stringify({
op: GatewayOpcode.Resume,
d: {
...identifyPayload,
session_id: sessionID,
seq: previousSequenceNumber,
},
}));
}
try {
for await (const message of shardSocket) {
if (typeof message === "string") {
const data = JSON.parse(message);
for await (const message of shardSocket) {
if (typeof message === "string") {
const data = JSON.parse(message);
switch (data.op) {
case GatewayOpcode.Hello:
sendConstantHeartbeats(
shardSocket,
(data.d as DiscordHeartbeatPayload).heartbeat_interval,
);
switch (data.op) {
case GatewayOpcode.Hello:
sendConstantHeartbeats(
(data.d as DiscordHeartbeatPayload).heartbeat_interval,
);
break;
case GatewayOpcode.Reconnect:
case GatewayOpcode.InvalidSession:
needToResume = true;
resumeConnection(botGatewayData, identifyPayload);
break;
default:
if (data.t === "RESUMED") {
needToResume = false;
break;
case GatewayOpcode.Reconnect:
case GatewayOpcode.InvalidSession:
// Reconnect to the gateway https://discordapp.com/developers/docs/topics/gateway#reconnect
resumeInterval = await resumeConnection(
identifyPayload,
botGatewayData,
);
break;
case GatewayOpcode.Resume:
logBlue("Got RESUME EVENT");
clearInterval(resumeInterval);
break;
default:
// Important for RESUME
if (data.t === "READY") {
sessionID = (data.d as ReadyPayload).session_id;
}
// @ts-ignore
postMessage(
{
type: "HANDLE_DISCORD_PAYLOAD",
payload: message,
resumeInterval,
},
);
break;
}
} else if (isWebSocketCloseEvent(message)) {
logRed(`Close :( ${JSON.stringify(message)}`);
resumeInterval = await resumeConnection(
identifyPayload,
botGatewayData,
}
// Important for RESUME
if (data.t === "READY") {
sessionID = (data.d as ReadyPayload).session_id;
}
// Update the sequence number if it is present
if (data.s) previousSequenceNumber = data.s;
// @ts-ignore
postMessage(
{
type: "HANDLE_DISCORD_PAYLOAD",
payload: message,
resumeInterval,
},
);
break;
}
} else if (isWebSocketCloseEvent(message)) {
logRed(`Close :( ${JSON.stringify(message)}`);
// These error codes should just crash the projects
if ([4004, 4005, 4012, 4013, 4014].includes(message.code)) {
throw new Error(
"Shard.ts: Error occurred that is not resumeable or able to be reconnected.",
);
}
// These error codes can not be resumed but need to reconnect from start
if ([4003, 4007, 4008, 4009].includes(message.code)) {
createShard(botGatewayData, identifyPayload);
} else {
needToResume = true;
resumeConnection(botGatewayData, identifyPayload);
}
}
} catch (error) {
logRed(error);
}
};
export function requestGuildMembers(
function requestGuildMembers(
guildID: string,
nonce: string,
options?: FetchMembersOptions,
@@ -119,29 +143,40 @@ export function requestGuildMembers(
}));
}
// TODO: Remove ts-ignore once https://github.com/denoland/deno/issues/5262 fixed
// TODO: Errors need to be fixed by VSC plugin
// @ts-ignore
if (typeof self.postMessage === "function") {
// @ts-ignore
postMessage({ type: "REQUEST_CLIENT_OPTIONS" });
}
postMessage({ type: "REQUEST_CLIENT_OPTIONS" });
// @ts-ignore
if (typeof self.onmessage === "function") {
// @ts-ignore
onmessage = (message) => {
if (message.data.type === "CREATE_SHARD") {
createShard(
message.data.botGatewayData,
message.data.identifyPayload,
);
}
onmessage = (message: MessageEvent) => {
if (message.data.type === "CREATE_SHARD") {
createShard(
message.data.botGatewayData,
message.data.identifyPayload,
);
}
if (message.data.type === "FETCH_MEMBERS") {
requestGuildMembers(
message.data.guildID,
message.data.nonce,
message.data.options,
);
}
};
}
if (message.data.type === "FETCH_MEMBERS") {
requestGuildMembers(
message.data.guildID,
message.data.nonce,
message.data.options,
);
}
if (message.data.type === "EDIT_BOTS_STATUS") {
shardSocket.send(JSON.stringify({
op: GatewayOpcode.StatusUpdate,
d: {
since: null,
game: message.data.game.name
? {
name: message.data.game.name,
type: message.data.game.type,
}
: null,
status: message.data.status,
afk: false,
},
}));
}
};

View File

@@ -14,9 +14,6 @@ import {
botID,
} from "./client.ts";
import { delay } from "https://deno.land/std@0.50.0/async/delay.ts";
import {
updatePreviousSequenceNumber,
} from "./gateway.ts";
import {
handleInternalChannelCreate,
handleInternalChannelUpdate,
@@ -105,8 +102,6 @@ export const spawnShards = async (
};
function handleDiscordPayload(data: DiscordPayload) {
// Update the sequence number if it is present
if (data.s) updatePreviousSequenceNumber(data.s);
eventHandlers.raw?.(data);
switch (data.op) {
@@ -195,7 +190,7 @@ function handleDiscordPayload(data: DiscordPayload) {
guild.memberCount = memberCount;
const member = createMember(
options,
options.guild_id,
guild.id,
[...guild.roles.values()].map((role) => role.raw),
guild.ownerID,
);
@@ -561,3 +556,12 @@ export function requestAllMembers(
options,
});
}
export function sendGatewayCommand(type: "EDIT_BOTS_STATUS", payload: object) {
shards.forEach((shard) => {
shard.postMessage({
type,
...payload,
});
});
}

View File

@@ -8,7 +8,7 @@ import {
CreateInviteOptions,
ChannelEditOptions,
} from "../types/channel.ts";
import { botID, updateChannelCache } from "../module/client.ts";
import { updateChannelCache } from "../module/client.ts";
import { endpoints } from "../constants/discord.ts";
import { createMessage } from "./message.ts";
import { MessageCreateOptions } from "../types/message.ts";
@@ -55,14 +55,13 @@ export function createChannel(data: ChannelCreatePayload) {
getMessage: async (id: string) => {
if (data.guild_id) {
if (
!botHasPermission(data.guild_id, botID, [Permissions.VIEW_CHANNEL])
!botHasPermission(data.guild_id, [Permissions.VIEW_CHANNEL])
) {
throw new Error(Errors.MISSING_VIEW_CHANNEL);
}
if (
!botHasPermission(
data.guild_id,
botID,
[Permissions.READ_MESSAGE_HISTORY],
)
) {
@@ -71,7 +70,7 @@ export function createChannel(data: ChannelCreatePayload) {
}
const result = await RequestManager.get(
endpoints.CHANNEL_MESSAGE(data.id, id),
);
) as MessageCreateOptions;
return createMessage(result);
},
/** Fetches between 2-100 messages. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY */
@@ -84,14 +83,13 @@ export function createChannel(data: ChannelCreatePayload) {
) => {
if (data.guild_id) {
if (
!botHasPermission(data.guild_id, botID, [Permissions.VIEW_CHANNEL])
!botHasPermission(data.guild_id, [Permissions.VIEW_CHANNEL])
) {
throw new Error(Errors.MISSING_VIEW_CHANNEL);
}
if (
!botHasPermission(
data.guild_id,
botID,
[Permissions.READ_MESSAGE_HISTORY],
)
) {
@@ -120,7 +118,7 @@ export function createChannel(data: ChannelCreatePayload) {
if (data.guild_id) {
if (
!botHasPermission(data.guild_id, botID, [Permissions.SEND_MESSAGES])
!botHasPermission(data.guild_id, [Permissions.SEND_MESSAGES])
) {
throw new Error(Errors.MISSING_SEND_MESSAGES);
}
@@ -128,7 +126,6 @@ export function createChannel(data: ChannelCreatePayload) {
content.tts &&
!botHasPermission(
data.guild_id,
botID,
[Permissions.SEND_TTS_MESSAGES],
)
) {
@@ -152,7 +149,7 @@ export function createChannel(data: ChannelCreatePayload) {
deleteMessages: (ids: string[], reason?: string) => {
if (
data.guild_id &&
!botHasPermission(data.guild_id, botID, [Permissions.MANAGE_MESSAGES])
!botHasPermission(data.guild_id, [Permissions.MANAGE_MESSAGES])
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -173,7 +170,7 @@ export function createChannel(data: ChannelCreatePayload) {
getInvites: () => {
if (
data.guild_id &&
!botHasPermission(data.guild_id, botID, [Permissions.MANAGE_CHANNELS])
!botHasPermission(data.guild_id, [Permissions.MANAGE_CHANNELS])
) {
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
}
@@ -185,7 +182,6 @@ export function createChannel(data: ChannelCreatePayload) {
data.guild_id &&
!botHasPermission(
data.guild_id,
botID,
[Permissions.CREATE_INSTANT_INVITE],
)
) {
@@ -197,7 +193,7 @@ export function createChannel(data: ChannelCreatePayload) {
getWebhooks: () => {
if (
data.guild_id &&
!botHasPermission(data.guild_id, botID, [Permissions.MANAGE_WEBHOOKS])
!botHasPermission(data.guild_id, [Permissions.MANAGE_WEBHOOKS])
) {
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
}

View File

@@ -1,4 +1,4 @@
import { botID, identifyPayload } from "../module/client.ts";
import { identifyPayload } from "../module/client.ts";
import { endpoints } from "../constants/discord.ts";
import { formatImageURL } from "../utils/cdn.ts";
import {
@@ -121,13 +121,12 @@ export const createGuild = (data: CreateGuildPayload) => {
: undefined,
/** Create a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */
createChannel: async (name: string, options: CreateChannelOptions) => {
if (!botHasPermission(data.id, botID, [Permissions.MANAGE_CHANNELS])) {
if (!botHasPermission(data.id, [Permissions.MANAGE_CHANNELS])) {
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
}
const result =
(await RequestManager.post(endpoints.GUILD_CHANNELS(data.id), {
name,
type: options.type ? ChannelTypes[options.type] : undefined,
permission_overwrites: options?.permission_overwrites
? options.permission_overwrites.map((perm) => ({
...perm,
@@ -136,6 +135,7 @@ export const createGuild = (data: CreateGuildPayload) => {
}))
: undefined,
...options,
type: options.type ? ChannelTypes[options.type] : undefined,
})) as ChannelCreatePayload;
const channel = createChannel(result);
@@ -173,7 +173,7 @@ export const createGuild = (data: CreateGuildPayload) => {
options: CreateEmojisOptions,
) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_EMOJIS])
!botHasPermission(data.id, [Permissions.MANAGE_EMOJIS])
) {
throw new Error(Errors.MISSING_MANAGE_EMOJIS);
}
@@ -186,7 +186,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */
editEmoji: (id: string, options: EditEmojisOptions) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_EMOJIS])
!botHasPermission(data.id, [Permissions.MANAGE_EMOJIS])
) {
throw new Error(Errors.MISSING_MANAGE_EMOJIS);
}
@@ -198,7 +198,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */
deleteEmoji: (id: string, reason?: string) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_EMOJIS])
!botHasPermission(data.id, [Permissions.MANAGE_EMOJIS])
) {
throw new Error(Errors.MISSING_MANAGE_EMOJIS);
}
@@ -210,7 +210,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Create a new role for the guild. Requires the MANAGE_ROLES permission. */
createRole: async (options: CreateRoleOptions, reason?: string) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_ROLES])
!botHasPermission(data.id, [Permissions.MANAGE_ROLES])
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
@@ -231,7 +231,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Edit a guild role. Requires the MANAGE_ROLES permission. */
editRole: (id: string, options: CreateRoleOptions) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_ROLES])
!botHasPermission(data.id, [Permissions.MANAGE_ROLES])
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
@@ -240,7 +240,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Delete a guild role. Requires the MANAGE_ROLES permission. */
deleteRole: (id: string) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_ROLES])
!botHasPermission(data.id, [Permissions.MANAGE_ROLES])
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
@@ -252,7 +252,7 @@ export const createGuild = (data: CreateGuildPayload) => {
*/
getRoles: () => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_ROLES])
!botHasPermission(data.id, [Permissions.MANAGE_ROLES])
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
@@ -261,7 +261,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Modify the positions of a set of role objects for the guild. Requires the MANAGE_ROLES permission. */
swapRoles: (rolePositons: PositionSwap) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_ROLES])
!botHasPermission(data.id, [Permissions.MANAGE_ROLES])
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
@@ -271,7 +271,7 @@ export const createGuild = (data: CreateGuildPayload) => {
getPruneCount: async (days: number) => {
if (days < 1) throw new Error(Errors.PRUNE_MIN_DAYS);
if (
!botHasPermission(data.id, botID, [Permissions.KICK_MEMBERS])
!botHasPermission(data.id, [Permissions.KICK_MEMBERS])
) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
@@ -285,7 +285,7 @@ export const createGuild = (data: CreateGuildPayload) => {
pruneMembers: (days: number) => {
if (days < 1) throw new Error(Errors.PRUNE_MIN_DAYS);
if (
!botHasPermission(data.id, botID, [Permissions.KICK_MEMBERS])
!botHasPermission(data.id, [Permissions.KICK_MEMBERS])
) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
@@ -302,7 +302,7 @@ export const createGuild = (data: CreateGuildPayload) => {
},
/** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */
getAuditLogs: (options: GetAuditLogsOptions) => {
if (!botHasPermission(data.id, botID, [Permissions.VIEW_AUDIT_LOG])) {
if (!botHasPermission(data.id, [Permissions.VIEW_AUDIT_LOG])) {
throw new Error(Errors.MISSING_VIEW_AUDIT_LOG);
}
@@ -316,7 +316,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Returns the guild embed object. Requires the MANAGE_GUILD permission. */
getEmbed: () => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_GUILD])
!botHasPermission(data.id, [Permissions.MANAGE_GUILD])
) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -325,7 +325,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Modify a guild embed object for the guild. Requires the MANAGE_GUILD permission. */
editEmbed: (enabled: boolean, channelID?: string | null) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_GUILD])
!botHasPermission(data.id, [Permissions.MANAGE_GUILD])
) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -341,7 +341,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Returns a list of integrations for the guild. Requires the MANAGE_GUILD permission. */
getIntegrations: () => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_GUILD])
!botHasPermission(data.id, [Permissions.MANAGE_GUILD])
) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -350,7 +350,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Modify the behavior and settings of an integration object for the guild. Requires the MANAGE_GUILD permission. */
editIntegration: (id: string, options: EditIntegrationOptions) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_GUILD])
!botHasPermission(data.id, [Permissions.MANAGE_GUILD])
) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -362,7 +362,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Delete the attached integration object for the guild with this id. Requires MANAGE_GUILD permission. */
deleteIntegration: (id: string) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_GUILD])
!botHasPermission(data.id, [Permissions.MANAGE_GUILD])
) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -371,7 +371,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Sync an integration. Requires teh MANAGE_GUILD permission. */
syncIntegration: (id: string) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_GUILD])
!botHasPermission(data.id, [Permissions.MANAGE_GUILD])
) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -380,7 +380,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */
getBans: () => {
if (
!botHasPermission(data.id, botID, [Permissions.BAN_MEMBERS])
!botHasPermission(data.id, [Permissions.BAN_MEMBERS])
) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
@@ -389,7 +389,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Ban a user from the guild and optionally delete previous messages sent by the user. Requires teh BAN_MEMBERS permission. */
ban: (id: string, options: BanOptions) => {
if (
!botHasPermission(data.id, botID, [Permissions.BAN_MEMBERS])
!botHasPermission(data.id, [Permissions.BAN_MEMBERS])
) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
@@ -398,7 +398,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Remove the ban for a user. REquires BAN_MEMBERS permission */
unban: (id: string) => {
if (
!botHasPermission(data.id, botID, [Permissions.BAN_MEMBERS])
!botHasPermission(data.id, [Permissions.BAN_MEMBERS])
) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
@@ -440,7 +440,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Modify a guilds settings. Requires the MANAGE_GUILD permission. */
edit: (options: GuildEditOptions) => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_GUILD])
!botHasPermission(data.id, [Permissions.MANAGE_GUILD])
) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -449,7 +449,7 @@ export const createGuild = (data: CreateGuildPayload) => {
/** Get all the invites for this guild. Requires MANAGE_GUILD permission */
getInvites: () => {
if (
!botHasPermission(data.id, botID, [Permissions.MANAGE_GUILD])
!botHasPermission(data.id, [Permissions.MANAGE_GUILD])
) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -465,7 +465,7 @@ export const createGuild = (data: CreateGuildPayload) => {
},
/** Returns a list of guild webhooks objects. Requires the MANAGE_WEBHOOKs permission. */
getWebhooks: () => {
if (!botHasPermission(data.id, botID, [Permissions.MANAGE_WEBHOOKS])) {
if (!botHasPermission(data.id, [Permissions.MANAGE_WEBHOOKS])) {
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
}

View File

@@ -1,13 +1,18 @@
import { botID } from "../module/client.ts";
import { endpoints } from "../constants/discord.ts";
import { formatImageURL } from "../utils/cdn.ts";
import { MemberCreatePayload, EditMemberOptions } from "../types/member.ts";
import { ImageSize, ImageFormats } from "../types/cdn.ts";
import { Permission, Permissions } from "../types/permission.ts";
import { RoleData } from "../types/role.ts";
import { memberHasPermission, botHasPermission } from "../utils/permissions.ts";
import {
memberHasPermission,
botHasPermission,
highestRole,
higherRolePosition,
} from "../utils/permissions.ts";
import { Errors } from "../types/errors.ts";
import { RequestManager } from "../module/requestManager.ts";
import { botID } from "../module/client.ts";
export const createMember = (
data: MemberCreatePayload,
@@ -38,12 +43,18 @@ export const createMember = (
: endpoints.USER_DEFAULT_AVATAR(Number(data.user.discriminator) % 5),
/** Add a role to the member */
addRole: (roleID: string, reason?: string) => {
// TODO: check if the bots highest role is above this one
const botsHighestRole = highestRole(guildID, botID);
if (
!botHasPermission(guildID, botID, [Permissions.MANAGE_ROLES])
botsHighestRole &&
!higherRolePosition(guildID, botsHighestRole.id, roleID)
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
if (!botHasPermission(guildID, [Permissions.MANAGE_ROLES])) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.put(
endpoints.GUILD_MEMBER_ROLE(guildID, data.user.id, roleID),
{ reason },
@@ -51,10 +62,15 @@ export const createMember = (
},
/** Remove a role from the member */
removeRole: (roleID: string, reason?: string) => {
// TODO: check if the bots highest role is above this role
const botsHighestRole = highestRole(guildID, botID);
if (
!botHasPermission(guildID, botID, [Permissions.MANAGE_ROLES])
botsHighestRole &&
!higherRolePosition(guildID, botsHighestRole.id, roleID)
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
if (!botHasPermission(guildID, [Permissions.MANAGE_ROLES])) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.delete(
@@ -64,10 +80,16 @@ export const createMember = (
},
/** Kick a member from the server */
kick: (reason?: string) => {
// TODO: Check if the bot is above the user so it is capable of kicking
const botsHighestRole = highestRole(guildID, botID);
const membersHighestRole = highestRole(guildID, data.user.id);
if (
!botHasPermission(guildID, botID, [Permissions.KICK_MEMBERS])
botsHighestRole && membersHighestRole &&
botsHighestRole.position <= membersHighestRole.position
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
if (!botHasPermission(guildID, [Permissions.KICK_MEMBERS])) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
return RequestManager.delete(
@@ -81,14 +103,14 @@ export const createMember = (
if (options.nick.length > 32) {
throw new Error(Errors.NICKNAMES_MAX_LENGTH);
}
if (!botHasPermission(guildID, botID, [Permissions.MANAGE_NICKNAMES])) {
if (!botHasPermission(guildID, [Permissions.MANAGE_NICKNAMES])) {
throw new Error(Errors.MISSING_MANAGE_NICKNAMES);
}
}
if (
options.roles &&
!botHasPermission(guildID, botID, [Permissions.MANAGE_ROLES])
!botHasPermission(guildID, [Permissions.MANAGE_ROLES])
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
@@ -96,7 +118,7 @@ export const createMember = (
if (options.mute) {
// TODO: This should check if the member is in a voice channel
if (
!botHasPermission(guildID, botID, [Permissions.MUTE_MEMBERS])
!botHasPermission(guildID, [Permissions.MUTE_MEMBERS])
) {
throw new Error(Errors.MISSING_MUTE_MEMBERS);
}
@@ -104,7 +126,7 @@ export const createMember = (
if (
options.deaf &&
!botHasPermission(guildID, botID, [Permissions.DEAFEN_MEMBERS])
!botHasPermission(guildID, [Permissions.DEAFEN_MEMBERS])
) {
throw new Error(Errors.MISSING_DEAFEN_MEMBERS);
}

View File

@@ -19,7 +19,7 @@ export function createMessage(data: MessageCreateOptions) {
mentionsEveryone: data.mentions_everyone,
mentionRoles: data.mention_roles,
mentionChannels: data.mention_channels,
mentions: data.mentions.map(user => createUser(user)),
mentions: data.mentions.map((user) => createUser(user)),
webhookID: data.webhook_id,
messageReference: data.message_reference,
@@ -34,7 +34,7 @@ export function createMessage(data: MessageCreateOptions) {
delete: (reason?: string) => {
if (
data.guild_id &&
!botHasPermission(data.guild_id, botID, [Permissions.MANAGE_MESSAGES])
!botHasPermission(data.guild_id, [Permissions.MANAGE_MESSAGES])
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -50,16 +50,17 @@ export function createMessage(data: MessageCreateOptions) {
pin: () => {
if (
data.guild_id &&
!botHasPermission(data.guild_id, botID, [Permissions.MANAGE_MESSAGES])
!botHasPermission(data.guild_id, [Permissions.MANAGE_MESSAGES])
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
RequestManager.put(endpoints.CHANNEL_MESSAGE(data.channel_id, data.id));
},
/** Unpin a message in a channel. Requires MANAGE_MESSAGES. */
unpin: () => {
if (
data.guild_id &&
!botHasPermission(data.guild_id, botID, [Permissions.MANAGE_MESSAGES])
!botHasPermission(data.guild_id, [Permissions.MANAGE_MESSAGES])
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -91,7 +92,7 @@ export function createMessage(data: MessageCreateOptions) {
removeAllReactions: () => {
if (
data.guild_id &&
!botHasPermission(data.guild_id, botID, [Permissions.MANAGE_MESSAGES])
!botHasPermission(data.guild_id, [Permissions.MANAGE_MESSAGES])
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -103,7 +104,7 @@ export function createMessage(data: MessageCreateOptions) {
removeReactionEmoji: (reaction: string) => {
if (
data.guild_id &&
!botHasPermission(data.guild_id, botID, [Permissions.MANAGE_MESSAGES])
!botHasPermission(data.guild_id, [Permissions.MANAGE_MESSAGES])
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -130,7 +131,7 @@ export function createMessage(data: MessageCreateOptions) {
if (data.guild_id) {
if (
!botHasPermission(data.guild_id, botID, [Permissions.SEND_MESSAGES])
!botHasPermission(data.guild_id, [Permissions.SEND_MESSAGES])
) {
throw new Error(Errors.MISSING_SEND_MESSAGES);
}
@@ -139,7 +140,6 @@ export function createMessage(data: MessageCreateOptions) {
content.tts &&
!botHasPermission(
data.guild_id,
botID,
[Permissions.SEND_TTS_MESSAGES],
)
) {

View File

@@ -10,8 +10,12 @@ export interface ActivityPayload {
}
export enum ActivityType {
/** Example: "Playing Rocket League" */
Game,
/** Example: "Streaming Rocket League" */
Streaming,
/** Example: "Listening to spotify" */
Listening,
/** Example: ":smiley: I am cool" */
Custom = 4,
}

View File

@@ -92,14 +92,14 @@ export enum VoiceCloseEventCode {
export enum HttpResponseCode {
Ok = 200,
Created,
Created = 201,
NoContent = 204,
NotModified = 304,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound,
MethodNotAllowed,
NotFound = 404,
MethodNotAllowed = 405,
TooManyRequests = 429,
GatewayUnavailable = 502,
// ServerError left untyped because it's 5xx.

View File

@@ -22,5 +22,9 @@ export enum Errors {
NICKNAMES_MAX_LENGTH = "NICKNAMES_MAX_LENGTH",
PRUNE_MIN_DAYS = "PRUNE_MIN_DAYS",
RATE_LIMIT_RETRY_MAXED = "RATE_LIMIT_RETRY_MAXED",
MISSING_INTENT_GUILD_MEMBERS = "MISSING_INTENT_GUILD_MEMBERS"
MISSING_INTENT_GUILD_MEMBERS = "MISSING_INTENT_GUILD_MEMBERS",
REQUEST_CLIENT_ERROR = "REQUEST_CLIENT_ERROR",
REQUEST_SERVER_ERROR = "REQUEST_SERVER_ERROR",
REQUEST_UNKNOWN_ERROR = "REQUEST_UNKNOWN_ERROR",
BOTS_HIGHEST_ROLE_TOO_LOW = "BOTS_HIGHEST_ROLE_TOO_LOW",
}

View File

@@ -1,307 +1,307 @@
import { Emoji, StatusType } from "./discord.ts"
import { User } from "../structures/user.ts"
import { Permission } from "./permission.ts"
import { RoleData } from "./role.ts"
import { MemberCreatePayload } from "./member.ts"
import { Activity } from "./message.ts"
import { Client_Status_Payload } from "./presence.ts"
import { ChannelCreatePayload } from "./channel.ts"
import { Emoji, StatusType } from "./discord.ts";
import { User } from "../structures/user.ts";
import { Permission } from "./permission.ts";
import { RoleData } from "./role.ts";
import { MemberCreatePayload } from "./member.ts";
import { Activity } from "./message.ts";
import { Client_Status_Payload } from "./presence.ts";
import { ChannelCreatePayload } from "./channel.ts";
export interface GuildRolePayload {
/** The id of the guild */
guild_id: string
guild_id: string;
/** The role object of the role created, deleted, or updated */
role: RoleData
role: RoleData;
}
export interface GuildMemberChunkPayload {
/** The id of the guild */
guild_id: string
guild_id: string;
/** The set of guild members */
members: MemberCreatePayload[]
members: MemberCreatePayload[];
/** The chunk index in the expected chunks for this response */
chunk_index: number
chunk_index: number;
/** The total number of expected chunks for this response */
chunk_count: number
chunk_count: number;
/** if passing an invalid id, it will be found here */
not_found?: string[]
not_found?: string[];
/** if passing true, presences of the members will be here */
presences?: Presence[]
presences?: Presence[];
/** The nonce to help identify */
nonce?: string
nonce?: string;
}
export interface GuildMemberUpdatePayload {
/** The id of the guild */
guild_id: string
guild_id: string;
/** The user's role ids */
roles: string[]
roles: string[];
/** The user */
user: UserPayload
user: UserPayload;
/** The nickname of the user in the guild */
nick: string
nick: string;
/** When the user used their nitro boost on the guild. */
premium_since: string | null
premium_since: string | null;
}
export interface GuildMemberAddPayload extends MemberCreatePayload {
guild_id: string
guild_id: string;
}
export interface GuildEmojisUpdatePayload {
guild_id: string
emojis: Emoji[]
guild_id: string;
emojis: Emoji[];
}
export interface GuildBanPayload {
/** The id of the guild */
guild_id: string
guild_id: string;
/** The banned user. Not a member as you can ban users outside of your guild. */
user: UserPayload
user: UserPayload;
}
export interface GuildDeletePayload {
/** The id of the guild */
id: string
id: string;
/** Whether this guild went unavailable. */
unavailable?: boolean
unavailable?: boolean;
}
export interface CreateGuildPayload {
/** The guild id */
id: string
id: string;
/** The guild name 2-100 characters */
name: string
name: string;
/** The guild icon image hash */
icon: string | null
icon: string | null;
/** The guild splash image hash */
splash: string | null
splash: string | null;
/** The id of the owner */
owner_id: string
owner_id: string;
/** The voice region id for the guild */
region: string
region: string;
/** The afk channel id */
afk_channel_id: string | null
afk_channel_id: string | null;
/** AFK Timeout in seconds. */
afk_timeout: number
afk_timeout: number;
/** Whether this guild is embeddable (widget) */
embed_enabled?: boolean
embed_enabled?: boolean;
/** If not null, the channel id that the widge will generate an invite to. */
embed_channel_id?: string | null
embed_channel_id?: string | null;
/** The verification level required for the guild */
verification_level: number
verification_level: number;
/** The roles in the guild */
roles: RoleData[]
roles: RoleData[];
/** The custom guild emojis */
emojis: Emoji[]
emojis: Emoji[];
/** Enabled guild features */
features: Guild_Features[]
features: Guild_Features[];
/** Required MFA level for the guild */
mfa_level: number
mfa_level: number;
/** The id of the channel to which system mesages are sent */
system_channel_id: string | null
system_channel_id: string | null;
/** When this guild was joined at */
joined_at: string
joined_at: string;
/** Whether this is considered a large guild */
large: boolean
large: boolean;
/** Whether this guild is unavailable */
unavailable: boolean
unavailable: boolean;
/** Total number of members in this guild */
member_count?: number
voice_states: Voice_State[]
member_count?: number;
voice_states: Voice_State[];
/** Users in the guild */
members: MemberCreatePayload[]
members: MemberCreatePayload[];
/** Channels in the guild */
channels: ChannelCreatePayload[]
presences: Presence[]
channels: ChannelCreatePayload[];
presences: Presence[];
/** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */
max_presences?: number | null
max_presences?: number | null;
/** The maximum amount of members for the guild */
max_members?: number
max_members?: number;
/** The vanity url code for the guild */
vanity_url_code: string | null
vanity_url_code: string | null;
/** The description for the guild */
description: string | null
description: string | null;
/** The banner hash */
banner: string | null
banner: string | null;
/** The premium tier */
premium_tier: number
premium_tier: number;
/** The total number of users currently boosting this server. */
premium_subscription_count: number
premium_subscription_count: number;
/** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */
preferred_locale: string
preferred_locale: string;
}
export type Guild_Features =
| `INVITE_SPLASH`
| `VIP_REGIONS`
| `VANITY_URL`
| `VERIFIED`
| `PARTNERED`
| `PUBLIC`
| `COMMERCE`
| `NEWS`
| `DISCOVERABLE`
| `FEATURABLE`
| `ANIMATED_ICON`
| `BANNER`
| "INVITE_SPLASH"
| "VIP_REGIONS"
| "VANITY_URL"
| "VERIFIED"
| "PARTNERED"
| "PUBLIC"
| "COMMERCE"
| "NEWS"
| "DISCOVERABLE"
| "FEATURABLE"
| "ANIMATED_ICON"
| "BANNER";
export interface Voice_Region {
/** unique ID for the region */
id: string
id: string;
/** name of the region */
name: string
name: string;
/** true if this is a vip-only server */
vip: boolean
vip: boolean;
/** true for a single server that is closest to the current user's client */
optimal: boolean
optimal: boolean;
/** whether this is a deprecated voice region (avoid switching to these) */
deprecated: boolean
deprecated: boolean;
/** whether this is a custom voice region (used for events/etc) */
custom: boolean
custom: boolean;
}
export interface BanOptions {
/** number of days to delete messages for (0-7) */
delete_message_days?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
delete_message_days?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
/** The reason for the ban. */
reason?: string
reason?: string;
}
export interface BannedUser {
/** The reason for the ban */
reason?: string
reason?: string;
/** The banned user object */
user: User
user: User;
}
export interface PositionSwap {
/** The unique id */
id: string
id: string;
/** The sorting position number. */
position: number
position: number;
}
export interface GuildEditOptions {
/** The guild name */
name?: string
name?: string;
/** The guild voice region id */
region?: string
region?: string;
/** The verification level. 0 is UNRESTRICTED. 1 is Verified email. 2 is 5 minutes user. 3 is 10 minutes member in guild. 4 is verified phone number */
verification_level?: 0 | 1 | 2 | 3
verification_level?: 0 | 1 | 2 | 3;
/** The default message notification level. 0 is ALL_MESSAGES and 1 is ONLY_MENTINS */
default_message_notifications?: 0 | 1
default_message_notifications?: 0 | 1;
/** Explicit content filter level. 0 is DISABLED 1 is members without roles. 2 is all members */
explicit_content_filter?: 0 | 1 | 2
explicit_content_filter?: 0 | 1 | 2;
/** The id for the afk channel. */
afk_channel_id?: string
afk_channel_id?: string;
/** The afk timeout in seconds. */
afk_timeout?: number
afk_timeout?: number;
/** base64 1024x1024 png/jpeg/gif image for the guild icon (can be animated gif when the server has ANIMATED_ICON feature) */
icon?: string
icon?: string;
/** user id to transfer guild ownership to (must be owner) */
owner_id?: string
owner_id?: string;
/** base64 16:9 png/jpeg image for the guild splash (when the server has INVITE_SPLASH feature) */
splash?: string
splash?: string;
/** base64 16:9 png/jpeg image for the guild banner (when the server has BANNER feature) */
banner?: string
banner?: string;
/** the id of the channel to which system messages are sent */
system_channel_id?: string
system_channel_id?: string;
}
export interface EditIntegrationOptions {
/** The behavior when an integration subscription lapses. */
expire_behavior: number
expire_behavior: number;
/** The period in seconds where the integration will ignore lapsed subscriptions */
expire_grace_period: number
expire_grace_period: number;
/** Whether emoticons should be synced for this integrations (twitch only currently) */
enable_emoticons: boolean
enable_emoticons: boolean;
}
export interface Guild_Integration {
/** The integrations unique id */
id: string
id: string;
/** the integrations name */
name: string
name: string;
/** The integration type like twitch, youtube etc */
type: string
type: string;
/** Is this integration enabled */
enabled: boolean
enabled: boolean;
/** is this integration syncing */
syncing: boolean
syncing: boolean;
/** id that this integration uses for "subscribers" */
role_id: string
role_id: string;
/** The behavior of expiring subscribers */
expire_behavior: number
expire_behavior: number;
/** The grace period before expiring subscribers */
expire_grace_period: number
expire_grace_period: number;
/** The user for this integration */
user: UserPayload
user: UserPayload;
/** The integration account information */
account: Account
account: Account;
/** When this integration was last synced */
synced_at: string
synced_at: string;
}
export interface Account {
/** id of the account */
id: string
id: string;
/** name of the account */
name: string
name: string;
}
export interface UserPayload {
/** The user's id */
id: string
id: string;
/** the user's username, not unique across the platform */
username: string
username: string;
/** The user's 4 digit discord tag */
discriminator: string
discriminator: string;
/** The user's avatar hash */
avatar: string | null
avatar: string | null;
/** Whether the user is a bot */
bot?: boolean
bot?: boolean;
/** Whether the user is an official discord system user (part of the urgent message system.) */
system?: boolean
system?: boolean;
/** Whether the user has two factor enabled on their account */
mfa_enabled?: boolean
mfa_enabled?: boolean;
/** the user's chosen language option */
locale?: string
locale?: string;
/** Whether the email on this account has been verified */
verified?: boolean
verified?: boolean;
/** The user's email */
email?: string
email?: string;
/** The flags on a user's account. */
flags?: number
flags?: number;
/** The type of Nitro subscription on a user's account. */
premium_type?: number
premium_type?: number;
}
export interface Partial_User {
/** The user's id */
id: string
id: string;
/** the user's username, not unique across the platform */
username?: string
username?: string;
/** The user's 4 digit discord tag */
discriminator?: string
discriminator?: string;
/** The user's avatar hash */
avatar?: string | null
avatar?: string | null;
/** Whether the user is a bot */
bot?: boolean
bot?: boolean;
/** Whether the user is an official discord system user (part of the urgent message system.) */
system?: boolean
system?: boolean;
/** Whether the user has two factor enabled on their account */
mfa_enabled?: boolean
mfa_enabled?: boolean;
/** the user's chosen language option */
locale?: string
locale?: string;
/** Whether the email on this account has been verified */
verified?: boolean
verified?: boolean;
/** The user's email */
email?: string
email?: string;
/** The flags on a user's account. */
flags?: number
flags?: number;
/** The type of Nitro subscription on a user's account. */
premium_type?: number
premium_type?: number;
}
export enum User_Flags {
@@ -324,62 +324,62 @@ export enum Nitro_Types {
}
export interface Vanity_Invite {
code: string | null
uses: number
code: string | null;
uses: number;
}
export interface Guild_Embed {
/** Whether the embed is enbaled. */
enabled: boolean
enabled: boolean;
}
export interface GetAuditLogsOptions {
/** Filter the logs for actions made by this user. */
user_id?: string
user_id?: string;
/** The type of audit log. */
action_type?: AuditLogType
action_type?: AuditLogType;
/** Filter the logs before a certain log entry. */
before?: string
before?: string;
/** How many entries are returned. Between 1-100. Default 50. */
limit?: number
limit?: number;
}
export type AuditLogType =
| `GUILD_UPDATE`
| `CHANNEL_CREATE`
| `CHANNEL_UPDATE`
| `CHANNEL_DELETE`
| `CHANNEL_OVERWRITE_CREATE`
| `CHANNEL_OVERWRITE_UPDATE`
| `CHANNEL_OVERWRITE_DELETE`
| `MEMBER_KICK`
| `MEMBER_PRUNE`
| `MEMBER_BAN_ADD`
| `MEMBER_BAN_REMOVE`
| `MEMBER_UPDATE`
| `MEMBER_ROLE_UPDATE`
| `MEMBER_MOVE`
| `MEMBER_DISCONNECT`
| `BOT_ADD`
| `ROLE_CREATE`
| `ROLE_UPDATE`
| `ROLE_DELETE`
| `INVITE_CREATE`
| `INVITE_UPDATE`
| `INVITE_DELETE`
| `WEBHOOK_CREATE`
| `WEBHOOK_UPDATE`
| `WEBHOOK_DELETE`
| `EMOJI_CREATE`
| `EMOJI_UPDATE`
| `EMOJI_DELETE`
| `MESSAGE_DELETE`
| `MESSAGE_BULK_DELETE`
| `MESSAGE_PIN`
| `MESSAGE_UNPIN`
| `INTEGRATION_CREATE`
| `INTEGRATION_UPDATE`
| `INTEGRATION_DELETE`
| "GUILD_UPDATE"
| "CHANNEL_CREATE"
| "CHANNEL_UPDATE"
| "CHANNEL_DELETE"
| "CHANNEL_OVERWRITE_CREATE"
| "CHANNEL_OVERWRITE_UPDATE"
| "CHANNEL_OVERWRITE_DELETE"
| "MEMBER_KICK"
| "MEMBER_PRUNE"
| "MEMBER_BAN_ADD"
| "MEMBER_BAN_REMOVE"
| "MEMBER_UPDATE"
| "MEMBER_ROLE_UPDATE"
| "MEMBER_MOVE"
| "MEMBER_DISCONNECT"
| "BOT_ADD"
| "ROLE_CREATE"
| "ROLE_UPDATE"
| "ROLE_DELETE"
| "INVITE_CREATE"
| "INVITE_UPDATE"
| "INVITE_DELETE"
| "WEBHOOK_CREATE"
| "WEBHOOK_UPDATE"
| "WEBHOOK_DELETE"
| "EMOJI_CREATE"
| "EMOJI_UPDATE"
| "EMOJI_DELETE"
| "MESSAGE_DELETE"
| "MESSAGE_BULK_DELETE"
| "MESSAGE_PIN"
| "MESSAGE_UNPIN"
| "INTEGRATION_CREATE"
| "INTEGRATION_UPDATE"
| "INTEGRATION_DELETE";
export enum AuditLogs {
GUILD_UPDATE = 1,
@@ -419,128 +419,134 @@ export enum AuditLogs {
INTEGRATION_DELETE,
}
export type ChannelType = "text" | "dm" | "news" | "voice" | "category" | "store"
export type ChannelType =
| "text"
| "dm"
| "news"
| "voice"
| "category"
| "store";
export interface Overwrite {
/** The role or user id */
id: string
id: string;
/** Whether this is a role or a member */
type: "role" | "member"
type: "role" | "member";
/** The permissions that this id is allowed to do. (This will mark it as a green check.) */
allow: Permission[]
allow: Permission[];
/** The permissions that this id is NOT allowed to do. (This will mark it as a red x.) */
deny: Permission[]
deny: Permission[];
}
export interface Raw_Overwrite {
/** The role or user id */
id: string
id: string;
/** Whether this is a role or a member */
type: "role" | "member"
type: "role" | "member";
/** The permissions that this id is allowed to do. (This will mark it as a green check.) */
allow: number
allow: number;
/** The permissions that this id is NOT allowed to do. (This will mark it as a red x.) */
deny: number
deny: number;
}
export interface ChannelCreate_Options {
/** The type of the channel */
type?: ChannelType
type?: ChannelType;
/** The channel topic. (0-1024 characters) */
topic?: string
topic?: string;
/** The bitrate(in bits) of the voice channel. */
bitrate?: number
bitrate?: number;
/** The user limit of the voice channel. */
user_limit?: number
user_limit?: number;
/** The amount of seconds a user has to wait before sending another message. (0-21600 seconds). Bots, as well as users with the permission `manage_messages or manage_channel` are unaffected. */
rate_limit_per_user?: number
rate_limit_per_user?: number;
/** The sorting position of the channel */
position?: number
position?: number;
/** The channel's permission overwrites */
permission_overwrites?: Overwrite[]
permission_overwrites?: Overwrite[];
/** The id of the parent category for the channel */
parent_id?: string
parent_id?: string;
/** Whether the channel is nsfw */
nsfw?: boolean
nsfw?: boolean;
/** The reason to add in the Audit Logs. */
reason?: string
reason?: string;
}
export interface CreateEmojisOptions {
/** The roles for which this emoji will be whitelisted. Only the users with one of these roles can use this emoji. */
roles: string[]
roles: string[];
/** The reason to have in the Audit Logs. */
reason: string
reason: string;
}
export interface EditEmojisOptions {
/** The name of the emoji */
name: string
name: string;
/** The roles for which this emoji will be whitelisted. Only the users with one of these roles can use this emoji. */
roles: string[]
roles: string[];
}
export interface CreateRoleOptions {
name?: string
permissions?: Permission[]
color?: number
hoist?: boolean
mentionable?: boolean
name?: string;
permissions?: Permission[];
color?: number;
hoist?: boolean;
mentionable?: boolean;
}
export interface PrunePayload {
pruned: number
pruned: number;
}
export interface Voice_State {
/** the guild id this voice state is for */
guild_id?: string
guild_id?: string;
/** the channel id this user is connected to */
channel_id: string | null
channel_id: string | null;
/** the user id this voice state is for */
user_id: string
user_id: string;
/** the guild member this voice state is for */
member?: MemberCreatePayload
member?: MemberCreatePayload;
/** the session id for this voice state */
session_id: string
session_id: string;
/** whether this user is deafened by the server */
deaf: boolean
deaf: boolean;
/** whether this user is muted by the server */
mute: boolean
mute: boolean;
/** whether this user is locally deafened */
self_deaf: boolean
self_deaf: boolean;
/** whether this user is locally muted */
self_mute: boolean
self_mute: boolean;
/** whether this user is streaming using "Go Live" */
self_stream?: boolean
self_stream?: boolean;
/** whether this user is muted by the current user */
suppress: boolean
suppress: boolean;
}
export interface Presence {
/** The user presence is being updated for */
user: UserPayload
user: UserPayload;
/** The roles this user is in */
roles: string[]
roles: string[];
/** null, or the user's current activity */
game: Activity | null
game: Activity | null;
/** The id of the guild */
guild_id: string
guild_id: string;
/** Either idle */
status: StatusType
activities: Activity[]
client_status: Client_Status_Payload
premium_since?: string | null
nick?: string | null
status: StatusType;
activities: Activity[];
client_status: Client_Status_Payload;
premium_since?: string | null;
nick?: string | null;
}
export interface FetchMembersOptions {
/** Used to specify if you want the presences of the matched members. Default = false. */
presences?: boolean
presences?: boolean;
/** Only returns members whose username or nickname starts with this string. DO NOT INCLUDE discriminators. If a string is provided, the max amount of members that can be fetched is 100. Default = return all members. */
query?: string
query?: string;
/** Used to specify which users to fetch specifically. */
userIDs?: string[]
userIDs?: string[];
/** Maximum number of members to return that match the query. Default = 0 which will return all members. */
limit?: number
limit?: number;
}

View File

@@ -137,7 +137,7 @@ export interface Reaction {
/** Whether the current user reacted using this emoji */
me: boolean;
/** The emoji information. Can be partial. */
emoji: Emoji;
emoji: EmojiReaction;
}
export enum Message_Types {
@@ -200,7 +200,7 @@ export enum Message_Flags {
URGENT = 1 << 4,
}
export interface Emoji {
export interface EmojiReaction {
/** The emoji id. */
id?: string;
/** The emoji name. Null in reaction emoji object if emoji is no longer on the server */

View File

@@ -1,34 +1,34 @@
export type Permission =
| `CREATE_INSTANT_INVITE`
| `KICK_MEMBERS`
| `BAN_MEMBERS`
| `ADMINISTRATOR`
| `MANAGE_CHANNELS`
| `MANAGE_GUILD`
| `ADD_REACTIONS`
| `VIEW_AUDIT_LOG`
| `VIEW_CHANNEL`
| `SEND_MESSAGES`
| `SEND_TTS_MESSAGES`
| `MANAGE_MESSAGES`
| `EMBED_LINKS`
| `ATTACH_FILES`
| `READ_MESSAGE_HISTORY`
| `MENTION_EVERYONE`
| `USE_EXTERNAL_EMOJIS`
| `CONNECT`
| `SPEAK`
| `MUTE_MEMBERS`
| `DEAFEN_MEMBERS`
| `MOVE_MEMBERS`
| `USE_VAD`
| `PRIORITY_SPEAKER`
| `STREAM`
| `CHANGE_NICKNAME`
| `MANAGE_NICKNAMES`
| `MANAGE_ROLES`
| `MANAGE_WEBHOOKS`
| `MANAGE_EMOJIS`
| "CREATE_INSTANT_INVITE"
| "KICK_MEMBERS"
| "BAN_MEMBERS"
| "ADMINISTRATOR"
| "MANAGE_CHANNELS"
| "MANAGE_GUILD"
| "ADD_REACTIONS"
| "VIEW_AUDIT_LOG"
| "VIEW_CHANNEL"
| "SEND_MESSAGES"
| "SEND_TTS_MESSAGES"
| "MANAGE_MESSAGES"
| "EMBED_LINKS"
| "ATTACH_FILES"
| "READ_MESSAGE_HISTORY"
| "MENTION_EVERYONE"
| "USE_EXTERNAL_EMOJIS"
| "CONNECT"
| "SPEAK"
| "MUTE_MEMBERS"
| "DEAFEN_MEMBERS"
| "MOVE_MEMBERS"
| "USE_VAD"
| "PRIORITY_SPEAKER"
| "STREAM"
| "CHANGE_NICKNAME"
| "MANAGE_NICKNAMES"
| "MANAGE_ROLES"
| "MANAGE_WEBHOOKS"
| "MANAGE_EMOJIS";
export enum Permissions {
CREATE_INSTANT_INVITE = 0x00000001,
@@ -60,5 +60,5 @@ export enum Permissions {
MANAGE_NICKNAMES = 0x08000000,
MANAGE_ROLES = 0x10000000,
MANAGE_WEBHOOKS = 0x20000000,
MANAGE_EMOJIS = 0x40000000
MANAGE_EMOJIS = 0x40000000,
}

View File

@@ -5,7 +5,5 @@ export const formatImageURL = (
size: ImageSize = 128,
format?: ImageFormats,
) => {
return `${url}.${
format || url.includes("/a_") ? "gif" : "jpg"
}/?size=${size}`;
return `${url}.${format || url.includes("/a_") ? "gif" : "jpg"}?size=${size}`;
};

View File

@@ -1,14 +1,16 @@
import { Permission, Permissions } from "../types/permission.ts";
import { RoleData } from "../types/role.ts";
import { cache } from "./cache.ts";
import { botID } from "../module/client.ts";
import { Role } from "../structures/role.ts";
export const memberHasPermission = (
export function memberHasPermission(
memberID: string,
ownerID: string,
roleData: RoleData[],
memberRoleIDs: string[],
permissions: Permission[],
) => {
) {
if (memberID === ownerID) return true;
const permissionBits = roleData
@@ -24,13 +26,9 @@ export const memberHasPermission = (
return permissions.every((permission) =>
permissionBits & Permissions[permission]
);
};
}
export const botHasPermission = (
guildID: string,
botID: string,
permissions: Permissions[],
) => {
export function botHasPermission(guildID: string, permissions: Permissions[]) {
const guild = cache.guilds.get(guildID);
if (!guild) return false;
@@ -49,10 +47,48 @@ export const botHasPermission = (
if (permissionBits & Permissions.ADMINISTRATOR) return true;
return permissions.every((permission) => permissionBits & permission);
};
}
export const calculatePermissions = (permissionBits: number) => {
export function calculatePermissions(permissionBits: number) {
return Object.keys(Permissions).filter((perm) => {
return permissionBits & Permissions[perm as Permission];
});
};
}
export function highestRole(guildID: string, memberID: string) {
const guild = cache.guilds.get(guildID);
if (!guild) return;
const member = guild?.members.get(memberID);
if (!member) return;
let memberHighestRole: Role | undefined;
for (const roleID of member.roles) {
const role = guild.roles.get(roleID);
if (!role) continue;
if (
!memberHighestRole || memberHighestRole.position < role.position
) {
memberHighestRole = role;
}
}
return memberHighestRole || (guild.roles.get(guild.id) as Role);
}
export function higherRolePosition(
guildID: string,
roleID: string,
otherRoleID: string,
) {
const guild = cache.guilds.get(guildID);
if (!guild) return;
const role = guild.roles.get(roleID);
const otherRole = guild.roles.get(otherRoleID);
if (!role || !otherRole) return;
return role.position > otherRole.position;
}

View File

@@ -1,3 +1,15 @@
import { StatusType } from "../types/discord.ts";
import { ActivityType } from "../types/activity.ts";
import { sendGatewayCommand } from "../module/shardingManager.ts";
export const sleep = (timeout: number) => {
return new Promise((resolve) => setTimeout(resolve, timeout));
};
export function editBotsStatus(
status: StatusType,
name?: string,
type = ActivityType.Game,
) {
sendGatewayCommand("EDIT_BOTS_STATUS", { status, game: { name, type } });
}