mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-03 05:26:07 +00:00
Merge branch 'main' of https://github.com/oasisjs/biscuit
This commit is contained in:
commit
91418dc936
2
Makefile
2
Makefile
@ -1,2 +0,0 @@
|
||||
deno-example:
|
||||
deno run --allow-read --allow-env --allow-net examples/deno.ts
|
@ -40,12 +40,10 @@ that you should not make software that does things it is not supposed to do.
|
||||
### Example bot (TS/JS)
|
||||
|
||||
```js
|
||||
import { GatewayIntents, Session } from "@oasisjs/biscuit";
|
||||
|
||||
const token = "your token goes here";
|
||||
import Biscuit, { GatewayIntents } from "@oasisjs/biscuit";
|
||||
|
||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
||||
const session = new Session({ token, intents });
|
||||
const session = new Biscuit({ token: "your token", intents });
|
||||
|
||||
session.on("ready", ({ user }) => {
|
||||
console.log("Logged in as:", user.username);
|
||||
|
50
egg.json
50
egg.json
@ -1,27 +1,27 @@
|
||||
{
|
||||
"$schema": "https://x.nest.land/eggs@0.3.10/src/schema.json",
|
||||
"name": "biscuit",
|
||||
"entry": "./mod.ts",
|
||||
"description": "A brand new bleeding edge non bloated Discord library",
|
||||
"homepage": "https://github.com/oasisjs/biscuit",
|
||||
"version": "0.1.0",
|
||||
"releaseType": "patch",
|
||||
"unstable": false,
|
||||
"unlisted": false,
|
||||
"files": [
|
||||
"./packages/**/*",
|
||||
"./mod.ts",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"ignore": [
|
||||
"npm",
|
||||
"build.ts",
|
||||
"scripts.ts",
|
||||
".git"
|
||||
],
|
||||
"checkFormat": true,
|
||||
"checkTests": false,
|
||||
"checkInstallation": false,
|
||||
"check": true
|
||||
"$schema": "https://x.nest.land/eggs@0.3.10/src/schema.json",
|
||||
"name": "biscuit",
|
||||
"entry": "./mod.ts",
|
||||
"description": "A brand new bleeding edge non bloated Discord library",
|
||||
"homepage": "https://github.com/oasisjs/biscuit",
|
||||
"version": "0.1.1",
|
||||
"releaseType": "patch",
|
||||
"unstable": false,
|
||||
"unlisted": false,
|
||||
"files": [
|
||||
"./packages/**/*",
|
||||
"./mod.ts",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"ignore": [
|
||||
"npm",
|
||||
"build.ts",
|
||||
"scripts.ts",
|
||||
".git"
|
||||
],
|
||||
"checkFormat": true,
|
||||
"checkTests": false,
|
||||
"checkInstallation": false,
|
||||
"check": true
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
GatewayIntents,
|
||||
InteractionResponseTypes,
|
||||
Session,
|
||||
} from "https://x.nest.land/biscuit/mod.ts";
|
||||
} from "https://deno.land/x/biscuit/mod.ts";
|
||||
|
||||
const token = Deno.env.get("TOKEN") ?? Deno.args[0];
|
||||
|
||||
@ -32,7 +32,6 @@ session.on("messageCreate", (message) => {
|
||||
|
||||
const args = message.content.substring(PREFIX.length).trim().split(/\s+/gm);
|
||||
const name = args.shift()?.toLowerCase();
|
||||
console.log(args, name);
|
||||
|
||||
if (name === "ping") {
|
||||
message.reply({ components: [row] });
|
||||
|
@ -3,10 +3,10 @@
|
||||
* this example should work on most systems, but if it doesn't just clone the library and import everything from mod.ts
|
||||
*/
|
||||
|
||||
const { GatewayIntents, Session } = require("@oasisjs/biscuit");
|
||||
const { GatewayIntents, Session } = require("../mod.ts");
|
||||
|
||||
// if it didn't worked use:
|
||||
// const { GatewayIntents, Session } = require("@oasisjs/biscuit");
|
||||
// import { GatewayIntents, Session } from "@oasisjs/biscuit";
|
||||
|
||||
const token = process.env.TOKEN;
|
||||
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages;
|
||||
|
@ -5,7 +5,7 @@
|
||||
import "https://deno.land/std@0.146.0/dotenv/load.ts";
|
||||
|
||||
// Session to create a new bot (and intents)
|
||||
import { GatewayIntents, Session } from "https://x.nest.land/biscuit/mod.ts";
|
||||
import { GatewayIntents, Session } from "https://deno.land/x/biscuit/mod.ts";
|
||||
|
||||
const token = Deno.env.get("TOKEN") ?? Deno.args[0];
|
||||
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
GatewayIntents,
|
||||
InteractionResponseTypes,
|
||||
Session,
|
||||
} from "https://x.nest.land/biscuit/mod.ts";
|
||||
} from "https://deno.land/x/biscuit/mod.ts";
|
||||
|
||||
const token = Deno.env.get("TOKEN") ?? Deno.args[0];
|
||||
|
||||
@ -20,14 +20,12 @@ const command: CreateApplicationCommand = {
|
||||
description: "Replies with pong!",
|
||||
};
|
||||
|
||||
const guildId = "";
|
||||
|
||||
session.on("ready", async (payload) => {
|
||||
console.log("Logged in as:", payload.user.username);
|
||||
console.log("Creating the application commands...");
|
||||
// create command
|
||||
try {
|
||||
await session.createApplicationCommand(command, guildId);
|
||||
await session.createApplicationCommand(command);
|
||||
console.log("Done!");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@ -36,7 +34,10 @@ session.on("ready", async (payload) => {
|
||||
|
||||
// Follow interaction event
|
||||
session.on("interactionCreate", (interaction) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
if (!interaction.isCommand()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interaction.commandName === "ping") {
|
||||
interaction.respond({
|
||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
||||
|
@ -67,7 +67,7 @@ import {
|
||||
} from "./structures/MessageReaction.ts";
|
||||
|
||||
export type RawHandler<T> = (...args: [Session, number, T]) => void;
|
||||
export type Handler<T extends unknown[]> = (...args: T) => unknown;
|
||||
export type Handler<T extends [obj?: unknown, ddy?: unknown]> = (...args: T) => unknown;
|
||||
|
||||
export const READY: RawHandler<DiscordReady> = (session, shardId, payload) => {
|
||||
session.applicationId = payload.application.id;
|
||||
@ -435,4 +435,5 @@ export interface Events {
|
||||
"webhooksUpdate": Handler<[{ guildId: Snowflake, channelId: Snowflake }]>;
|
||||
"userUpdate": Handler<[User]>;
|
||||
"presenceUpdate": Handler<[Presence]>;
|
||||
"debug": Handler<[string]>;
|
||||
}
|
||||
|
192
packages/cache/Collection.ts
vendored
Normal file
192
packages/cache/Collection.ts
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
|
||||
import type { Session, Snowflake } from "./deps.ts";
|
||||
|
||||
export class Collection<V> extends Map<Snowflake, V> {
|
||||
constructor(session: Session, entries?: Iterable<readonly [Snowflake, V]>) {
|
||||
super(entries);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
readonly session: Session;
|
||||
|
||||
get(key: Snowflake): V | undefined {
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
set(key: Snowflake, value: V): this {
|
||||
return super.set(key, value);
|
||||
}
|
||||
|
||||
has(key: Snowflake): boolean {
|
||||
return super.has(key);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
return super.clear();
|
||||
}
|
||||
|
||||
random(): V | undefined;
|
||||
random(amount: number): V[];
|
||||
random(amount?: number): V | V[] | undefined {
|
||||
const arr = [...this.values()];
|
||||
if (typeof amount === "undefined") return arr[Math.floor(Math.random() * arr.length)];
|
||||
if (!arr.length) return [];
|
||||
if (amount && amount > arr.length) amount = arr.length;
|
||||
return Array.from(
|
||||
{ length: Math.min(amount, arr.length) },
|
||||
(): V => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
|
||||
);
|
||||
}
|
||||
|
||||
find(fn: (value: V, key: Snowflake, structCache: this) => boolean): V | undefined {
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (fn(value, key, this)) return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
filter(fn: (value: V, key: Snowflake, structCache: this) => boolean): Collection<V> {
|
||||
const result = new Collection<V>(this.session);
|
||||
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (fn(value, key, this)) result.set(key, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
forEach<T>(fn: (value: V, key: Snowflake, structCache: this) => T): void {
|
||||
super.forEach((v: V, k: Snowflake) => {
|
||||
fn(v, k, this);
|
||||
});
|
||||
}
|
||||
|
||||
clone(): Collection<V> {
|
||||
return new Collection(this.session, this.entries());
|
||||
}
|
||||
|
||||
concat(structures: Collection<V>[]): Collection<V> {
|
||||
const conc = this.clone();
|
||||
|
||||
for (const structure of structures) {
|
||||
if (!structure || !(structure instanceof Collection)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of structure.entries()) {
|
||||
conc.set(key, value);
|
||||
}
|
||||
}
|
||||
return conc;
|
||||
}
|
||||
|
||||
some(fn: (value: V, key: Snowflake, structCache: this) => boolean): boolean {
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (fn(value, key, this)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
every(fn: (value: V, key: Snowflake, structCache: this) => boolean): boolean {
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (!fn(value, key, this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
first(): V | undefined;
|
||||
first(amount: number): V[];
|
||||
first(amount?: number): V | V[] | undefined {
|
||||
if (!amount || amount <= 1) {
|
||||
return this.values().next().value;
|
||||
}
|
||||
const values = [...this.values()];
|
||||
amount = Math.min(values.length, amount);
|
||||
return values.slice(0, amount);
|
||||
}
|
||||
|
||||
last(): V | undefined;
|
||||
last(amount: number): V[];
|
||||
last(amount?: number): V | V[] | undefined {
|
||||
const values = [...this.values()];
|
||||
if (!amount || amount <= 1) {
|
||||
return values[values.length - 1];
|
||||
}
|
||||
amount = Math.min(values.length, amount);
|
||||
return values.slice(-amount);
|
||||
}
|
||||
|
||||
reverse(): this {
|
||||
const entries = [...this.entries()].reverse();
|
||||
this.clear();
|
||||
for (const [key, value] of entries) this.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
map<T>(fn: (value: V, key: Snowflake, collection: this) => T): T[] {
|
||||
const result: T[] = [];
|
||||
for (const [key, value] of this.entries()) {
|
||||
result.push(fn(value, key, this));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
reduce<T>(fn: (acc: T, value: V, key: Snowflake, structCache: this) => T, initV?: T): T {
|
||||
const entries = this.entries();
|
||||
const first = entries.next().value;
|
||||
let result = initV;
|
||||
if (result !== undefined) {
|
||||
result = fn(result, first[1], first[0], this);
|
||||
} else {
|
||||
result = first;
|
||||
}
|
||||
for (const [key, value] of entries) {
|
||||
result = fn(result!, value, key, this);
|
||||
}
|
||||
return result!;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return super.size;
|
||||
}
|
||||
|
||||
get empty(): boolean {
|
||||
return this.size === 0;
|
||||
}
|
||||
|
||||
updateFields(key: Snowflake, obj: Partial<V>) {
|
||||
const value = this.get(key);
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const prop in obj) {
|
||||
if (obj[prop]) {
|
||||
value[prop] = obj[prop]!;
|
||||
}
|
||||
}
|
||||
|
||||
return this.set(key, value);
|
||||
}
|
||||
|
||||
getOr(key: Snowflake, or: V): V | undefined {
|
||||
return this.get(key) ?? or;
|
||||
}
|
||||
|
||||
retrieve<T>(key: Snowflake, fn: (value: V) => T) {
|
||||
const value = this.get(key);
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
return fn(value);
|
||||
}
|
||||
}
|
||||
|
||||
export default Collection;
|
70
packages/cache/channels.ts
vendored
Normal file
70
packages/cache/channels.ts
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
import type {
|
||||
ChannelInGuild,
|
||||
ChannelWithMessagesInGuild,
|
||||
ChannelTypes,
|
||||
DiscordChannel,
|
||||
Snowflake,
|
||||
} from "./deps.ts";
|
||||
import type { CachedMessage } from "./messages.ts";
|
||||
import type { CachedGuild } from "./guilds.ts";
|
||||
import type { SessionCache } from "./mod.ts";
|
||||
import { Collection } from "./Collection.ts";
|
||||
import { ChannelFactory, DMChannel, textBasedChannels } from "./deps.ts";
|
||||
|
||||
export interface CachedGuildChannel extends Omit<ChannelWithMessagesInGuild, "type"> {
|
||||
type: ChannelTypes;
|
||||
messages: Collection<CachedMessage>;
|
||||
guild: CachedGuild;
|
||||
guildId: Snowflake;
|
||||
}
|
||||
|
||||
export interface CachedGuildChannel extends Omit<ChannelInGuild, "type"> {
|
||||
type: ChannelTypes;
|
||||
guild: CachedGuild;
|
||||
guildId: Snowflake;
|
||||
}
|
||||
|
||||
export interface CachedDMChannel extends DMChannel {
|
||||
messages: Collection<CachedMessage>;
|
||||
}
|
||||
|
||||
export function channelBootstrapper(cache: SessionCache, channel: DiscordChannel) {
|
||||
if (!channel.guild_id) {
|
||||
cache.dms.set(channel.id, Object.assign(new DMChannel(cache.session, channel), {
|
||||
messages: new Collection<CachedMessage>(cache.session),
|
||||
}))
|
||||
return;
|
||||
}
|
||||
|
||||
cache.guilds.retrieve(channel.guild_id, (guild) => {
|
||||
if (textBasedChannels.includes(channel.type)) {
|
||||
guild.channels.set(
|
||||
channel.id,
|
||||
Object.assign(
|
||||
ChannelFactory.fromGuildChannel(cache.session, channel) as ChannelWithMessagesInGuild,
|
||||
{
|
||||
messages: new Collection<CachedMessage>(cache.session),
|
||||
guildId: channel.guild_id!,
|
||||
get guild(): CachedGuild {
|
||||
return cache.guilds.get(this.guildId)!;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
guild.channels.set(
|
||||
channel.id,
|
||||
<CachedGuildChannel>Object.assign(
|
||||
ChannelFactory.fromGuildChannel(cache.session, channel),
|
||||
{
|
||||
guildId: channel.guild_id!,
|
||||
get guild(): CachedGuild {
|
||||
return cache.guilds.get(this.guildId)!;
|
||||
}
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
53
packages/cache/guilds.ts
vendored
Normal file
53
packages/cache/guilds.ts
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
import type {
|
||||
DiscordGuild,
|
||||
DiscordMemberWithUser,
|
||||
} from "./deps.ts";
|
||||
import type { SessionCache } from "./mod.ts";
|
||||
import type { CachedMember } from "./members.ts";
|
||||
import type { CachedUser } from "./users.ts";
|
||||
import type { CachedGuildChannel } from "./channels.ts";
|
||||
import { ChannelFactory, Guild, Member } from "./deps.ts";
|
||||
import { Collection } from "./Collection.ts";
|
||||
|
||||
export interface CachedGuild extends Omit<Guild, "members" | "channels"> {
|
||||
channels: Collection<CachedGuildChannel>;
|
||||
members: Collection<CachedMember>;
|
||||
}
|
||||
|
||||
export function guildBootstrapper(cache: SessionCache, guild: DiscordGuild) {
|
||||
const members = new Collection(
|
||||
cache.session,
|
||||
guild.members?.map((data) => {
|
||||
const obj: CachedMember = Object.assign(
|
||||
new Member(cache.session, data as DiscordMemberWithUser, guild.id),
|
||||
{
|
||||
userId: data.user!.id,
|
||||
get user(): CachedUser | undefined {
|
||||
return cache.users.get(this.userId);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return [data.user!.id, obj as CachedMember];
|
||||
}),
|
||||
);
|
||||
|
||||
const channels = new Collection(
|
||||
cache.session,
|
||||
guild.channels?.map((data) => {
|
||||
const obj = Object.assign(ChannelFactory.from(cache.session, data), {
|
||||
messages: new Map(),
|
||||
});
|
||||
|
||||
return [data.id, obj as CachedGuildChannel];
|
||||
}),
|
||||
);
|
||||
|
||||
cache.guilds.set(
|
||||
guild.id,
|
||||
Object.assign(
|
||||
new Guild(cache.session, guild),
|
||||
{ members, channels },
|
||||
),
|
||||
);
|
||||
}
|
26
packages/cache/members.ts
vendored
Normal file
26
packages/cache/members.ts
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
import type { DiscordMemberWithUser, Snowflake } from "./deps.ts";
|
||||
import type { SessionCache } from "./mod.ts";
|
||||
import type { CachedUser } from "./users.ts";
|
||||
import { Member } from "./deps.ts";
|
||||
|
||||
export interface CachedMember extends Omit<Member, "user"> {
|
||||
userId: Snowflake;
|
||||
user?: CachedUser;
|
||||
}
|
||||
|
||||
export function memberBootstrapper(cache: SessionCache, member: DiscordMemberWithUser, guildId: Snowflake) {
|
||||
cache.guilds.retrieve(guildId, (guild) => {
|
||||
guild.members.set(
|
||||
member.user.id,
|
||||
Object.assign(
|
||||
new Member(cache.session, member, guildId),
|
||||
{
|
||||
userId: member.user.id,
|
||||
get user(): CachedUser | undefined {
|
||||
return cache.users.get(this.userId);
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
150
packages/cache/messages.ts
vendored
Normal file
150
packages/cache/messages.ts
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
import type {
|
||||
DiscordEmoji,
|
||||
DiscordMessage,
|
||||
DiscordMemberWithUser,
|
||||
DiscordMessageReactionAdd,
|
||||
DiscordMessageReactionRemove,
|
||||
DiscordMessageReactionRemoveAll,
|
||||
Snowflake
|
||||
} from "./deps.ts";
|
||||
import type { CachedUser } from "./users.ts";
|
||||
import type { SessionCache } from "./mod.ts";
|
||||
import { Emoji, GuildEmoji, Message, MessageReaction } from "./deps.ts";
|
||||
import { memberBootstrapper } from "./members.ts";
|
||||
|
||||
export interface CachedMessage extends Omit<Message, "author"> {
|
||||
authorId: Snowflake;
|
||||
author?: CachedUser;
|
||||
}
|
||||
|
||||
export function messageBootstrapper(cache: SessionCache, message: DiscordMessage, partial: boolean) {
|
||||
if (message.member) {
|
||||
const member: DiscordMemberWithUser = Object.assign(message.member, { user: message.author });
|
||||
|
||||
memberBootstrapper(cache, member, message.guild_id!);
|
||||
}
|
||||
|
||||
if (cache.dms.has(message.channel_id)) {
|
||||
// is dm
|
||||
cache.dms.retrieve(message.channel_id, (dm) => {
|
||||
dm.messages[partial ? "updateFields" : "set"](
|
||||
message.id,
|
||||
Object.assign(
|
||||
new Message(cache.session, message),
|
||||
{
|
||||
authorId: message.author.id,
|
||||
get author(): CachedUser | undefined {
|
||||
return cache.users.get(this.authorId);
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// is not dm
|
||||
cache.guilds.retrieve(message.guild_id!, (guild) =>
|
||||
guild.channels.retrieve(message.channel_id, (dm) => {
|
||||
dm.messages[partial ? "updateFields" : "set"](
|
||||
message.id,
|
||||
Object.assign(
|
||||
new Message(cache.session, message),
|
||||
{
|
||||
authorId: message.author.id,
|
||||
get author(): CachedUser | undefined {
|
||||
return cache.users.get(this.authorId);
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export function reactionBootstrapper(
|
||||
cache: SessionCache,
|
||||
reaction: DiscordMessageReactionAdd | DiscordMessageReactionRemove,
|
||||
remove: boolean,
|
||||
) {
|
||||
cache.emojis.set(reaction.emoji.id ?? reaction.emoji.name!, new Emoji(cache.session, reaction.emoji));
|
||||
|
||||
function onAdd(message: CachedMessage) {
|
||||
const reactions = message.reactions.map((r) => r.emoji.name);
|
||||
|
||||
const upsertData = {
|
||||
count: 1,
|
||||
emoji: reaction.emoji,
|
||||
me: reaction.user_id === cache.session.botId,
|
||||
};
|
||||
|
||||
if (reactions.length === 0) {
|
||||
message.reactions = [];
|
||||
} else if (!reactions.includes(reaction.emoji.name)) {
|
||||
message.reactions.push(new MessageReaction(cache.session, upsertData));
|
||||
} else {
|
||||
const current = message.reactions?.[reactions.indexOf(reaction.emoji.name)];
|
||||
|
||||
if (current && message.reactions?.[message.reactions.indexOf(current)]) {
|
||||
// add 1 to reaction count
|
||||
++message.reactions[message.reactions.indexOf(current)].count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onRemove(message: CachedMessage) {
|
||||
const reactions = message.reactions.map((r) => r.emoji.name);
|
||||
|
||||
if (reactions.indexOf(reaction.emoji.name) !== undefined) {
|
||||
const current = message.reactions[reactions.indexOf(reaction.emoji.name)];
|
||||
|
||||
if (current) {
|
||||
if (current.count > 0) {
|
||||
current.count--;
|
||||
}
|
||||
if (current.count === 0) {
|
||||
message.reactions.splice(reactions?.indexOf(reaction.emoji.name), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reaction.guild_id) {
|
||||
cache.guilds.retrieve(reaction.guild_id, (guild) => {
|
||||
guild.channels.retrieve(reaction.channel_id, (channel) => {
|
||||
channel.messages.retrieve(reaction.message_id, (message) => {
|
||||
if (remove) onRemove(message);
|
||||
else onAdd(message);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cache.dms.retrieve(reaction.channel_id, (channel) => {
|
||||
channel.messages.retrieve(reaction.message_id, (message) => {
|
||||
if (remove) onRemove(message);
|
||||
else onAdd(message);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function reactionBootstrapperDeletions(cache: SessionCache, payload: DiscordMessageReactionRemoveAll) {
|
||||
if (payload.guild_id) {
|
||||
cache.guilds.retrieve(payload.guild_id, (guild) => {
|
||||
guild.channels.retrieve(payload.channel_id, (channel) => {
|
||||
channel.messages.retrieve(payload.message_id, (message: CachedMessage) => {
|
||||
message.reactions = [];
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cache.dms.retrieve(payload.channel_id, (channel) => {
|
||||
channel.messages.retrieve(payload.message_id, (message) => {
|
||||
message.reactions = [];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function emojiBootstrapper(cache: SessionCache, emoji: DiscordEmoji, guildId: Snowflake) {
|
||||
if (!emoji.id) return;
|
||||
cache.emojis.set(emoji.id, new GuildEmoji(cache.session, emoji, guildId));
|
||||
}
|
507
packages/cache/mod.ts
vendored
507
packages/cache/mod.ts
vendored
@ -1,93 +1,34 @@
|
||||
import type {
|
||||
DiscordChannel,
|
||||
DiscordEmoji,
|
||||
DiscordGuild,
|
||||
DiscordMemberWithUser,
|
||||
DiscordMessage,
|
||||
DiscordMessageReactionAdd,
|
||||
DiscordMessageReactionRemove,
|
||||
DiscordMessageReactionRemoveAll,
|
||||
DiscordMessageReactionRemoveEmoji,
|
||||
DiscordUser,
|
||||
Session,
|
||||
Snowflake,
|
||||
SymCache,
|
||||
} from "./deps.ts";
|
||||
|
||||
import type { Emoji, Session, SymCache } from "./deps.ts";
|
||||
import type { CachedGuild } from "./guilds.ts";
|
||||
import type { CachedUser } from "./users.ts";
|
||||
import type { CachedDMChannel } from "./channels.ts";
|
||||
import { Collection } from "./Collection.ts";
|
||||
import { memberBootstrapper } from "./members.ts";
|
||||
import { userBootstrapper } from "./users.ts";
|
||||
import { channelBootstrapper } from "./channels.ts";
|
||||
import { guildBootstrapper } from "./guilds.ts";
|
||||
import {
|
||||
ChannelFactory,
|
||||
ChannelTypes,
|
||||
DMChannel,
|
||||
Emoji,
|
||||
Guild,
|
||||
GuildChannel,
|
||||
GuildEmoji,
|
||||
GuildTextChannel,
|
||||
Member,
|
||||
Message,
|
||||
MessageReaction,
|
||||
NewsChannel,
|
||||
textBasedChannels,
|
||||
ThreadChannel,
|
||||
User,
|
||||
VoiceChannel,
|
||||
} from "./deps.ts";
|
||||
messageBootstrapper,
|
||||
reactionBootstrapper,
|
||||
reactionBootstrapperDeletions
|
||||
} from "./messages.ts";
|
||||
|
||||
export const cache_sym = Symbol("@cache");
|
||||
|
||||
export interface CachedMessage extends Omit<Message, "author"> {
|
||||
authorId: Snowflake;
|
||||
author?: User;
|
||||
}
|
||||
|
||||
export interface CachedMember extends Omit<Member, "user"> {
|
||||
userId: Snowflake;
|
||||
user?: User;
|
||||
}
|
||||
|
||||
export interface CachedGuild extends Omit<Guild, "members" | "channels"> {
|
||||
channels: StructCache<CachedGuildChannel>;
|
||||
members: StructCache<CachedMember>;
|
||||
}
|
||||
|
||||
export interface CachedGuildChannel extends Omit<GuildTextChannel, "type"> {
|
||||
type: ChannelTypes;
|
||||
messages: StructCache<CachedMessage>;
|
||||
}
|
||||
|
||||
export interface CachedGuildChannel extends Omit<VoiceChannel, "type"> {
|
||||
type: ChannelTypes;
|
||||
messages: StructCache<CachedMessage>;
|
||||
}
|
||||
|
||||
export interface CachedGuildChannel extends Omit<NewsChannel, "type"> {
|
||||
type: ChannelTypes;
|
||||
messages: StructCache<CachedMessage>;
|
||||
}
|
||||
|
||||
export interface CachedGuildChannel extends Omit<ThreadChannel, "type"> {
|
||||
type: ChannelTypes;
|
||||
messages: StructCache<CachedMessage>;
|
||||
}
|
||||
|
||||
export interface CachedDMChannel extends DMChannel {
|
||||
messages: StructCache<CachedMessage>;
|
||||
}
|
||||
|
||||
export interface SessionCache extends SymCache {
|
||||
guilds: StructCache<CachedGuild>;
|
||||
users: StructCache<User>;
|
||||
dms: StructCache<CachedDMChannel>;
|
||||
emojis: StructCache<Emoji>;
|
||||
guilds: Collection<CachedGuild>;
|
||||
users: Collection<CachedUser>;
|
||||
dms: Collection<CachedDMChannel>;
|
||||
emojis: Collection<Emoji>;
|
||||
session: Session;
|
||||
}
|
||||
|
||||
export default function (session: Session): SessionCache {
|
||||
export function enableCache(session: Session): SessionCache {
|
||||
const cache = {
|
||||
guilds: new StructCache<CachedGuild>(session),
|
||||
users: new StructCache<User>(session),
|
||||
dms: new StructCache<CachedDMChannel>(session),
|
||||
emojis: new StructCache<Emoji>(session),
|
||||
guilds: new Collection<CachedGuild>(session),
|
||||
users: new Collection<CachedUser>(session),
|
||||
dms: new Collection<CachedDMChannel>(session),
|
||||
emojis: new Collection<Emoji>(session),
|
||||
cache: cache_sym,
|
||||
session,
|
||||
};
|
||||
@ -96,14 +37,17 @@ export default function (session: Session): SessionCache {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const raw = data.d as any;
|
||||
|
||||
if (!raw) return;
|
||||
|
||||
switch (data.t) {
|
||||
// TODO: add more events
|
||||
// for now users have to use the bootstrappers that are not implemented yet
|
||||
case "MESSAGE_CREATE":
|
||||
messageBootstrapper(cache, raw, false);
|
||||
break;
|
||||
case "MESSAGE_UPDATE":
|
||||
messageBootstrapper(cache, raw, !raw.edited_timestamp);
|
||||
break;
|
||||
case "CHANNEL_UPDATE":
|
||||
case "CHANNEL_CREATE":
|
||||
channelBootstrapper(cache, raw);
|
||||
break;
|
||||
@ -125,402 +69,15 @@ export default function (session: Session): SessionCache {
|
||||
case "MESSAGE_REACTION_REMOVE_ALL":
|
||||
reactionBootstrapperDeletions(cache, raw);
|
||||
break;
|
||||
case "READY":
|
||||
userBootstrapper(cache, raw.user);
|
||||
break;
|
||||
default:
|
||||
session.emit("debug", `NOT CACHED: ${JSON.stringify(raw)}`);
|
||||
}
|
||||
});
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
export class StructCache<V> extends Map<Snowflake, V> {
|
||||
constructor(session: Session, entries?: Iterable<readonly [Snowflake, V]>) {
|
||||
super(entries);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
readonly session: Session;
|
||||
|
||||
get(key: Snowflake): V | undefined {
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
set(key: Snowflake, value: V): this {
|
||||
return super.set(key, value);
|
||||
}
|
||||
|
||||
has(key: Snowflake): boolean {
|
||||
return super.has(key);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
return super.clear();
|
||||
}
|
||||
|
||||
random(): V | undefined;
|
||||
random(amount: number): V[];
|
||||
random(amount?: number): V | V[] | undefined {
|
||||
const arr = [...this.values()];
|
||||
if (typeof amount === "undefined") return arr[Math.floor(Math.random() * arr.length)];
|
||||
if (!arr.length) return [];
|
||||
if (amount && amount > arr.length) amount = arr.length;
|
||||
return Array.from(
|
||||
{ length: Math.min(amount, arr.length) },
|
||||
(): V => arr.splice(Math.floor(Math.random() * arr.length), 1)[0],
|
||||
);
|
||||
}
|
||||
|
||||
find(fn: (value: V, key: Snowflake, structCache: this) => boolean): V | undefined {
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (fn(value, key, this)) return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
filter(fn: (value: V, key: Snowflake, structCache: this) => boolean): StructCache<V> {
|
||||
const result = new StructCache<V>(this.session);
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (fn(value, key, this)) result.set(key, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
forEach<T>(fn: (value: V, key: Snowflake, structCache: this) => T): void {
|
||||
super.forEach((v: V, k: Snowflake) => {
|
||||
fn(v, k, this);
|
||||
});
|
||||
}
|
||||
|
||||
clone(): StructCache<V> {
|
||||
return new StructCache(this.session, this.entries());
|
||||
}
|
||||
|
||||
concat(structures: StructCache<V>[]): StructCache<V> {
|
||||
const conc = this.clone();
|
||||
|
||||
for (const structure of structures) {
|
||||
if (!structure || !(structure instanceof StructCache)) continue;
|
||||
for (const [key, value] of structure.entries()) {
|
||||
conc.set(key, value);
|
||||
}
|
||||
}
|
||||
return conc;
|
||||
}
|
||||
|
||||
some(fn: (value: V, key: Snowflake, structCache: this) => boolean): boolean {
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (fn(value, key, this)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
every(fn: (value: V, key: Snowflake, structCache: this) => boolean): boolean {
|
||||
for (const [key, value] of this.entries()) {
|
||||
if (!fn(value, key, this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
first(): V | undefined;
|
||||
first(amount: number): V[];
|
||||
first(amount?: number): V | V[] | undefined {
|
||||
if (!amount || amount <= 1) {
|
||||
return this.values().next().value;
|
||||
}
|
||||
const values = [...this.values()];
|
||||
amount = Math.min(values.length, amount);
|
||||
return values.slice(0, amount);
|
||||
}
|
||||
|
||||
last(): V | undefined;
|
||||
last(amount: number): V[];
|
||||
last(amount?: number): V | V[] | undefined {
|
||||
const values = [...this.values()];
|
||||
if (!amount || amount <= 1) {
|
||||
return values[values.length - 1];
|
||||
}
|
||||
amount = Math.min(values.length, amount);
|
||||
return values.slice(-amount);
|
||||
}
|
||||
|
||||
reverse(): this {
|
||||
const entries = [...this.entries()].reverse();
|
||||
this.clear();
|
||||
for (const [key, value] of entries) this.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
map<T>(fn: (value: V, key: Snowflake, collection: this) => T): T[] {
|
||||
const result: T[] = [];
|
||||
for (const [key, value] of this.entries()) {
|
||||
result.push(fn(value, key, this));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
reduce<T>(fn: (acc: T, value: V, key: Snowflake, structCache: this) => T, initV?: T): T {
|
||||
const entries = this.entries();
|
||||
const first = entries.next().value;
|
||||
let result = initV;
|
||||
if (result !== undefined) {
|
||||
result = fn(result, first[1], first[0], this);
|
||||
} else {
|
||||
result = first;
|
||||
}
|
||||
for (const [key, value] of entries) {
|
||||
result = fn(result!, value, key, this);
|
||||
}
|
||||
return result!;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return super.size;
|
||||
}
|
||||
|
||||
get empty(): boolean {
|
||||
return this.size === 0;
|
||||
}
|
||||
|
||||
updateFields(key: Snowflake, obj: Partial<V>) {
|
||||
const value = this.get(key);
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const prop in obj) {
|
||||
if (obj[prop]) {
|
||||
value[prop] = obj[prop]!;
|
||||
}
|
||||
}
|
||||
|
||||
return this.set(key, value);
|
||||
}
|
||||
|
||||
getOr(key: Snowflake, or: V): V | undefined {
|
||||
return this.get(key) ?? or;
|
||||
}
|
||||
|
||||
retrieve<T>(key: Snowflake, fn: (value: V) => T) {
|
||||
const value = this.get(key);
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
return fn(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function reactionBootstrapperDeletions(cache: SessionCache, payload: DiscordMessageReactionRemoveAll) {
|
||||
if (payload.guild_id) {
|
||||
cache.guilds.retrieve(payload.guild_id, (guild) => {
|
||||
guild.channels.retrieve(payload.channel_id, (channel) => {
|
||||
channel.messages.retrieve(payload.message_id, (message) => {
|
||||
message.reactions = [];
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cache.dms.retrieve(payload.channel_id, (channel) => {
|
||||
channel.messages.retrieve(payload.message_id, (message) => {
|
||||
message.reactions = [];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function reactionBootstrapper(
|
||||
cache: SessionCache,
|
||||
reaction: DiscordMessageReactionAdd | DiscordMessageReactionRemove,
|
||||
remove: boolean,
|
||||
) {
|
||||
cache.emojis.set(reaction.emoji.id ?? reaction.emoji.name!, new Emoji(cache.session, reaction.emoji));
|
||||
|
||||
function onAdd(message: CachedMessage) {
|
||||
const reactions = message.reactions.map((r) => r.emoji.name);
|
||||
|
||||
const upsertData = {
|
||||
count: 1,
|
||||
emoji: reaction.emoji,
|
||||
me: reaction.user_id === cache.session.botId,
|
||||
};
|
||||
|
||||
if (reactions.length === 0) {
|
||||
message.reactions = [];
|
||||
} else if (!reactions.includes(reaction.emoji.name)) {
|
||||
message.reactions.push(new MessageReaction(cache.session, upsertData));
|
||||
} else {
|
||||
const current = message.reactions?.[reactions.indexOf(reaction.emoji.name)];
|
||||
|
||||
if (current && message.reactions?.[message.reactions.indexOf(current)]) {
|
||||
// add 1 to reaction count
|
||||
++message.reactions[message.reactions.indexOf(current)].count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onRemove(message: CachedMessage) {
|
||||
const reactions = message.reactions.map((r) => r.emoji.name);
|
||||
|
||||
if (reactions.indexOf(reaction.emoji.name) !== undefined) {
|
||||
const current = message.reactions[reactions.indexOf(reaction.emoji.name)];
|
||||
|
||||
if (current) {
|
||||
if (current.count > 0) {
|
||||
current.count--;
|
||||
}
|
||||
if (current.count === 0) {
|
||||
message.reactions.splice(reactions?.indexOf(reaction.emoji.name), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reaction.guild_id) {
|
||||
cache.guilds.retrieve(reaction.guild_id, (guild) => {
|
||||
guild.channels.retrieve(reaction.channel_id, (channel) => {
|
||||
channel.messages.retrieve(reaction.message_id, (message) => {
|
||||
if (remove) onRemove(message);
|
||||
else onAdd(message);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cache.dms.retrieve(reaction.channel_id, (channel) => {
|
||||
channel.messages.retrieve(reaction.message_id, (message) => {
|
||||
if (remove) onRemove(message);
|
||||
else onAdd(message);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function userBootstrapper(cache: SessionCache, user: DiscordUser) {
|
||||
cache.users.set(user.id, new User(cache.session, user));
|
||||
}
|
||||
|
||||
export function emojiBootstrapper(cache: SessionCache, emoji: DiscordEmoji, guildId: Snowflake) {
|
||||
if (!emoji.id) return;
|
||||
cache.emojis.set(emoji.id, new GuildEmoji(cache.session, emoji, guildId));
|
||||
}
|
||||
|
||||
export function channelBootstrapper(cache: SessionCache, channel: DiscordChannel) {
|
||||
if (!channel.guild_id) return;
|
||||
|
||||
cache.guilds.retrieve(channel.guild_id, (guild) => {
|
||||
if (textBasedChannels.includes(channel.type)) {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
guild.channels.set(
|
||||
channel.id,
|
||||
<any> Object.assign(
|
||||
ChannelFactory.fromGuildChannel(cache.session, channel),
|
||||
{ messages: new StructCache<CachedMessage>(cache.session) },
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
guild.channels.set(channel.id, <any> ChannelFactory.fromGuildChannel(cache.session, channel));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function memberBootstrapper(cache: SessionCache, member: DiscordMemberWithUser, guildId: Snowflake) {
|
||||
cache.guilds.retrieve(guildId, (guild) => {
|
||||
guild.members.set(
|
||||
member.user.id,
|
||||
Object.assign(
|
||||
new Member(cache.session, member, guildId),
|
||||
{
|
||||
userId: member.user.id,
|
||||
get user(): User | undefined {
|
||||
return cache.users.get(this.userId);
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function messageBootstrapper(cache: SessionCache, message: DiscordMessage, partial: boolean) {
|
||||
if (message.member) {
|
||||
const member: DiscordMemberWithUser = Object.assign(message.member, { user: message.author });
|
||||
|
||||
memberBootstrapper(cache, member, message.guild_id!);
|
||||
}
|
||||
|
||||
if (cache.dms.has(message.channel_id)) {
|
||||
// is dm
|
||||
cache.dms.retrieve(message.channel_id, (dm) => {
|
||||
dm.messages[partial ? "updateFields" : "set"](
|
||||
message.id,
|
||||
Object.assign(
|
||||
new Message(cache.session, message),
|
||||
{
|
||||
authorId: message.author.id,
|
||||
get author(): User | undefined {
|
||||
return cache.users.get(this.authorId);
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// is not dm
|
||||
cache.guilds.retrieve(message.guild_id!, (guild) =>
|
||||
guild.channels.retrieve(message.channel_id, (dm) => {
|
||||
dm.messages[partial ? "updateFields" : "set"](
|
||||
message.id,
|
||||
Object.assign(
|
||||
new Message(cache.session, message),
|
||||
{
|
||||
authorId: message.author.id,
|
||||
get author(): User | undefined {
|
||||
return cache.users.get(this.authorId);
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export function guildBootstrapper(cache: SessionCache, guild: DiscordGuild) {
|
||||
const members = new StructCache(
|
||||
cache.session,
|
||||
guild.members?.map((data) => {
|
||||
const obj: CachedMember = Object.assign(
|
||||
new Member(cache.session, data as DiscordMemberWithUser, guild.id),
|
||||
{
|
||||
userId: data.user!.id,
|
||||
get user(): User | undefined {
|
||||
return cache.users.get(this.userId);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return [data.user!.id, obj as CachedMember];
|
||||
}),
|
||||
);
|
||||
|
||||
const channels = new StructCache(
|
||||
cache.session,
|
||||
guild.channels?.map((data) => {
|
||||
const obj = Object.assign(ChannelFactory.from(cache.session, data), {
|
||||
messages: new Map(),
|
||||
});
|
||||
|
||||
return [data.id, obj as CachedGuildChannel];
|
||||
}),
|
||||
);
|
||||
|
||||
cache.guilds.set(
|
||||
guild.id,
|
||||
Object.assign(
|
||||
new Guild(cache.session, guild),
|
||||
{ members, channels },
|
||||
),
|
||||
);
|
||||
}
|
||||
export default enableCache;
|
||||
|
9
packages/cache/users.ts
vendored
Normal file
9
packages/cache/users.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import type { DiscordUser } from "./deps.ts";
|
||||
import type { SessionCache } from "./mod.ts";
|
||||
import { User } from "./deps.ts";
|
||||
|
||||
export type CachedUser = User;
|
||||
|
||||
export function userBootstrapper(cache: SessionCache, user: DiscordUser) {
|
||||
cache.users.set(user.id, new User(cache.session, user));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user