mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-02 17:00:08 +00:00
some utils converted
This commit is contained in:
78
src/bot.ts
78
src/bot.ts
@@ -12,7 +12,7 @@ import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts";
|
||||
import { GetGatewayBot } from "./types/gateway/get_gateway_bot.ts";
|
||||
import { dispatchRequirements } from "./util/dispatch_requirements.ts";
|
||||
import { processQueue } from "./rest/process_queue.ts";
|
||||
import { snowflakeToBigint } from "./util/bigint.ts";
|
||||
import { bigintToSnowflake, snowflakeToBigint } from "./util/bigint.ts";
|
||||
import { Collection } from "./util/collection.ts";
|
||||
import { DiscordenoUser, transformMember, transformUser } from "./transformers/member.ts";
|
||||
import { SnakeCasedPropertiesDeep } from "./types/util.ts";
|
||||
@@ -36,31 +36,50 @@ import { handleOnMessage } from "./ws/handle_on_message.ts";
|
||||
import { closeWS } from "./ws/close_ws.ts";
|
||||
import { sendShardMessage } from "./ws/send_shard_message.ts";
|
||||
import { resume } from "./ws/resume.ts";
|
||||
import { calculateShardId } from "./util/calculate_shard_id.ts";
|
||||
import {
|
||||
baseEndpoints,
|
||||
CHANNEL_MENTION_REGEX,
|
||||
CONTEXT_MENU_COMMANDS_NAME_REGEX,
|
||||
DISCORDENO_VERSION,
|
||||
DISCORD_SNOWFLAKE_REGEX,
|
||||
endpoints,
|
||||
SLASH_COMMANDS_NAME_REGEX,
|
||||
USER_AGENT,
|
||||
} from "./util/constants.ts";
|
||||
import { GatewayPayload } from "./types/gateway/gateway_payload.ts";
|
||||
import { delay } from "./util/utils.ts";
|
||||
import { iconBigintToHash, iconHashToBigInt } from "./util/hash.ts";
|
||||
import { loopObject } from "./util/loop_object.ts";
|
||||
|
||||
export async function createBot(options: CreateBotOptions) {
|
||||
return {
|
||||
id: options.botId,
|
||||
applicationId: options.applicationId || options.botId,
|
||||
token: `Bot ${options.token}`,
|
||||
events: { dispatchRequirements: dispatchRequirements, ...options.events },
|
||||
events: options.events,
|
||||
intents: options.intents.reduce((bits, next) => (bits |= DiscordGatewayIntents[next]), 0),
|
||||
botGatewayData: options.botGatewayData || (await getGatewayBot()),
|
||||
isReady: false,
|
||||
activeGuildIds: new Set<bigint>(),
|
||||
constants: createBotConstants(),
|
||||
};
|
||||
}
|
||||
|
||||
const bot = await createBot({
|
||||
token: "",
|
||||
botId: 0n,
|
||||
events: createEventHandlers(),
|
||||
events: createEventHandlers({}),
|
||||
intents: [],
|
||||
});
|
||||
|
||||
export function createEventHandlers(options?: Partial<EventHandlers>) {
|
||||
export function createEventHandlers(events: Partial<EventHandlers>): EventHandlers {
|
||||
function ignore() {}
|
||||
|
||||
return {
|
||||
debug: () => undefined,
|
||||
// PROVIDED OPTIONS OVERRIDE EVERYTHING ABOVE
|
||||
...options,
|
||||
channelCreate: events.channelCreate ?? ignore,
|
||||
debug: events.debug ?? ignore,
|
||||
dispatchRequirements: events.dispatchRequirements ?? ignore,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,6 +133,9 @@ export function createRestManager(options: CreateRestManagerOptions) {
|
||||
export async function startBot(bot: Bot) {
|
||||
const transformers = createTransformers(bot.transformers);
|
||||
|
||||
// SETUP UTILS
|
||||
bot.utils = createUtils({});
|
||||
|
||||
// SETUP CACHE
|
||||
bot.users = new Collection();
|
||||
|
||||
@@ -124,6 +146,28 @@ export async function startBot(bot: Bot) {
|
||||
bot.gateway = createGatewayManager({});
|
||||
}
|
||||
|
||||
export function createUtils(options: Partial<HelperUtils>) {
|
||||
return {
|
||||
snowflakeToBigint,
|
||||
bigintToSnowflake,
|
||||
calculateShardId,
|
||||
delay,
|
||||
iconHashToBigInt,
|
||||
iconBigintToHash,
|
||||
loopObject,
|
||||
};
|
||||
}
|
||||
|
||||
export interface HelperUtils {
|
||||
snowflakeToBigint: typeof snowflakeToBigint;
|
||||
bigintToSnowflake: typeof bigintToSnowflake;
|
||||
calculateShardId: typeof calculateShardId;
|
||||
delay: typeof delay;
|
||||
iconHashToBigInt: typeof iconHashToBigInt;
|
||||
iconBigintToHash: typeof iconBigintToHash;
|
||||
loopObject: typeof loopObject;
|
||||
}
|
||||
|
||||
export function createGatewayManager(options: Partial<GatewayManager>): GatewayManager {
|
||||
return {
|
||||
secretKey: options.secretKey ?? "",
|
||||
@@ -181,7 +225,7 @@ export interface CreateBotOptions {
|
||||
token: string;
|
||||
botId: bigint;
|
||||
applicationId?: bigint;
|
||||
events: Partial<EventHandlers>;
|
||||
events: EventHandlers;
|
||||
intents: (keyof typeof DiscordGatewayIntents)[];
|
||||
botGatewayData?: GetGatewayBot;
|
||||
rest?: Omit<CreateRestManagerOptions, "token">;
|
||||
@@ -192,6 +236,7 @@ export type UnPromise<T extends Promise<unknown>> = T extends Promise<infer K> ?
|
||||
export type CreatedBot = UnPromise<ReturnType<typeof createBot>>;
|
||||
|
||||
export type Bot = CreatedBot & {
|
||||
utils: HelperUtils;
|
||||
rest: RestManager;
|
||||
gateway: GatewayManager;
|
||||
transformers: Transformers;
|
||||
@@ -326,4 +371,21 @@ export interface GatewayManager {
|
||||
export interface EventHandlers {
|
||||
debug: (text: string) => unknown;
|
||||
channelCreate: (bot: Bot, channel: DiscordenoChannel) => unknown;
|
||||
dispatchRequirements: (bot: Bot, data: GatewayPayload, shardId: number) => Promise<unknown> | unknown;
|
||||
}
|
||||
|
||||
export function createBotConstants() {
|
||||
return {
|
||||
DISCORDENO_VERSION,
|
||||
USER_AGENT,
|
||||
BASE_URL: baseEndpoints.BASE_URL,
|
||||
CDN_URL: baseEndpoints.CDN_URL,
|
||||
endpoints,
|
||||
regexes: {
|
||||
SLASH_COMMANDS_NAME_REGEX,
|
||||
CONTEXT_MENU_COMMANDS_NAME_REGEX,
|
||||
CHANNEL_MENTION_REGEX,
|
||||
DISCORD_SNOWFLAKE_REGEX,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,30 +1,25 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { Errors } from "../../types/discordeno/errors.ts";
|
||||
import { Bot } from "../../bot.ts";
|
||||
import type { CreateWebhook } from "../../types/webhooks/create_webhook.ts";
|
||||
import type { Webhook } from "../../types/webhooks/webhook.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotChannelPermissions } from "../../util/permissions.ts";
|
||||
import { urlToBase64 } from "../../util/utils.ts";
|
||||
import { validateLength } from "../../util/validate_length.ts";
|
||||
|
||||
/**
|
||||
* Create a new webhook. Requires the MANAGE_WEBHOOKS permission. Returns a webhook object on success. Webhook names follow our naming restrictions that can be found in our Usernames and Nicknames documentation, with the following additional stipulations:
|
||||
*
|
||||
* Webhook names cannot be: 'clyde'
|
||||
*/
|
||||
export async function createWebhook(channelId: bigint, options: CreateWebhook) {
|
||||
await requireBotChannelPermissions(channelId, ["MANAGE_WEBHOOKS"]);
|
||||
export async function createWebhook(bot: Bot, channelId: bigint, options: CreateWebhook) {
|
||||
await bot.utils.requireBotChannelPermissions(channelId, ["MANAGE_WEBHOOKS"]);
|
||||
|
||||
if (
|
||||
// Specific usernames that discord does not allow
|
||||
options.name === "clyde" ||
|
||||
!validateLength(options.name, { min: 2, max: 32 })
|
||||
!bot.utils.validateLength(options.name, { min: 2, max: 32 })
|
||||
) {
|
||||
throw new Error(Errors.INVALID_WEBHOOK_NAME);
|
||||
throw new Error(bot.constants.Errors.INVALID_WEBHOOK_NAME);
|
||||
}
|
||||
|
||||
return await rest.runMethod<Webhook>("post", endpoints.CHANNEL_WEBHOOKS(channelId), {
|
||||
return await bot.rest.runMethod<Webhook>(bot.rest, "post", bot.constants.endpoints.CHANNEL_WEBHOOKS(channelId), {
|
||||
...options,
|
||||
avatar: options.avatar ? await urlToBase64(options.avatar) : undefined,
|
||||
avatar: options.avatar ? await bot.utils.urlToBase64(options.avatar) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { eventHandlers } from "../bot.ts";
|
||||
import { cacheHandlers } from "../cache.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { GuildMemberWithUser } from "../types/members/guild_member.ts";
|
||||
|
||||
const guildMemberQueue = new Map<bigint, { members: GuildMemberWithUser[]; resolve?: (value?: unknown) => void }>();
|
||||
let processingQueue = false;
|
||||
|
||||
/** Cache all guild members without need to worry about overwriting something. */
|
||||
// deno-lint-ignore require-await
|
||||
export async function cacheMembers(guildId: bigint, members: GuildMemberWithUser[]) {
|
||||
if (!members.length) return;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
guildMemberQueue.set(guildId, { members, resolve });
|
||||
startQueue();
|
||||
});
|
||||
}
|
||||
|
||||
async function startQueue() {
|
||||
if (processingQueue) return;
|
||||
|
||||
processingQueue = true;
|
||||
|
||||
while (guildMemberQueue.size) {
|
||||
eventHandlers.debug?.("loop", "Running whille loop in cache_members file.");
|
||||
const [guildId, queue]: [bigint, { members: GuildMemberWithUser[]; resolve: (value?: unknown) => void }] =
|
||||
guildMemberQueue.entries().next().value;
|
||||
|
||||
await Promise.allSettled([
|
||||
queue.members.map(async (member) => {
|
||||
const discordenoMember = await structures.createDiscordenoMember(member, guildId);
|
||||
|
||||
await cacheHandlers.set("members", discordenoMember.id, discordenoMember);
|
||||
}),
|
||||
]);
|
||||
|
||||
queue.resolve?.();
|
||||
|
||||
guildMemberQueue.delete(guildId);
|
||||
}
|
||||
|
||||
processingQueue = false;
|
||||
}
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Bot } from "../bot.ts";
|
||||
import { cache } from "../cache.ts";
|
||||
import { getChannels } from "../helpers/channels/get_channels.ts";
|
||||
import { getGuild } from "../helpers/guilds/get_guild.ts";
|
||||
import { getMember } from "../helpers/members/get_member.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import type { DiscordGatewayPayload } from "../types/gateway/gateway_payload.ts";
|
||||
import type { Guild } from "../types/guilds/guild.ts";
|
||||
import { snowflakeToBigint } from "./bigint.ts";
|
||||
import { delay } from "./utils.ts";
|
||||
import { SnakeCasedPropertiesDeep } from "../types/util.ts";
|
||||
|
||||
const processing = new Set<bigint>();
|
||||
|
||||
@@ -17,7 +11,7 @@ export async function dispatchRequirements(bot: Bot, data: DiscordGatewayPayload
|
||||
// DELETE MEANS WE DONT NEED TO FETCH. CREATE SHOULD HAVE DATA TO CACHE
|
||||
if (data.t && ["GUILD_CREATE", "GUILD_DELETE"].includes(data.t)) return;
|
||||
|
||||
const id = snowflakeToBigint(
|
||||
const id = bot.utils.snowflakeToBigint(
|
||||
(data.t && ["GUILD_UPDATE"].includes(data.t)
|
||||
? // deno-lint-ignore no-explicit-any
|
||||
(data.d as any)?.id
|
||||
@@ -25,11 +19,11 @@ export async function dispatchRequirements(bot: Bot, data: DiscordGatewayPayload
|
||||
(data.d as any)?.guild_id) ?? ""
|
||||
);
|
||||
|
||||
if (!id || cache.activeGuildIds.has(id)) return;
|
||||
if (!id || bot.activeGuildIds.has(id)) return;
|
||||
|
||||
// If this guild is in cache, it has not been swept and we can cancel
|
||||
if (cache.guilds.has(id)) {
|
||||
cache.activeGuildIds.add(id);
|
||||
if (bot.guilds.has(id)) {
|
||||
bot.activeGuildIds.add(id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -38,7 +32,7 @@ export async function dispatchRequirements(bot: Bot, data: DiscordGatewayPayload
|
||||
|
||||
let runs = 0;
|
||||
do {
|
||||
await delay(500);
|
||||
await bot.utils.delay(500);
|
||||
runs++;
|
||||
} while (processing.has(id) && runs < 40);
|
||||
|
||||
@@ -54,10 +48,12 @@ export async function dispatchRequirements(bot: Bot, data: DiscordGatewayPayload
|
||||
// New guild id has appeared, fetch all relevant data
|
||||
bot.events.debug(`[DISPATCH] New Guild ID has appeared: ${id} in ${data.t} event`);
|
||||
|
||||
const rawGuild = (await getGuild(id, {
|
||||
counts: true,
|
||||
addToCache: false,
|
||||
}).catch(console.log)) as Guild | undefined;
|
||||
const rawGuild = (await bot.helpers
|
||||
.getGuild(id, {
|
||||
counts: true,
|
||||
addToCache: false,
|
||||
})
|
||||
.catch(console.log)) as SnakeCasedPropertiesDeep<Guild> | undefined;
|
||||
|
||||
if (!rawGuild) {
|
||||
processing.delete(id);
|
||||
@@ -67,8 +63,8 @@ export async function dispatchRequirements(bot: Bot, data: DiscordGatewayPayload
|
||||
bot.events.debug(`[DISPATCH] Guild ID ${id} has been found. ${rawGuild.name}`);
|
||||
|
||||
const [channels, botMember] = await Promise.all([
|
||||
getChannels(id, false),
|
||||
getMember(id, bot.id, { force: true }),
|
||||
bot.helpers.getChannels(id, false),
|
||||
bot.helpers.getMember(id, bot.id, { force: true }),
|
||||
]).catch((error) => {
|
||||
bot.events.debug(error);
|
||||
return [];
|
||||
@@ -81,17 +77,18 @@ export async function dispatchRequirements(bot: Bot, data: DiscordGatewayPayload
|
||||
);
|
||||
}
|
||||
|
||||
const guild = await structures.createDiscordenoGuild(
|
||||
{ ...rawGuild, memberCount: rawGuild.approximateMemberCount },
|
||||
shardId
|
||||
);
|
||||
const guild = await bot.transformers.guild(bot, {
|
||||
...rawGuild,
|
||||
member_count: rawGuild.approximateMemberCount,
|
||||
shardId,
|
||||
});
|
||||
|
||||
// Add to cache
|
||||
cache.guilds.set(id, guild);
|
||||
cache.dispatchedGuildIds.delete(id);
|
||||
bot.guilds.set(id, guild);
|
||||
bot.dispatchedGuildIds.delete(id);
|
||||
channels.forEach((channel) => {
|
||||
cache.dispatchedChannelIds.delete(channel.id);
|
||||
cache.channels.set(channel.id, channel);
|
||||
bot.dispatchedChannelIds.delete(channel.id);
|
||||
bot.channels.set(channel.id, channel);
|
||||
});
|
||||
|
||||
processing.delete(id);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { eventHandlers } from "../bot.ts";
|
||||
import { Bot } from "../bot.ts";
|
||||
|
||||
export function loopObject<T = {}>(obj: {}, handler: (value: unknown, key: string) => unknown, log: string) {
|
||||
export function loopObject<T = {}>(bot: Bot, obj: {}, handler: (value: unknown, key: string) => unknown, log: string) {
|
||||
let res: Record<string, unknown> | unknown[] = {};
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
@@ -9,18 +9,21 @@ export function loopObject<T = {}>(obj: {}, handler: (value: unknown, key: strin
|
||||
for (const o of obj) {
|
||||
if (typeof o === "object" && !Array.isArray(o) && o !== null) {
|
||||
// A nested object
|
||||
res.push(loopObject(o as {}, handler, log));
|
||||
res.push(loopObject(bot, o as {}, handler, log));
|
||||
} else {
|
||||
res.push(handler(o, "array"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
eventHandlers.debug?.("loop", log);
|
||||
bot.events.debug(log);
|
||||
|
||||
if (typeof value === "object" && !Array.isArray(value) && value !== null && !(value instanceof Blob)) {
|
||||
if (
|
||||
Array.isArray(value) ||
|
||||
(typeof value === "object" && !Array.isArray(value) && value !== null && !(value instanceof Blob))
|
||||
) {
|
||||
// A nested object
|
||||
res[key] = loopObject(value as {}, handler, log);
|
||||
res[key] = loopObject(bot, value as {}, handler, log);
|
||||
} else {
|
||||
res[key] = handler(value, key);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user