mirror of
https://github.com/discordeno/discordeno.git
synced 2026-09-17 08:47:22 +00:00
sharding bots starting up now
This commit is contained in:
+30
-30
@@ -1,20 +1,20 @@
|
||||
import { endpoints } from "../constants/discord.ts"
|
||||
import { DiscordBotGatewayData } from "../types/discord.ts"
|
||||
import { ClientOptions, EventHandlers } from "../types/options.ts"
|
||||
import { RequestManager } from "./requestManager.ts"
|
||||
import { Channel } from "../structures/channel.ts"
|
||||
import { spawnShards } from "./shardingManager.ts"
|
||||
import { cache } from "../utils/cache.ts"
|
||||
import { endpoints } from "../constants/discord.ts";
|
||||
import { DiscordBotGatewayData } from "../types/discord.ts";
|
||||
import { ClientOptions, EventHandlers } from "../types/options.ts";
|
||||
import { RequestManager } from "./requestManager.ts";
|
||||
import { Channel } from "../structures/channel.ts";
|
||||
import { spawnShards } from "./shardingManager.ts";
|
||||
import { cache } from "../utils/cache.ts";
|
||||
// import { connectWebSocket } from "https://deno.land/std@0.50.0/ws/mod.ts";
|
||||
|
||||
export let authorization = ""
|
||||
export let botID = ""
|
||||
export let authorization = "";
|
||||
export let botID = "";
|
||||
/** The bot's token. This should never be used by end users. It is meant to be used internally to make requests to the Discord API. */
|
||||
export let token = ""
|
||||
export let token = "";
|
||||
|
||||
export let eventHandlers: EventHandlers = {}
|
||||
|
||||
export let botGatewayData: DiscordBotGatewayData
|
||||
export let eventHandlers: EventHandlers = {};
|
||||
|
||||
export let botGatewayData: DiscordBotGatewayData;
|
||||
|
||||
export const identifyPayload = {
|
||||
token: "",
|
||||
@@ -25,29 +25,29 @@ export const identifyPayload = {
|
||||
$device: "Discordeno",
|
||||
},
|
||||
intents: 0,
|
||||
shards: [0, 0],
|
||||
}
|
||||
shard: [0, 0],
|
||||
};
|
||||
|
||||
export const createClient = async (data: ClientOptions) => {
|
||||
// Assign some defaults to the options to make them fulfilled / not annoying to use.
|
||||
botID = data.botID
|
||||
token = data.token
|
||||
if (data.eventHandlers) eventHandlers = data.eventHandlers
|
||||
authorization = `Bot ${data.token}`
|
||||
botID = data.botID;
|
||||
token = data.token;
|
||||
if (data.eventHandlers) eventHandlers = data.eventHandlers;
|
||||
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);
|
||||
|
||||
identifyPayload.token = data.token
|
||||
identifyPayload.intents = data.intents.reduce((bits, next) => (bits |= next), 0)
|
||||
identifyPayload.token = data.token;
|
||||
identifyPayload.intents = data.intents.reduce(
|
||||
(bits, next) => (bits |= next),
|
||||
0,
|
||||
);
|
||||
|
||||
spawnShards(botGatewayData, identifyPayload)
|
||||
}
|
||||
spawnShards(botGatewayData, identifyPayload);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default createClient
|
||||
export default createClient;
|
||||
|
||||
export const updateChannelCache = (key: string, value: Channel) => {
|
||||
cache.channels.set(key, value)
|
||||
}
|
||||
cache.channels.set(key, value);
|
||||
};
|
||||
|
||||
+16
-11
@@ -1,17 +1,22 @@
|
||||
import { WebSocket } from "https://deno.land/std@v1.0.0-rc1/ws/mod.ts"
|
||||
import { GatewayOpcode } from "../types/discord.ts"
|
||||
import { delay } from "https://deno.land/std@v1.0.0-rc1/util/async.ts"
|
||||
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
|
||||
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 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
|
||||
}
|
||||
previousSequenceNumber = sequence;
|
||||
};
|
||||
|
||||
+57
-47
@@ -1,108 +1,118 @@
|
||||
import { RequestMethod } from "../types/fetch.ts"
|
||||
import { authorization } from "./client.ts"
|
||||
import { sleep } from "../utils/utils.ts"
|
||||
import { RequestMethod } from "../types/fetch.ts";
|
||||
import { authorization } from "./client.ts";
|
||||
import { sleep } from "../utils/utils.ts";
|
||||
|
||||
const ratelimitedPaths = new Map<string, RateLimitedPath>()
|
||||
const ratelimitedPaths = new Map<string, RateLimitedPath>();
|
||||
|
||||
export interface RateLimitedPath {
|
||||
url: string
|
||||
resetTimestamp: number
|
||||
url: string;
|
||||
resetTimestamp: number;
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
const now = Date.now()
|
||||
const now = Date.now();
|
||||
ratelimitedPaths.forEach((value, key) => {
|
||||
if (value.resetTimestamp > now) return
|
||||
ratelimitedPaths.delete(key)
|
||||
})
|
||||
}, 1000)
|
||||
if (value.resetTimestamp > now) return;
|
||||
ratelimitedPaths.delete(key);
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
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)
|
||||
await checkRatelimits(url);
|
||||
|
||||
return result.json()
|
||||
const result = await fetch(url, createRequestBody(body));
|
||||
processHeaders(url, result.headers);
|
||||
|
||||
return result.json();
|
||||
},
|
||||
post: (url: string, body?: unknown) => {
|
||||
return runMethod(RequestMethod.Post, url, body)
|
||||
return runMethod(RequestMethod.Post, url, body);
|
||||
},
|
||||
delete: (url: string, body?: unknown) => {
|
||||
return runMethod(RequestMethod.Delete, url, body)
|
||||
return runMethod(RequestMethod.Delete, url, body);
|
||||
},
|
||||
patch: (url: string, body?: unknown) => {
|
||||
return runMethod(RequestMethod.Patch, url, body)
|
||||
return runMethod(RequestMethod.Patch, url, body);
|
||||
},
|
||||
put: (url: string, body?: unknown) => {
|
||||
return runMethod(RequestMethod.Put, url, body)
|
||||
return runMethod(RequestMethod.Put, url, body);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const createRequestBody = (body: any, method?: RequestMethod) => {
|
||||
return {
|
||||
headers: {
|
||||
Authorization: authorization,
|
||||
"User-Agent": `DiscordBot (https://github.com/skillz4killz/discordeno, 0.0.1)`,
|
||||
"User-Agent":
|
||||
`DiscordBot (https://github.com/skillz4killz/discordeno, 0.0.1)`,
|
||||
"Content-Type": "application/json",
|
||||
"X-Audit-Log-Reason": body ? encodeURIComponent(body.reason) : "",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
method: method?.toUpperCase(),
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const runMethod = async (method: RequestMethod, url: string, body?: unknown) => {
|
||||
await checkRatelimits(url)
|
||||
const response = await fetch(url, createRequestBody(body, method))
|
||||
processHeaders(url, response.headers)
|
||||
const runMethod = async (
|
||||
method: RequestMethod,
|
||||
url: string,
|
||||
body?: unknown,
|
||||
) => {
|
||||
await checkRatelimits(url);
|
||||
const response = await fetch(url, createRequestBody(body, method));
|
||||
processHeaders(url, response.headers);
|
||||
|
||||
// Sometimes Discord returns an empty 204 response that can't be made to JSON.
|
||||
if (response.status === 204) return
|
||||
if (response.status === 204) return;
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const checkRatelimits = async (url: string) => {
|
||||
const ratelimited = ratelimitedPaths.get(url)
|
||||
const global = ratelimitedPaths.get("global")
|
||||
const ratelimited = ratelimitedPaths.get(url);
|
||||
const global = ratelimitedPaths.get("global");
|
||||
|
||||
const now = Date.now()
|
||||
if (ratelimited && now < ratelimited.resetTimestamp) await sleep(now - ratelimited.resetTimestamp)
|
||||
if (global && now < global.resetTimestamp) await sleep(now - global.resetTimestamp)
|
||||
}
|
||||
const now = Date.now();
|
||||
if (ratelimited && now < ratelimited.resetTimestamp) {
|
||||
await sleep(now - ratelimited.resetTimestamp);
|
||||
}
|
||||
if (global && now < global.resetTimestamp) {
|
||||
await sleep(now - global.resetTimestamp);
|
||||
}
|
||||
};
|
||||
|
||||
const processHeaders = (url: string, headers: Headers) => {
|
||||
// If a rate limit response is encountered this will become true and returned
|
||||
let ratelimited = false
|
||||
let ratelimited = false;
|
||||
|
||||
// Get all useful headers
|
||||
const remaining = headers.get("x-ratelimit-remaining")
|
||||
const resetTimestamp = headers.get("x-ratelimit-reset")
|
||||
const retryAfter = headers.get("retry-after")
|
||||
const global = headers.get("x-ratelimit-global")
|
||||
const remaining = headers.get("x-ratelimit-remaining");
|
||||
const resetTimestamp = headers.get("x-ratelimit-reset");
|
||||
const retryAfter = headers.get("retry-after");
|
||||
const global = headers.get("x-ratelimit-global");
|
||||
|
||||
// If there is no remaining rate limit for this endpoint, we save it in cache
|
||||
if (remaining && remaining === "0") {
|
||||
ratelimited = true
|
||||
ratelimited = true;
|
||||
|
||||
ratelimitedPaths.set(url, {
|
||||
url,
|
||||
resetTimestamp: Number(resetTimestamp),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// If there is no remaining global limit, we save it in cache
|
||||
if (global) {
|
||||
ratelimited = true
|
||||
ratelimited = true;
|
||||
|
||||
ratelimitedPaths.set("global", {
|
||||
url: "global",
|
||||
resetTimestamp: Date.now() + Number(retryAfter),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a boolean to check if we need to request again once the rate limit resets
|
||||
return ratelimited
|
||||
}
|
||||
return ratelimited;
|
||||
};
|
||||
|
||||
+32
-17
@@ -1,20 +1,35 @@
|
||||
import { connectWebSocket, isWebSocketCloseEvent } from "https://deno.land/std@v1.0.0-rc1/ws/mod.ts"
|
||||
import { botGatewayData, identifyPayload } from "./client.ts"
|
||||
import { GatewayOpcode } from "../types/discord.ts"
|
||||
import { logRed } from "../utils/logger.ts"
|
||||
import { handleDiscordPayload, resumeConnection } from "./shardingManager.ts"
|
||||
import {
|
||||
connectWebSocket,
|
||||
isWebSocketCloseEvent,
|
||||
} from "https://deno.land/std@0.50.0/ws/mod.ts";
|
||||
import { GatewayOpcode, DiscordBotGatewayData } from "../types/discord.ts";
|
||||
import { logRed } from "../utils/logger.ts";
|
||||
import { handleDiscordPayload, resumeConnection } from "./shardingManager.ts";
|
||||
|
||||
let shardSocket = await connectWebSocket(botGatewayData.url)
|
||||
let resumeInterval = 0
|
||||
export const createShard = async (
|
||||
botGatewayData: DiscordBotGatewayData,
|
||||
identifyPayload: object,
|
||||
) => {
|
||||
const shardSocket = await connectWebSocket(botGatewayData.url);
|
||||
let resumeInterval = 0;
|
||||
|
||||
// Intial identify with the gateway
|
||||
await shardSocket.send(JSON.stringify({ op: GatewayOpcode.Identify, d: identifyPayload }))
|
||||
|
||||
for await (const message of shardSocket) {
|
||||
if (typeof message === "string") {
|
||||
handleDiscordPayload(JSON.parse(message), shardSocket, resumeInterval)
|
||||
} else if (isWebSocketCloseEvent(message)) {
|
||||
logRed(`Close :( ${JSON.stringify(message)}`)
|
||||
resumeInterval = await resumeConnection(identifyPayload, shardSocket)
|
||||
// Intial identify with the gateway
|
||||
await shardSocket.send(
|
||||
JSON.stringify({ op: GatewayOpcode.Identify, d: identifyPayload }),
|
||||
);
|
||||
for await (const message of shardSocket) {
|
||||
if (typeof message === "string") {
|
||||
handleDiscordPayload(JSON.parse(message), shardSocket, resumeInterval);
|
||||
} else if (isWebSocketCloseEvent(message)) {
|
||||
logRed(`Close :( ${JSON.stringify(message)}`);
|
||||
resumeInterval = await resumeConnection(identifyPayload, shardSocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
postMessage({ type: "REQUEST_CLIENT_OPTIONS" });
|
||||
onmessage = (message) => {
|
||||
if (message.data.type === "CREATE_SHARD") {
|
||||
createShard(message.data.botGatewayData, message.data.identifyPayload);
|
||||
}
|
||||
};
|
||||
|
||||
+295
-193
@@ -8,202 +8,266 @@ import {
|
||||
TypingStartPayload,
|
||||
VoiceStateUpdatePayload,
|
||||
WebhookUpdatePayload,
|
||||
} from "../types/discord.ts"
|
||||
import { eventHandlers, botID, botGatewayData, identifyPayload } from "./client.ts"
|
||||
import { updatePreviousSequenceNumber, sendConstantHeartbeats, previousSequenceNumber } from "./gateway.ts"
|
||||
import { WebSocket, connectWebSocket } from "https://deno.land/std@v1.0.0-rc1/ws/mod.ts"
|
||||
import { handleInternalChannelCreate } from "../events/channels.ts"
|
||||
import { handleInternalChannelUpdate } from "../events/channels.ts"
|
||||
import { handleInternalChannelDelete } from "../events/channels.ts"
|
||||
import { createGuild } from "../structures/guild.ts"
|
||||
import { handleInternalGuildCreate } from "../events/guilds.ts"
|
||||
import { cache } from "../utils/cache.ts"
|
||||
import { handleInternalGuildUpdate } from "../events/guilds.ts"
|
||||
import { CreateGuildPayload, GuildMemberChunkPayload, GuildRolePayload, UserPayload } from "../types/guild.ts"
|
||||
import { GuildDeletePayload } from "../types/guild.ts"
|
||||
import { handleInternalGuildDelete } from "../events/guilds.ts"
|
||||
import { GuildBanPayload } from "../types/guild.ts"
|
||||
import { createUser } from "../structures/user.ts"
|
||||
import { GuildEmojisUpdatePayload } from "../types/guild.ts"
|
||||
import { GuildMemberAddPayload } from "../types/guild.ts"
|
||||
import { createMember } from "../structures/member.ts"
|
||||
import { GuildMemberUpdatePayload } from "../types/guild.ts"
|
||||
import { ChannelCreatePayload } from "../types/channel.ts"
|
||||
import { createRole } from "../structures/role.ts"
|
||||
import { MessageCreateOptions } from "../types/message.ts"
|
||||
import { createMessage } from "../structures/message.ts"
|
||||
import { MessageDeletePayload } from "../types/message.ts"
|
||||
import { MessageDeleteBulkPayload } from "../types/message.ts"
|
||||
import { MessageUpdatePayload } from "../types/message.ts"
|
||||
import { MessageReactionPayload } from "../types/message.ts"
|
||||
import { BaseMessageReactionPayload } from "../types/message.ts"
|
||||
import { MessageReactionRemoveEmojiPayload } from "../types/message.ts"
|
||||
} from "../types/discord.ts";
|
||||
import {
|
||||
eventHandlers,
|
||||
botID,
|
||||
botGatewayData,
|
||||
identifyPayload,
|
||||
} from "./client.ts";
|
||||
import {
|
||||
updatePreviousSequenceNumber,
|
||||
sendConstantHeartbeats,
|
||||
previousSequenceNumber,
|
||||
} from "./gateway.ts";
|
||||
import {
|
||||
WebSocket,
|
||||
connectWebSocket,
|
||||
} from "https://deno.land/std@0.50.0/ws/mod.ts";
|
||||
import { handleInternalChannelCreate } from "../events/channels.ts";
|
||||
import { handleInternalChannelUpdate } from "../events/channels.ts";
|
||||
import { handleInternalChannelDelete } from "../events/channels.ts";
|
||||
import { createGuild } from "../structures/guild.ts";
|
||||
import { handleInternalGuildCreate } from "../events/guilds.ts";
|
||||
import { cache } from "../utils/cache.ts";
|
||||
import { handleInternalGuildUpdate } from "../events/guilds.ts";
|
||||
import {
|
||||
CreateGuildPayload,
|
||||
GuildMemberChunkPayload,
|
||||
GuildRolePayload,
|
||||
UserPayload,
|
||||
} from "../types/guild.ts";
|
||||
import { GuildDeletePayload } from "../types/guild.ts";
|
||||
import { handleInternalGuildDelete } from "../events/guilds.ts";
|
||||
import { GuildBanPayload } from "../types/guild.ts";
|
||||
import { createUser } from "../structures/user.ts";
|
||||
import { GuildEmojisUpdatePayload } from "../types/guild.ts";
|
||||
import { GuildMemberAddPayload } from "../types/guild.ts";
|
||||
import { createMember } from "../structures/member.ts";
|
||||
import { GuildMemberUpdatePayload } from "../types/guild.ts";
|
||||
import { ChannelCreatePayload } from "../types/channel.ts";
|
||||
import { createRole } from "../structures/role.ts";
|
||||
import { MessageCreateOptions } from "../types/message.ts";
|
||||
import { createMessage } from "../structures/message.ts";
|
||||
import { MessageDeletePayload } from "../types/message.ts";
|
||||
import { MessageDeleteBulkPayload } from "../types/message.ts";
|
||||
import { MessageUpdatePayload } from "../types/message.ts";
|
||||
import { MessageReactionPayload } from "../types/message.ts";
|
||||
import { BaseMessageReactionPayload } from "../types/message.ts";
|
||||
import { MessageReactionRemoveEmojiPayload } from "../types/message.ts";
|
||||
|
||||
/** The session id is needed for RESUME functionality when discord disconnects randomly. */
|
||||
let sessionID = ""
|
||||
let sessionID = "";
|
||||
let shardCounter = 0;
|
||||
|
||||
function createShardWorker() {
|
||||
new Worker("./module/shard.ts", { type: "module" })
|
||||
const shard = new Worker("./module/shard.ts", { type: "module", deno: true });
|
||||
shard.onmessage = (message) => {
|
||||
if (message.data.type === "REQUEST_CLIENT_OPTIONS") {
|
||||
identifyPayload.shard = [shardCounter++, botGatewayData.shards];
|
||||
shard.postMessage(
|
||||
{ type: "CREATE_SHARD", botGatewayData, identifyPayload },
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const spawnShards = (data: DiscordBotGatewayData, payload: unknown, id = 0) => {
|
||||
identifyPayload.shards = [id, data.shards]
|
||||
createShardWorker()
|
||||
if (id < data.shards) spawnShards(data, payload, id + 1)
|
||||
}
|
||||
export const spawnShards = async (
|
||||
data: DiscordBotGatewayData,
|
||||
payload: unknown,
|
||||
id = 1,
|
||||
) => {
|
||||
createShardWorker();
|
||||
if (id < data.shards) spawnShards(data, payload, id + 1);
|
||||
};
|
||||
|
||||
export function handleDiscordPayload(data: DiscordPayload, socket: WebSocket, resumeInterval: number) {
|
||||
export function handleDiscordPayload(
|
||||
data: DiscordPayload,
|
||||
socket: WebSocket,
|
||||
resumeInterval: number,
|
||||
) {
|
||||
// Update the sequence number if it is present
|
||||
if (data.s) updatePreviousSequenceNumber(data.s)
|
||||
eventHandlers.raw?.(data)
|
||||
if (data.s) updatePreviousSequenceNumber(data.s);
|
||||
eventHandlers.raw?.(data);
|
||||
|
||||
switch (data.op) {
|
||||
case GatewayOpcode.Hello:
|
||||
sendConstantHeartbeats(socket, (data.d as DiscordHeartbeatPayload).heartbeat_interval)
|
||||
return
|
||||
sendConstantHeartbeats(
|
||||
socket,
|
||||
(data.d as DiscordHeartbeatPayload).heartbeat_interval,
|
||||
);
|
||||
return;
|
||||
case GatewayOpcode.HeartbeatACK:
|
||||
// Incase the user wants to listen to heartbeat responses
|
||||
return eventHandlers.heartbeat?.()
|
||||
return eventHandlers.heartbeat?.();
|
||||
case GatewayOpcode.Reconnect:
|
||||
case GatewayOpcode.InvalidSession:
|
||||
// Reconnect to the gateway https://discordapp.com/developers/docs/topics/gateway#reconnect
|
||||
// I think this should be handled automatically when the websocket closes
|
||||
return
|
||||
return;
|
||||
case GatewayOpcode.Resume:
|
||||
return clearInterval(resumeInterval)
|
||||
return clearInterval(resumeInterval);
|
||||
case GatewayOpcode.Dispatch:
|
||||
if (data.t === "READY") {
|
||||
// Important for RESUME
|
||||
sessionID = (data.d as ReadyPayload).session_id
|
||||
return eventHandlers.ready?.()
|
||||
sessionID = (data.d as ReadyPayload).session_id;
|
||||
return eventHandlers.ready?.();
|
||||
}
|
||||
|
||||
if (data.t === "CHANNEL_CREATE") return handleInternalChannelCreate(data.d as ChannelCreatePayload)
|
||||
if (data.t === "CHANNEL_UPDATE") return handleInternalChannelUpdate(data.d as ChannelCreatePayload)
|
||||
if (data.t === "CHANNEL_DELETE") return handleInternalChannelDelete(data.d as ChannelCreatePayload)
|
||||
if (data.t === "CHANNEL_CREATE") {
|
||||
return handleInternalChannelCreate(data.d as ChannelCreatePayload);
|
||||
}
|
||||
if (data.t === "CHANNEL_UPDATE") {
|
||||
return handleInternalChannelUpdate(data.d as ChannelCreatePayload);
|
||||
}
|
||||
if (data.t === "CHANNEL_DELETE") {
|
||||
return handleInternalChannelDelete(data.d as ChannelCreatePayload);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_CREATE") {
|
||||
const guild = createGuild(data.d as CreateGuildPayload)
|
||||
handleInternalGuildCreate(guild)
|
||||
if (cache.unavailableGuilds.get(guild.id)) return cache.unavailableGuilds.delete(guild.id)
|
||||
return eventHandlers.guildCreate?.(guild)
|
||||
const guild = createGuild(data.d as CreateGuildPayload);
|
||||
handleInternalGuildCreate(guild);
|
||||
if (cache.unavailableGuilds.get(guild.id)) {
|
||||
return cache.unavailableGuilds.delete(guild.id);
|
||||
}
|
||||
return eventHandlers.guildCreate?.(guild);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_UPDATE") {
|
||||
const options = data.d as CreateGuildPayload
|
||||
const cachedGuild = cache.guilds.get(options.id)
|
||||
const guild = createGuild(options)
|
||||
handleInternalGuildUpdate(guild)
|
||||
if (!cachedGuild) return
|
||||
const options = data.d as CreateGuildPayload;
|
||||
const cachedGuild = cache.guilds.get(options.id);
|
||||
const guild = createGuild(options);
|
||||
handleInternalGuildUpdate(guild);
|
||||
if (!cachedGuild) return;
|
||||
|
||||
return eventHandlers.guildUpdate?.(guild, cachedGuild)
|
||||
return eventHandlers.guildUpdate?.(guild, cachedGuild);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_DELETE") {
|
||||
const options = data.d as GuildDeletePayload
|
||||
const guild = cache.guilds.get(options.id)
|
||||
if (!guild) return
|
||||
const options = data.d as GuildDeletePayload;
|
||||
const guild = cache.guilds.get(options.id);
|
||||
if (!guild) return;
|
||||
|
||||
guild.channels.forEach((channel) => cache.channels.delete(channel.id))
|
||||
if (options.unavailable) return cache.unavailableGuilds.set(options.id, Date.now())
|
||||
guild.channels.forEach((channel) => cache.channels.delete(channel.id));
|
||||
if (options.unavailable) {
|
||||
return cache.unavailableGuilds.set(options.id, Date.now());
|
||||
}
|
||||
|
||||
handleInternalGuildDelete(guild)
|
||||
return eventHandlers.guildDelete?.(guild)
|
||||
handleInternalGuildDelete(guild);
|
||||
return eventHandlers.guildDelete?.(guild);
|
||||
}
|
||||
|
||||
if (data.t && ["GUILD_BAN_ADD", "GUILD_BAN_REMOVE"].includes(data.t)) {
|
||||
const options = data.d as GuildBanPayload
|
||||
const guild = cache.guilds.get(options.guild_id)
|
||||
if (!guild) return
|
||||
const options = data.d as GuildBanPayload;
|
||||
const guild = cache.guilds.get(options.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const user = createUser(options.user)
|
||||
const user = createUser(options.user);
|
||||
return data.t === "GUILD_BAN_ADD"
|
||||
? eventHandlers.guildBanAdd?.(guild, user)
|
||||
: eventHandlers.guildBanRemove?.(guild, user)
|
||||
: eventHandlers.guildBanRemove?.(guild, user);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_EMOJIS_UPDATE") {
|
||||
const options = data.d as GuildEmojisUpdatePayload
|
||||
const guild = cache.guilds.get(options.guild_id)
|
||||
if (!guild) return
|
||||
const options = data.d as GuildEmojisUpdatePayload;
|
||||
const guild = cache.guilds.get(options.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedEmojis = guild.emojis
|
||||
guild.emojis = options.emojis
|
||||
const cachedEmojis = guild.emojis;
|
||||
guild.emojis = options.emojis;
|
||||
|
||||
return eventHandlers.guildEmojisUpdate?.(guild, options.emojis, cachedEmojis)
|
||||
return eventHandlers.guildEmojisUpdate?.(
|
||||
guild,
|
||||
options.emojis,
|
||||
cachedEmojis,
|
||||
);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_MEMBER_ADD") {
|
||||
const options = data.d as GuildMemberAddPayload
|
||||
const guild = cache.guilds.get(options.guild_id)
|
||||
if (!guild) return
|
||||
const options = data.d as GuildMemberAddPayload;
|
||||
const guild = cache.guilds.get(options.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const memberCount = guild.memberCount + 1
|
||||
guild.memberCount = memberCount
|
||||
const memberCount = guild.memberCount + 1;
|
||||
guild.memberCount = memberCount;
|
||||
const member = createMember(
|
||||
options,
|
||||
options.guild_id,
|
||||
[...guild.roles.values()].map((role) => role.raw),
|
||||
guild.owner_id
|
||||
)
|
||||
guild.members.set(options.user.id, member)
|
||||
guild.owner_id,
|
||||
);
|
||||
guild.members.set(options.user.id, member);
|
||||
|
||||
return eventHandlers.guildMemberAdd?.(guild, member)
|
||||
return eventHandlers.guildMemberAdd?.(guild, member);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_MEMBER_REMOVE") {
|
||||
const options = data.d as GuildBanPayload
|
||||
const guild = cache.guilds.get(options.guild_id)
|
||||
if (!guild) return
|
||||
const options = data.d as GuildBanPayload;
|
||||
const guild = cache.guilds.get(options.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const memberCount = guild.memberCount - 1
|
||||
guild.memberCount = memberCount
|
||||
const memberCount = guild.memberCount - 1;
|
||||
guild.memberCount = memberCount;
|
||||
|
||||
const member = guild.members.get(options.user.id)
|
||||
return eventHandlers.guildMemberRemove?.(guild, member || createUser(options.user))
|
||||
const member = guild.members.get(options.user.id);
|
||||
return eventHandlers.guildMemberRemove?.(
|
||||
guild,
|
||||
member || createUser(options.user),
|
||||
);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_MEMBER_UPDATE") {
|
||||
const options = data.d as GuildMemberUpdatePayload
|
||||
const guild = cache.guilds.get(options.guild_id)
|
||||
if (!guild) return
|
||||
const options = data.d as GuildMemberUpdatePayload;
|
||||
const guild = cache.guilds.get(options.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedMember = guild.members.get(options.user.id)
|
||||
const cachedMember = guild.members.get(options.user.id);
|
||||
|
||||
const newMemberData = {
|
||||
...options,
|
||||
premium_since: options.premium_since || undefined,
|
||||
joined_at: new Date(cachedMember?.joined_at || Date.now()).toISOString(),
|
||||
joined_at: new Date(cachedMember?.joined_at || Date.now())
|
||||
.toISOString(),
|
||||
deaf: cachedMember?.deaf || false,
|
||||
mute: cachedMember?.mute || false,
|
||||
}
|
||||
};
|
||||
const member = createMember(
|
||||
newMemberData,
|
||||
options.guild_id,
|
||||
[...guild.roles.values()].map((r) => r.raw),
|
||||
guild.owner_id
|
||||
)
|
||||
guild.members.set(options.user.id, member)
|
||||
guild.owner_id,
|
||||
);
|
||||
guild.members.set(options.user.id, member);
|
||||
|
||||
if (cachedMember?.nick !== options.nick)
|
||||
eventHandlers.nicknameUpdate?.(guild, member, options.nick, cachedMember?.nick)
|
||||
const roleIDs = cachedMember?.roles || []
|
||||
if (cachedMember?.nick !== options.nick) {
|
||||
eventHandlers.nicknameUpdate?.(
|
||||
guild,
|
||||
member,
|
||||
options.nick,
|
||||
cachedMember?.nick,
|
||||
);
|
||||
}
|
||||
const roleIDs = cachedMember?.roles || [];
|
||||
|
||||
roleIDs.forEach((id) => {
|
||||
if (!options.roles.includes(id)) eventHandlers.role_lost?.(guild, member, id)
|
||||
})
|
||||
if (!options.roles.includes(id)) {
|
||||
eventHandlers.role_lost?.(guild, member, id);
|
||||
}
|
||||
});
|
||||
|
||||
options.roles.forEach((id) => {
|
||||
if (!roleIDs.includes(id)) eventHandlers.role_gained?.(guild, member, id)
|
||||
})
|
||||
if (!roleIDs.includes(id)) {
|
||||
eventHandlers.role_gained?.(guild, member, id);
|
||||
}
|
||||
});
|
||||
|
||||
return eventHandlers.guild_member_update?.(guild, member, cachedMember)
|
||||
return eventHandlers.guild_member_update?.(guild, member, cachedMember);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_MEMBERS_CHUNK") {
|
||||
const options = data.d as GuildMemberChunkPayload
|
||||
const guild = cache.guilds.get(options.guild_id)
|
||||
if (!guild) return
|
||||
const options = data.d as GuildMemberChunkPayload;
|
||||
const guild = cache.guilds.get(options.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
options.members.forEach((member) =>
|
||||
guild.members.set(
|
||||
@@ -212,44 +276,48 @@ export function handleDiscordPayload(data: DiscordPayload, socket: WebSocket, re
|
||||
member,
|
||||
options.guild_id,
|
||||
[...guild.roles.values()].map((r) => r.raw),
|
||||
guild.owner_id
|
||||
)
|
||||
guild.owner_id,
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (data.t && ["GUILD_ROLE_CREATE", "GUILD_ROLE_DELETE", "GUILD_ROLE_UPDATE"].includes(data.t)) {
|
||||
const options = data.d as GuildRolePayload
|
||||
const guild = cache.guilds.get(options.guild_id)
|
||||
if (!guild) return
|
||||
if (
|
||||
data.t &&
|
||||
["GUILD_ROLE_CREATE", "GUILD_ROLE_DELETE", "GUILD_ROLE_UPDATE"]
|
||||
.includes(data.t)
|
||||
) {
|
||||
const options = data.d as GuildRolePayload;
|
||||
const guild = cache.guilds.get(options.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
if (data.t === "GUILD_ROLE_CREATE") {
|
||||
const role = createRole(options.role)
|
||||
const roles = guild.roles.set(options.role.id, role)
|
||||
guild.roles = roles
|
||||
return eventHandlers.roleCreate?.(guild, role)
|
||||
const role = createRole(options.role);
|
||||
const roles = guild.roles.set(options.role.id, role);
|
||||
guild.roles = roles;
|
||||
return eventHandlers.roleCreate?.(guild, role);
|
||||
}
|
||||
|
||||
const cached_role = guild.roles.get(options.role.id)
|
||||
if (!cached_role) return
|
||||
const cached_role = guild.roles.get(options.role.id);
|
||||
if (!cached_role) return;
|
||||
|
||||
if (data.t === "GUILD_ROLE_DELETE") {
|
||||
const roles = guild.roles
|
||||
roles.delete(options.role.id)
|
||||
guild.roles = roles
|
||||
return eventHandlers.roleDelete?.(guild, cached_role)
|
||||
const roles = guild.roles;
|
||||
roles.delete(options.role.id);
|
||||
guild.roles = roles;
|
||||
return eventHandlers.roleDelete?.(guild, cached_role);
|
||||
}
|
||||
|
||||
if (data.t === "GUILD_ROLE_UPDATE") {
|
||||
const role = createRole(options.role)
|
||||
return eventHandlers.roleUpdate?.(guild, role, cached_role)
|
||||
const role = createRole(options.role);
|
||||
return eventHandlers.roleUpdate?.(guild, role, cached_role);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.t === "MESSAGE_CREATE") {
|
||||
const options = data.d as MessageCreateOptions
|
||||
const channel = cache.channels.get(options.channel_id)
|
||||
const message = createMessage(options)
|
||||
const options = data.d as MessageCreateOptions;
|
||||
const channel = cache.channels.get(options.channel_id);
|
||||
const message = createMessage(options);
|
||||
|
||||
if (channel) {
|
||||
// channel.last_message_id = () => options.id
|
||||
@@ -257,103 +325,131 @@ export function handleDiscordPayload(data: DiscordPayload, socket: WebSocket, re
|
||||
// TODO: LIMIT THIS TO 100 messages
|
||||
// }
|
||||
}
|
||||
return eventHandlers.messageCreate?.(message)
|
||||
return eventHandlers.messageCreate?.(message);
|
||||
}
|
||||
|
||||
if (data.t && ["MESSAGE_DELETE", "MESSAGE_DELETE_BULK"].includes(data.t)) {
|
||||
const options = data.d as MessageDeletePayload
|
||||
const deletedMessages = data.t === "MESSAGE_DELETE" ? [options.id] : (data.d as MessageDeleteBulkPayload).ids
|
||||
if (
|
||||
data.t && ["MESSAGE_DELETE", "MESSAGE_DELETE_BULK"].includes(data.t)
|
||||
) {
|
||||
const options = data.d as MessageDeletePayload;
|
||||
const deletedMessages = data.t === "MESSAGE_DELETE"
|
||||
? [options.id]
|
||||
: (data.d as MessageDeleteBulkPayload).ids;
|
||||
|
||||
const channel = cache.channels.get(options.channel_id)
|
||||
if (!channel) return
|
||||
const channel = cache.channels.get(options.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
deletedMessages.forEach((id) => {
|
||||
console.log(id)
|
||||
console.log(id);
|
||||
// const message = channel.messages().get(id)
|
||||
// if (message) {
|
||||
// // TODO: update the messages cache
|
||||
// }
|
||||
|
||||
// return eventHandlers.message_delete?.(message || { id, channel })
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (data.t === "MESSAGE_UPDATE") {
|
||||
const options = data.d as MessageUpdatePayload
|
||||
const channel = cache.channels.get(options.channel_id)
|
||||
if (!channel) return
|
||||
const options = data.d as MessageUpdatePayload;
|
||||
const channel = cache.channels.get(options.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
// const cachedMessage = channel.messages().get(options.id)
|
||||
// return eventHandlers.message_update?.(message, cachedMessage)
|
||||
}
|
||||
|
||||
if (data.t && ["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE"].includes(data.t)) {
|
||||
const options = data.d as MessageReactionPayload
|
||||
const message = cache.messages.get(options.message_id)
|
||||
const isAdd = data.t === "MESSAGE_REACTION_ADD"
|
||||
if (
|
||||
data.t &&
|
||||
["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE"].includes(data.t)
|
||||
) {
|
||||
const options = data.d as MessageReactionPayload;
|
||||
const message = cache.messages.get(options.message_id);
|
||||
const isAdd = data.t === "MESSAGE_REACTION_ADD";
|
||||
|
||||
if (message) {
|
||||
const previousReactions = message.reactions
|
||||
const previousReactions = message.reactions;
|
||||
const reactionExisted = previousReactions?.find(
|
||||
(reaction) => reaction.emoji.id === options.emoji.id && reaction.emoji.name === options.emoji.name
|
||||
)
|
||||
if (reactionExisted) reactionExisted.count = isAdd ? reactionExisted.count + 1 : reactionExisted.count - 1
|
||||
else {
|
||||
(reaction) =>
|
||||
reaction.emoji.id === options.emoji.id &&
|
||||
reaction.emoji.name === options.emoji.name,
|
||||
);
|
||||
if (reactionExisted) {
|
||||
reactionExisted.count = isAdd
|
||||
? reactionExisted.count + 1
|
||||
: reactionExisted.count - 1;
|
||||
} else {
|
||||
const newReaction = {
|
||||
count: 1,
|
||||
me: options.user_id === botID,
|
||||
emoji: { ...options.emoji, id: options.emoji.id || undefined },
|
||||
}
|
||||
message.reactions = message.reactions ? [...message.reactions, newReaction] : [newReaction]
|
||||
};
|
||||
message.reactions = message.reactions
|
||||
? [...message.reactions, newReaction]
|
||||
: [newReaction];
|
||||
}
|
||||
|
||||
cache.messages.set(options.message_id, message)
|
||||
cache.messages.set(options.message_id, message);
|
||||
}
|
||||
|
||||
return isAdd
|
||||
? eventHandlers.reactionAdd?.(message || options, options.emoji, options.user_id)
|
||||
: eventHandlers.reactionRemove?.(message || options, options.emoji, options.user_id)
|
||||
? eventHandlers.reactionAdd?.(
|
||||
message || options,
|
||||
options.emoji,
|
||||
options.user_id,
|
||||
)
|
||||
: eventHandlers.reactionRemove?.(
|
||||
message || options,
|
||||
options.emoji,
|
||||
options.user_id,
|
||||
);
|
||||
}
|
||||
|
||||
if (data.t === "MESSAGE_REACTION_REMOVE_ALL") {
|
||||
return eventHandlers.reactionRemoveAll?.(data.d as BaseMessageReactionPayload)
|
||||
return eventHandlers.reactionRemoveAll?.(
|
||||
data.d as BaseMessageReactionPayload,
|
||||
);
|
||||
}
|
||||
|
||||
if (data.t === "MESSAGE_REACTION_REMOVE_EMOJI") {
|
||||
return eventHandlers.reactionRemoveEmoji?.(data.d as MessageReactionRemoveEmojiPayload)
|
||||
return eventHandlers.reactionRemoveEmoji?.(
|
||||
data.d as MessageReactionRemoveEmojiPayload,
|
||||
);
|
||||
}
|
||||
|
||||
if (data.t === "PRESENCE_UPDATE") {
|
||||
return eventHandlers.presenceUpdate?.(data.d as PresenceUpdatePayload)
|
||||
return eventHandlers.presenceUpdate?.(data.d as PresenceUpdatePayload);
|
||||
}
|
||||
|
||||
if (data.t === "TYPING_START") {
|
||||
return eventHandlers.typingStart?.(data.d as TypingStartPayload)
|
||||
return eventHandlers.typingStart?.(data.d as TypingStartPayload);
|
||||
}
|
||||
|
||||
if (data.t === "USER_UPDATE") {
|
||||
const userData = data.d as UserPayload
|
||||
const cachedUser = cache.users.get(botID)
|
||||
const user = createUser(userData)
|
||||
cache.users.set(userData.id, user)
|
||||
return eventHandlers.botUpdate?.(user, cachedUser)
|
||||
const userData = data.d as UserPayload;
|
||||
const cachedUser = cache.users.get(botID);
|
||||
const user = createUser(userData);
|
||||
cache.users.set(userData.id, user);
|
||||
return eventHandlers.botUpdate?.(user, cachedUser);
|
||||
}
|
||||
|
||||
if (data.t === "VOICE_STATE_UPDATE") {
|
||||
const payload = data.d as VoiceStateUpdatePayload
|
||||
if (!payload.guild_id) return
|
||||
const payload = data.d as VoiceStateUpdatePayload;
|
||||
if (!payload.guild_id) return;
|
||||
|
||||
const guild = cache.guilds.get(payload.guild_id)
|
||||
if (!guild) return
|
||||
const guild = cache.guilds.get(payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const member = guild.members.get(payload.user_id)
|
||||
if (!member) return
|
||||
const member = guild.members.get(payload.user_id);
|
||||
if (!member) return;
|
||||
|
||||
const cached_state = guild.voice_states.find((state) => state.user_id === payload.user_id)
|
||||
const cached_state = guild.voice_states.find((state) =>
|
||||
state.user_id === payload.user_id
|
||||
);
|
||||
// No cached state before so lets make one for em
|
||||
if (!cached_state) {
|
||||
guild.voice_states = [...guild.voice_states, payload]
|
||||
return
|
||||
guild.voice_states = [...guild.voice_states, payload];
|
||||
return;
|
||||
}
|
||||
|
||||
if (cached_state.channel_id !== payload.channel_id) {
|
||||
@@ -361,33 +457,39 @@ export function handleDiscordPayload(data: DiscordPayload, socket: WebSocket, re
|
||||
if (payload.channel_id) {
|
||||
cached_state.channel_id
|
||||
? // Was in a channel before
|
||||
eventHandlers.voiceChannelSwitch?.(member, payload.channel_id, cached_state.channel_id)
|
||||
eventHandlers.voiceChannelSwitch?.(
|
||||
member,
|
||||
payload.channel_id,
|
||||
cached_state.channel_id,
|
||||
)
|
||||
: // Was not in a channel before so user just joined
|
||||
eventHandlers.voiceChannelJoin?.(member, payload.channel_id)
|
||||
}
|
||||
// Left the channel
|
||||
eventHandlers.voiceChannelJoin?.(member, payload.channel_id);
|
||||
} // Left the channel
|
||||
else if (cached_state.channel_id) {
|
||||
eventHandlers.voiceChannelLeave?.(member, cached_state.channel_id)
|
||||
eventHandlers.voiceChannelLeave?.(member, cached_state.channel_id);
|
||||
}
|
||||
}
|
||||
|
||||
return eventHandlers.voiceStateUpdate?.(member, payload)
|
||||
return eventHandlers.voiceStateUpdate?.(member, payload);
|
||||
}
|
||||
|
||||
if (data.t === "WEBHOOKS_UPDATE") {
|
||||
const options = data.d as WebhookUpdatePayload
|
||||
return eventHandlers.webhooksUpdate?.(options.channel_id, options.guild_id)
|
||||
const options = data.d as WebhookUpdatePayload;
|
||||
return eventHandlers.webhooksUpdate?.(
|
||||
options.channel_id,
|
||||
options.guild_id,
|
||||
);
|
||||
}
|
||||
|
||||
return
|
||||
return;
|
||||
default:
|
||||
return
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export async function resumeConnection(payload: object, socket: WebSocket) {
|
||||
return setInterval(async () => {
|
||||
socket = await connectWebSocket(botGatewayData.url)
|
||||
socket = await connectWebSocket(botGatewayData.url);
|
||||
await socket.send(
|
||||
JSON.stringify({
|
||||
op: GatewayOpcode.Resume,
|
||||
@@ -396,7 +498,7 @@ export async function resumeConnection(payload: object, socket: WebSocket) {
|
||||
session_id: sessionID,
|
||||
seq: previousSequenceNumber,
|
||||
},
|
||||
})
|
||||
)
|
||||
}, 1000 * 15)
|
||||
}),
|
||||
);
|
||||
}, 1000 * 15);
|
||||
}
|
||||
|
||||
+25
-18
@@ -1,32 +1,39 @@
|
||||
import { blue, green, red, yellow } from "https://deno.land/std@v1.0.0-rc1/fmt/colors.ts"
|
||||
import {
|
||||
blue,
|
||||
green,
|
||||
red,
|
||||
yellow,
|
||||
} from "https://deno.land/std@0.50.0/fmt/colors.ts";
|
||||
|
||||
export const getTime = () => {
|
||||
const now = new Date()
|
||||
const hours = now.getHours()
|
||||
const minute = now.getMinutes()
|
||||
const now = new Date();
|
||||
const hours = now.getHours();
|
||||
const minute = now.getMinutes();
|
||||
|
||||
let hour = hours
|
||||
let amOrPm = `AM`
|
||||
let hour = hours;
|
||||
let amOrPm = `AM`;
|
||||
if (hour > 12) {
|
||||
amOrPm = `PM`
|
||||
hour = hour - 12
|
||||
amOrPm = `PM`;
|
||||
hour = hour - 12;
|
||||
}
|
||||
|
||||
return `${hour >= 10 ? hour : `0${hour}`}:${minute >= 10 ? minute : `0${minute}`} ${amOrPm}`
|
||||
}
|
||||
return `${hour >= 10 ? hour : `0${hour}`}:${
|
||||
minute >= 10 ? minute : `0${minute}`
|
||||
} ${amOrPm}`;
|
||||
};
|
||||
|
||||
export const logGreen = (text: unknown) => {
|
||||
console.log(green(`[${getTime()}] => ${JSON.stringify(text)}`))
|
||||
}
|
||||
console.log(green(`[${getTime()}] => ${JSON.stringify(text)}`));
|
||||
};
|
||||
|
||||
export const logBlue = (text: unknown) => {
|
||||
console.log(blue(`[${getTime()}] => ${JSON.stringify(text)}`))
|
||||
}
|
||||
console.log(blue(`[${getTime()}] => ${JSON.stringify(text)}`));
|
||||
};
|
||||
|
||||
export const logRed = (text: unknown) => {
|
||||
console.log(red(`[${getTime()}] => ${JSON.stringify(text)}`))
|
||||
}
|
||||
console.log(red(`[${getTime()}] => ${JSON.stringify(text)}`));
|
||||
};
|
||||
|
||||
export const logYellow = (text: unknown) => {
|
||||
console.log(yellow(`[${getTime()}] => ${JSON.stringify(text)}`))
|
||||
}
|
||||
console.log(yellow(`[${getTime()}] => ${JSON.stringify(text)}`));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user