remaining collectors

This commit is contained in:
Skillz
2020-09-12 12:58:41 -04:00
parent 2ff52d96e1
commit 02badb1c97
7 changed files with 411 additions and 276 deletions

107
src/controllers/messages.ts Normal file
View File

@@ -0,0 +1,107 @@
import { eventHandlers } from "../module/client.ts";
import { structures } from "../structures/mod.ts";
import { DiscordPayload } from "../types/discord.ts";
import {
MessageCreateOptions,
MessageDeletePayload,
MessageDeleteBulkPayload,
} from "../types/message.ts";
import { cache } from "../utils/cache.ts";
export function handleInternalMessageCreate(data: DiscordPayload) {
if (data.t !== "MESSAGE_CREATE") return;
const payload = data.d as MessageCreateOptions;
const channel = cache.channels.get(payload.channel_id);
if (channel) channel.lastMessageID = payload.id;
const message = structures.createMessage(payload);
// Cache the message
cache.messages.set(payload.id, message);
const guild = payload.guild_id
? cache.guilds.get(payload.guild_id)
: undefined;
if (payload.member) {
// If in a guild cache the author as a member
guild?.members.set(
payload.author.id,
structures.createMember(
{ ...payload.member, user: payload.author },
guild,
),
);
}
payload.mentions.forEach((mention) => {
// Cache the member if its a valid member
if (mention.member) {
guild?.members.set(
mention.id,
structures.createMember(
{ ...mention.member, user: mention },
guild,
),
);
}
});
eventHandlers.messageCreate?.(message);
}
export function handleInternalMessageDelete(data: DiscordPayload) {
if (data.t !== "MESSAGE_DELETE") return;
const payload = data.d as MessageDeletePayload;
const channel = cache.channels.get(payload.channel_id);
if (!channel) return;
eventHandlers.messageDelete?.(
cache.messages.get(payload.id) || { id: payload.id, channel },
);
cache.messages.delete(payload.id);
}
export function handleInternalMessageDeleteBulk(data: DiscordPayload) {
if (data.t !== "MESSAGE_DELETE_BULK") return;
const payload = data.d as MessageDeleteBulkPayload;
const channel = cache.channels.get(payload.channel_id);
if (!channel) return;
payload.ids.forEach((id) => {
eventHandlers.messageDelete?.(cache.messages.get(id) || { id, channel });
cache.messages.delete(id);
});
}
export function handleInternalMessageUpdate(data: DiscordPayload) {
if (data.t !== "MESSAGE_UPDATE") return;
const payload = data.d as MessageCreateOptions;
const channel = cache.channels.get(payload.channel_id);
if (!channel) return;
const cachedMessage = cache.messages.get(payload.id);
if (!cachedMessage) return;
const oldMessage = {
attachments: cachedMessage.attachments,
content: cachedMessage.content,
embeds: cachedMessage.embeds,
editedTimestamp: cachedMessage.editedTimestamp,
tts: cachedMessage.tts,
pinned: cachedMessage.pinned,
};
// Messages with embeds can trigger update but they wont have edited_timestamp
if (
!payload.edited_timestamp ||
(cachedMessage.content !== payload.content)
) {
return;
}
eventHandlers.messageUpdate?.(cachedMessage, oldMessage);
}

View File

@@ -1,16 +1,28 @@
import { delay } from "https://deno.land/std@0.67.0/async/delay.ts";
import { eventHandlers, setBotID } from "../module/client.ts";
import { allowNextShard } from "../module/shardingManager.ts";
import { DiscordPayload, ReadyPayload } from "../types/discord.ts";
import { structures } from "../structures/mod.ts";
import {
DiscordPayload,
PresenceUpdatePayload,
ReadyPayload,
TypingStartPayload,
VoiceStateUpdatePayload,
WebhookUpdatePayload,
} from "../types/discord.ts";
import { UserPayload } from "../types/guild.ts";
import { cache } from "../utils/cache.ts";
export async function handleInternalReady(data: DiscordPayload, shardID: number) {
export async function handleInternalReady(
data: DiscordPayload,
shardID: number,
) {
if (data.t !== "READY") return;
const payload = data.d as ReadyPayload;
setBotID(payload.user.id);
// Triggered on each shard
// Triggered on each shard
eventHandlers.shardReady?.(shardID);
if (payload.shard && shardID === payload.shard[1] - 1) {
// Wait 10 seconds to allow all guild create events to be processed
@@ -19,7 +31,101 @@ export async function handleInternalReady(data: DiscordPayload, shardID: number)
eventHandlers.ready?.();
}
// Wait 5 seconds to spawn next shard
await delay(5000);
allowNextShard()
// Wait 5 seconds to spawn next shard
await delay(5000);
allowNextShard();
}
export function handleInternalPresenceUpdate(data: DiscordPayload) {
if (data.t !== "PRESENCE_UPDATE") return;
const payload = data.d as PresenceUpdatePayload;
const oldPresence = cache.presences.get(payload.user.id);
cache.presences.set(payload.user.id, payload);
return eventHandlers.presenceUpdate?.(payload, oldPresence);
}
export function handleInternalTypingStart(data: DiscordPayload) {
if (data.t !== "TYPING_START") return;
eventHandlers.typingStart?.(data.d as TypingStartPayload);
}
export function handleInternalUserUpdate(data: DiscordPayload) {
if (data.t !== "USER_UPDATE") return;
const userData = data.d as UserPayload;
cache.guilds.forEach((guild) => {
const member = guild.members.get(userData.id);
if (!member) return;
// member.author = userData;
Object.entries(userData).forEach(([key, value]) => {
// @ts-ignore
if (member[key] === value) return;
// @ts-ignore
member[key] = value;
});
});
return eventHandlers.botUpdate?.(userData);
}
export function handleInternalVoiceStateUpdate(data: DiscordPayload) {
if (data.t !== "VOICE_STATE_UPDATE") return;
const payload = data.d as VoiceStateUpdatePayload;
if (!payload.guild_id) return;
const guild = cache.guilds.get(payload.guild_id);
if (!guild) return;
const member = guild.members.get(payload.user_id) ||
(payload.member
? structures.createMember(payload.member, guild)
: undefined);
if (!member) return;
// No cached state before so lets make one for em
const cachedState = guild.voiceStates.get(payload.user_id);
guild.voiceStates.set(payload.user_id, {
...payload,
guildID: payload.guild_id,
channelID: payload.channel_id,
userID: payload.user_id,
sessionID: payload.session_id,
selfDeaf: payload.self_deaf,
selfMute: payload.self_mute,
selfStream: payload.self_stream,
});
if (cachedState?.channelID !== payload.channel_id) {
// Either joined or moved channels
if (payload.channel_id) {
cachedState?.channelID
? // Was in a channel before
eventHandlers.voiceChannelSwitch?.(
member,
payload.channel_id,
cachedState.channelID,
)
: // Was not in a channel before so user just joined
eventHandlers.voiceChannelJoin?.(member, payload.channel_id);
} // Left the channel
else if (cachedState?.channelID) {
guild.voiceStates.delete(payload.user_id);
eventHandlers.voiceChannelLeave?.(member, cachedState.channelID);
}
}
eventHandlers.voiceStateUpdate?.(member, payload);
}
export function handleInternalWebhooksUpdate(data: DiscordPayload) {
if (data.t !== "WEBHOOKS_UPDATE") return;
const options = data.d as WebhookUpdatePayload;
return eventHandlers.webhooksUpdate?.(
options.channel_id,
options.guild_id,
);
}

View File

@@ -19,7 +19,31 @@ import {
handleInternalGuildMembersChunk,
handleInternalGuildMemberUpdate,
} from "./members.ts";
import { handleInternalReady } from "./misc.ts";
import {
handleInternalMessageCreate,
handleInternalMessageDelete,
handleInternalMessageDeleteBulk,
handleInternalMessageUpdate,
} from "./messages.ts";
import {
handleInternalPresenceUpdate,
handleInternalReady,
handleInternalTypingStart,
handleInternalUserUpdate,
handleInternalVoiceStateUpdate,
handleInternalWebhooksUpdate,
} from "./misc.ts";
import {
handleInternalMessageReactionAdd,
handleInternalMessageReactionRemove,
handleInternalMessageReactionRemoveAll,
handleInternalMessageReactionRemoveEmoji,
} from "./reactions.ts";
import {
handleInternalGuildRoleCreate,
handleInternalGuildRoleDelete,
handleInternalGuildRoleUpdate,
} from "./roles.ts";
export let controllers = {
READY: handleInternalReady,
@@ -36,4 +60,26 @@ export let controllers = {
GUILD_MEMBER_REMOVE: handleInternalGuildMemberRemove,
GUILD_MEMBER_UPDATE: handleInternalGuildMemberUpdate,
GUILD_MEMBERS_CHUNK: handleInternalGuildMembersChunk,
GUILD_ROLE_CREATE: handleInternalGuildRoleCreate,
GUILD_ROLE_DELETE: handleInternalGuildRoleDelete,
GUILD_ROLE_UPDATE: handleInternalGuildRoleUpdate,
MESSAGE_CREATE: handleInternalMessageCreate,
MESSAGE_DELETE: handleInternalMessageDelete,
MESSAGE_DELETE_BULK: handleInternalMessageDeleteBulk,
MESSAGE_UPDATE: handleInternalMessageUpdate,
MESSAGE_REACTION_ADD: handleInternalMessageReactionAdd,
MESSAGE_REACTION_REMOVE: handleInternalMessageReactionRemove,
MESSAGE_REACTION_REMOVE_ALL: handleInternalMessageReactionRemoveAll,
MESSAGE_REACTION_REMOVE_EMOJI: handleInternalMessageReactionRemoveEmoji,
PRESENCE_UPDATE: handleInternalPresenceUpdate,
TYPING_START: handleInternalTypingStart,
USER_UPDATE: handleInternalUserUpdate,
VOICE_STATE_UPDATE: handleInternalVoiceStateUpdate,
WEBHOOKS_UPDATE: handleInternalWebhooksUpdate,
};
export type Controllers = typeof controllers
export function updateControllers(newControllers: Controllers) {
controllers = newControllers
}

View File

@@ -0,0 +1,129 @@
import { botID, eventHandlers } from "../module/client.ts";
import { structures } from "../structures/mod.ts";
import { DiscordPayload } from "../types/discord.ts";
import {
BaseMessageReactionPayload,
MessageReactionPayload,
MessageReactionRemoveEmojiPayload,
} from "../types/message.ts";
import { cache } from "../utils/cache.ts";
export function handleInternalMessageReactionAdd(data: DiscordPayload) {
if (data.t !== "MESSAGE_REACTION_ADD") return;
const payload = data.d as MessageReactionPayload;
const message = cache.messages.get(payload.message_id);
if (message) {
const previousReactions = message.reactions;
const reactionExisted = previousReactions?.find(
(reaction) =>
reaction.emoji.id === payload.emoji.id &&
reaction.emoji.name === payload.emoji.name,
);
if (reactionExisted) reactionExisted.count++;
else {
const newReaction = {
count: 1,
me: payload.user_id === botID,
emoji: { ...payload.emoji, id: payload.emoji.id || undefined },
};
message.reactions = message.reactions
? [...message.reactions, newReaction]
: [newReaction];
}
cache.messages.set(payload.message_id, message);
}
if (payload.member && payload.guild_id) {
const guild = cache.guilds.get(payload.guild_id);
guild?.members.set(
payload.member.user.id,
structures.createMember(
payload.member,
guild,
),
);
}
const uncachedOptions = {
...payload,
id: payload.message_id,
channelID: payload.channel_id,
guildID: payload.guild_id,
};
eventHandlers.reactionAdd?.(
message || uncachedOptions,
payload.emoji,
payload.user_id,
);
}
export function handleInternalMessageReactionRemove(data: DiscordPayload) {
if (data.t !== "MESSAGE_REACTION_REMOVE") return;
const payload = data.d as MessageReactionPayload;
const message = cache.messages.get(payload.message_id);
if (message) {
const previousReactions = message.reactions;
const reactionExisted = previousReactions?.find(
(reaction) =>
reaction.emoji.id === payload.emoji.id &&
reaction.emoji.name === payload.emoji.name,
);
if (reactionExisted) reactionExisted.count--;
else {
const newReaction = {
count: 1,
me: payload.user_id === botID,
emoji: { ...payload.emoji, id: payload.emoji.id || undefined },
};
message.reactions = message.reactions
? [...message.reactions, newReaction]
: [newReaction];
}
cache.messages.set(payload.message_id, message);
}
if (payload.member && payload.guild_id) {
const guild = cache.guilds.get(payload.guild_id);
guild?.members.set(
payload.member.user.id,
structures.createMember(
payload.member,
guild,
),
);
}
const uncachedOptions = {
...payload,
id: payload.message_id,
channelID: payload.channel_id,
guildID: payload.guild_id,
};
eventHandlers.reactionRemove?.(
message || uncachedOptions,
payload.emoji,
payload.user_id,
);
}
export function handleInternalMessageReactionRemoveAll(data: DiscordPayload) {
if (data.t !== "MESSAGE_REACTION_REMOVE_ALL") return;
eventHandlers.reactionRemoveAll?.(data.d as BaseMessageReactionPayload);
}
export function handleInternalMessageReactionRemoveEmoji(data: DiscordPayload) {
if (data.t !== "MESSAGE_REACTION_REMOVE_EMOJI") return;
eventHandlers.reactionRemoveEmoji?.(data.d as MessageReactionRemoveEmojiPayload);
}

View File

@@ -2,42 +2,25 @@ import {
DiscordBotGatewayData,
DiscordPayload,
GatewayOpcode,
PresenceUpdatePayload,
TypingStartPayload,
VoiceStateUpdatePayload,
WebhookUpdatePayload,
} from "../types/discord.ts";
import {
eventHandlers,
botGatewayData,
identifyPayload,
botID,
IdentifyPayload,
} from "./client.ts";
import { delay } from "https://deno.land/std@0.67.0/async/delay.ts";
import { Guild } from "../structures/guild.ts";
import {
GuildRolePayload,
UserPayload,
FetchMembersOptions,
GuildRoleDeletePayload,
} from "../types/guild.ts";
import { cache } from "../utils/cache.ts";
import {
MessageCreateOptions,
MessageDeletePayload,
MessageDeleteBulkPayload,
MessageReactionPayload,
BaseMessageReactionPayload,
MessageReactionRemoveEmojiPayload,
} from "../types/message.ts";
import {
createBasicShard,
requestGuildMembers,
botGatewayStatusRequest,
} from "./basicShard.ts";
import { BotStatusRequest } from "../utils/utils.ts";
import { structures } from "../structures/mod.ts";
import { controllers } from "../controllers/mod.ts";
let shardCounter = 0;
@@ -117,256 +100,7 @@ export async function handleDiscordPayload(
case GatewayOpcode.Dispatch:
if (!data.t) return;
// Run the appropriate controller for this event.
controllers[data.t]?.(data, shardID);
if (data.t === "MESSAGE_CREATE") {
const options = data.d as MessageCreateOptions;
const channel = cache.channels.get(options.channel_id);
if (channel) channel.lastMessageID = options.id;
const message = structures.createMessage(options);
// Cache the message
cache.messages.set(options.id, message);
const guild = options.guild_id
? cache.guilds.get(options.guild_id)
: undefined;
if (options.member) {
// If in a guild cache the author as a member
guild?.members.set(
options.author.id,
structures.createMember(
{ ...options.member, user: options.author },
guild,
),
);
}
options.mentions.forEach((mention) => {
// Cache the member if its a valid member
if (mention.member) {
guild?.members.set(
mention.id,
structures.createMember(
{ ...mention.member, user: mention },
guild,
),
);
}
});
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;
const channel = cache.channels.get(options.channel_id);
if (!channel) return;
deletedMessages.forEach((id) => {
const message = cache.messages.get(id);
if (!message) return;
eventHandlers.messageDelete?.(message || { id, channel });
cache.messages.delete(id);
});
}
if (data.t === "MESSAGE_UPDATE") {
const options = data.d as MessageCreateOptions;
const channel = cache.channels.get(options.channel_id);
if (!channel) return;
const cachedMessage = cache.messages.get(options.id);
if (!cachedMessage) return;
const oldMessage = {
attachments: cachedMessage.attachments,
content: cachedMessage.content,
embeds: cachedMessage.embeds,
editedTimestamp: cachedMessage.editedTimestamp,
tts: cachedMessage.tts,
pinned: cachedMessage.pinned,
};
// Messages with embeds can trigger update but they wont have edited_timestamp
if (
!options.edited_timestamp ||
(cachedMessage.content !== options.content)
) {
return;
}
return eventHandlers.messageUpdate?.(cachedMessage, oldMessage);
}
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 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 {
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];
}
cache.messages.set(options.message_id, message);
}
if (options.member && options.guild_id) {
const guild = cache.guilds.get(options.guild_id);
guild?.members.set(
options.member.user.id,
structures.createMember(
options.member,
guild,
),
);
}
const uncachedOptions = {
...options,
id: options.message_id,
channelID: options.channel_id,
guildID: options.guild_id,
};
return isAdd
? eventHandlers.reactionAdd?.(
message || uncachedOptions,
options.emoji,
options.user_id,
)
: eventHandlers.reactionRemove?.(
message || uncachedOptions,
options.emoji,
options.user_id,
);
}
if (data.t === "MESSAGE_REACTION_REMOVE_ALL") {
return eventHandlers.reactionRemoveAll?.(
data.d as BaseMessageReactionPayload,
);
}
if (data.t === "MESSAGE_REACTION_REMOVE_EMOJI") {
return eventHandlers.reactionRemoveEmoji?.(
data.d as MessageReactionRemoveEmojiPayload,
);
}
if (data.t === "PRESENCE_UPDATE") {
const payload = data.d as PresenceUpdatePayload;
const oldPresence = cache.presences.get(payload.user.id);
cache.presences.set(payload.user.id, payload);
return eventHandlers.presenceUpdate?.(payload, oldPresence);
}
if (data.t === "TYPING_START") {
return eventHandlers.typingStart?.(data.d as TypingStartPayload);
}
if (data.t === "USER_UPDATE") {
const userData = data.d as UserPayload;
cache.guilds.forEach((guild) => {
const member = guild.members.get(userData.id);
if (!member) return;
// member.author = userData;
Object.entries(userData).forEach(([key, value]) => {
// @ts-ignore
if (member[key] === value) return;
// @ts-ignore
member[key] = value;
});
});
return eventHandlers.botUpdate?.(userData);
}
if (data.t === "VOICE_STATE_UPDATE") {
const payload = data.d as VoiceStateUpdatePayload;
if (!payload.guild_id) return;
const guild = cache.guilds.get(payload.guild_id);
if (!guild) return;
const member = guild.members.get(payload.user_id) ||
(payload.member
? structures.createMember(payload.member, guild)
: undefined);
if (!member) return;
// No cached state before so lets make one for em
const cachedState = guild.voiceStates.get(payload.user_id);
guild.voiceStates.set(payload.user_id, {
...payload,
guildID: payload.guild_id,
channelID: payload.channel_id,
userID: payload.user_id,
sessionID: payload.session_id,
selfDeaf: payload.self_deaf,
selfMute: payload.self_mute,
selfStream: payload.self_stream,
});
if (cachedState?.channelID !== payload.channel_id) {
// Either joined or moved channels
if (payload.channel_id) {
cachedState?.channelID
? // Was in a channel before
eventHandlers.voiceChannelSwitch?.(
member,
payload.channel_id,
cachedState.channelID,
)
: // Was not in a channel before so user just joined
eventHandlers.voiceChannelJoin?.(member, payload.channel_id);
} // Left the channel
else if (cachedState?.channelID) {
guild.voiceStates.delete(payload.user_id);
eventHandlers.voiceChannelLeave?.(member, cachedState.channelID);
}
}
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,
);
}
return;
return controllers[data.t]?.(data, shardID);
default:
return;
}

View File

@@ -13,7 +13,7 @@ export let structures = {
createRole,
};
export type Structures = typeof structures[keyof typeof structures];
export type Structures = typeof structures;
/** This function is used to update/reload/customize the internal structure of Discordeno.
*

View File

@@ -28,7 +28,20 @@ export interface DiscordPayload {
| "GUILD_MEMBERS_CHUNK"
| "GUILD_ROLE_CREATE"
| "GUILD_ROLE_DELETE"
| "GUILD_ROLE_UPDATE";
| "GUILD_ROLE_UPDATE"
| "MESSAGE_CREATE"
| "MESSAGE_DELETE"
| "MESSAGE_DELETE_BULK"
| "MESSAGE_UPDATE"
| "MESSAGE_REACTION_ADD"
| "MESSAGE_REACTION_REMOVE"
| "MESSAGE_REACTION_REMOVE_ALL"
| "MESSAGE_REACTION_REMOVE_EMOJI"
| "PRESENCE_UPDATE"
| "TYPING_START"
| "USER_UPDATE"
| "VOICE_STATE_UPDATE"
| "WEBHOOKS_UPDATE";
}
export interface DiscordBotGatewayData {