Skip to content

Commit 6fc9a84

Browse files
committed
Revert plugins rework
Not very nice to work with...
1 parent d08f98d commit 6fc9a84

File tree

126 files changed

+8469
-535
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+8469
-535
lines changed

.vscode/settings.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,5 @@
2020
"javascript.format.semicolons": "insert",
2121
"typescript.format.semicolons": "insert",
2222
"typescript.preferences.importModuleSpecifier": "non-relative",
23-
"javascript.preferences.importModuleSpecifier": "non-relative",
24-
"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^zod(/(v3|v4-mini))?$"],
23+
"javascript.preferences.importModuleSpecifier": "non-relative"
2524
}

backend/package.json

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,33 @@
66
},
77
"scripts": {
88
"lint": "eslint && tsc",
9-
"bot": "pnpm run _node src/interface/discord/main.ts",
10-
"http": "pnpm run _node src/interface/http/main.ts",
11-
"migration": "pnpm run _node src/interface/migration/main.ts",
9+
"bot": "pnpm run _node src/bot/main.ts",
10+
"http": "pnpm run _node src/http/main.ts",
11+
"migrate": "pnpm run _node src/db/main.ts migrate --",
12+
"check-migrations": "pnpm run _node src/db/main.ts check --",
1213
"test": "node --import=./registerSWC.js --test src/**/*.test.ts",
1314
"_node": "node --env-file=.env --disable-proto=throw --disallow-code-generation-from-strings --experimental-transform-types"
1415
},
1516
"dependencies": {
1617
"@hono/node-server": "^1.14.4",
17-
"@hono/zod-validator": "^0.7.0",
18+
"@hono/valibot-validator": "^0.5.2",
1819
"async-lock": "^1.4.1",
19-
"hono": "^4.8.2",
20+
"hono": "^4.7.11",
2021
"oceanic-component-helper": "^1.0.1",
2122
"oceanic.js": "~1.12.0",
22-
"pg": "^8.16.3",
23+
"pg": "^8.16.0",
2324
"smol-toml": "^1.3.4",
24-
"zod": "^3.25.76"
25+
"valibot": "^1.1.0"
2526
},
2627
"devDependencies": {
27-
"@eslint/js": "^9.30.1",
28+
"@eslint/js": "^9.27.0",
2829
"@swc-node/register": "^1.10.10",
2930
"@types/async-lock": "^1.4.2",
30-
"@types/node": "^24.0.3",
31+
"@types/node": "^24.0.1",
3132
"@types/pg": "^8.15.2",
32-
"eslint": "^9.29.0",
33+
"eslint": "^9.27.0",
3334
"globals": "^16.2.0",
3435
"typescript": "^5.8.3",
35-
"typescript-eslint": "^8.36.0"
36+
"typescript-eslint": "^8.33.1"
3637
}
3738
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { isTextableGuildChannel, isThreadChannel } from "#bot/common/discord/general.ts";
2+
import { bot } from "#bot/index.ts";
3+
import { type AnyTextableGuildChannel, type AnyThreadChannel, DiscordRESTError, Guild, Member, PrivateChannel, type RequestGuildMembersOptions, type Uncached, User } from "oceanic.js";
4+
5+
export async function fetchUserCached(userID: string): Promise<User> {
6+
return bot.users.get(userID) ?? await bot.rest.users.get(userID);
7+
}
8+
9+
export async function fetchUserCachedSupressed(userID: string): Promise<User | Uncached> {
10+
try {
11+
return await fetchUserCached(userID);
12+
} catch (error) {
13+
if (!(error instanceof DiscordRESTError))
14+
throw error;
15+
16+
return { id: userID };
17+
}
18+
}
19+
20+
export async function fetchMemberCached(guild: Guild, userID: string): Promise<Member> {
21+
return guild.members.get(userID) ?? await bot.rest.guilds.getMember(guild.id, userID);
22+
}
23+
24+
export function fetchBotUserCached(guild: Guild): Promise<Member> {
25+
return fetchMemberCached(guild, bot.user.id);
26+
}
27+
28+
export async function createDMCached(userID: string): Promise<PrivateChannel> {
29+
return bot.privateChannels.find(channel => channel.recipient.id === userID) ?? await bot.rest.users.createDM(userID);
30+
}
31+
32+
export async function fetchThreadCached(guild: Guild, threadID: string): Promise<AnyThreadChannel | null> {
33+
const cached = guild.threads.get(threadID);
34+
35+
if (cached !== undefined)
36+
return cached;
37+
38+
if (bot.getChannel(threadID) !== undefined)
39+
return null;
40+
41+
const fetched = await bot.rest.channels.get(threadID);
42+
43+
if (!isThreadChannel(fetched))
44+
return null;
45+
46+
if (fetched.guildID !== guild.id)
47+
return null;
48+
49+
return fetched;
50+
}
51+
52+
export async function fetchTextableGuildChannelCached(guild: Guild, channelID: string): Promise<AnyTextableGuildChannel | null> {
53+
const cachedRegularChannel = guild.channels.get(channelID);
54+
55+
if (cachedRegularChannel !== undefined) {
56+
if (isTextableGuildChannel(cachedRegularChannel))
57+
return cachedRegularChannel;
58+
else
59+
return null;
60+
}
61+
62+
const cachedThreadChannel = guild.threads.get(channelID);
63+
64+
if (cachedThreadChannel !== undefined)
65+
return cachedThreadChannel;
66+
67+
// must belong to another guild
68+
if (bot.getChannel(channelID))
69+
return null;
70+
71+
const fetched = await bot.rest.channels.get(channelID);
72+
73+
if (!isTextableGuildChannel(fetched))
74+
return null;
75+
76+
if (fetched.guildID !== guild.id)
77+
return null;
78+
79+
return fetched;
80+
}
81+
82+
export async function fetchMembersCached(
83+
guild: Guild,
84+
userIDs: readonly string[],
85+
options?: Pick<RequestGuildMembersOptions, "presences" | "timeout">
86+
): Promise<Map<string, Member>> {
87+
const result: Map<string, Member> = new Map;
88+
const queue: string[] = [];
89+
const promises: Promise<unknown>[] = [];
90+
91+
const request = (): void => {
92+
promises.push(
93+
guild.shard.requestGuildMembers(guild.id, {
94+
userIDs: queue,
95+
...options,
96+
}).then(members => members.forEach(member => result.set(member.id, member)))
97+
);
98+
queue.length = 0;
99+
};
100+
101+
for (const id of userIDs) {
102+
const member = guild.members.get(id);
103+
if (member !== undefined)
104+
result.set(id, member);
105+
else {
106+
queue.push(id);
107+
if (queue.length === 100)
108+
request();
109+
}
110+
}
111+
112+
if (queue.length > 0)
113+
request();
114+
115+
await Promise.all(promises);
116+
return result;
117+
}
118+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const blurple = 0x5865F2;
2+
const green = 0x57F287;
3+
const yellow = 0xFEE75C;
4+
const fuchsia = 0xEB459E;
5+
const red = 0xED4245;
6+
const white = 0xFFFFFF;
7+
const black = 0x000000;
8+
9+
export const colors = Object.freeze({ blurple, green, yellow, fuchsia, red, white, black, __proto__: null });
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { bot } from "#bot/index.ts";
2+
import type { AnyChannel, Guild, Member, Uncached, User } from "oceanic.js";
3+
4+
export function debugFormatUser(user: User): string {
5+
return `@${user.tag}[${user.id}]`;
6+
}
7+
8+
export function debugFormatChannel(channel: AnyChannel): string {
9+
let name = "<unnamed>";
10+
11+
if ("name" in channel)
12+
name = channel.name ?? name;
13+
14+
return `#${name}[${channel.id}]`;
15+
}
16+
17+
export function debugFormatGuildByID(id: string): string {
18+
return debugFormatGuild(bot.guilds.get(id) ?? { id });
19+
}
20+
21+
export function debugFormatGuild(guild: Guild | Uncached): string {
22+
let name = "<unknown>";
23+
24+
if ("name" in guild)
25+
name = guild.name;
26+
27+
return `*${name}[${guild.id}]`;
28+
}
29+
30+
export function debugFormatPermissionContext(member: Member, channel: AnyChannel): string {
31+
return `${debugFormatUser(member.user)} in ${debugFormatChannel(channel)}, ${debugFormatGuild(member.guild)}`;
32+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { fetchUserCachedSupressed } from "#bot/common/discord/cachedRequest.ts";
2+
import { escapeMarkdown } from "#bot/common/discord/markdown.ts";
3+
import { DiscordRESTError, Member, User, UserFlags, type Uncached } from "oceanic.js";
4+
5+
export function formatRESTError(restError: DiscordRESTError): string {
6+
if (restError.resBody !== null
7+
&& typeof restError.resBody.message === "string") {
8+
return `API Error ${restError.code}: ${escapeMarkdown(restError.resBody.message)}`;
9+
}
10+
11+
return `HTTP Error ${restError.status}: ${escapeMarkdown(restError.statusText)}`;
12+
}
13+
14+
export async function formatUserTagByID(id: string): Promise<string> {
15+
return formatUserTag(await fetchUserCachedSupressed(id));
16+
}
17+
18+
export async function formatUserByID(id: string): Promise<string> {
19+
return formatUser(await fetchUserCachedSupressed(id));
20+
}
21+
22+
export async function formatUserBoldByID(id: string): Promise<string> {
23+
return formatUserBold(await fetchUserCachedSupressed(id));
24+
}
25+
26+
type UserLike = { id: string; tag: string; } | Uncached;
27+
28+
export function formatUser(user: UserLike): string {
29+
return `${formatUserTag(user)} (<@${user.id}>)`;
30+
}
31+
32+
export function formatUserBold(user: UserLike): string {
33+
return `**${formatUserTag(user)}** (<@${user.id}>)`;
34+
}
35+
36+
export function formatUserTag(user: UserLike): string {
37+
if ("tag" in user)
38+
return escapeMarkdown(user.tag);
39+
else
40+
return "\\<unknown\\>";
41+
}
42+
43+
/** This should be used in user lookup commands to nicely present information next to the name which otherwise would be displayed elsewhere. */
44+
export function formatUserTagRich(user: User | Member): string {
45+
if ("user" in user)
46+
user = user.user;
47+
48+
let result = escapeMarkdown(user.tag || user.globalName || "<unknown>");
49+
50+
if (user.clan !== null)
51+
result += " \\[" + escapeMarkdown(user.clan.tag) + "\\]";
52+
53+
if (user.system)
54+
result += " \\[SYSTEM\\]";
55+
else if (user.bot) {
56+
if (user.publicFlags & UserFlags.VERIFIED_BOT)
57+
result += " \\[✔\u8201APP]\\";
58+
else
59+
result += " \\[APP\\]";
60+
}
61+
62+
return result;
63+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Channel, ChannelTypes, MessageTypes, TextableChannelTypes, TextableGuildChannelTypes, ThreadChannelTypes, UndeletableMessageTypes, type AnyTextableChannel, type AnyTextableGuildChannel, type AnyThreadChannel, type TextableChannels, type TextableGuildChannels, type ThreadChannels } from "oceanic.js";
2+
3+
export function isThreadChannel(channel: Channel): channel is AnyThreadChannel {
4+
return isThreadChannelType(channel.type);
5+
}
6+
7+
export function isThreadChannelType(type: ChannelTypes): type is ThreadChannels {
8+
return ThreadChannelTypes.includes(type as ThreadChannels);
9+
}
10+
11+
export function isTextableChannel(channel: Channel): channel is AnyTextableChannel {
12+
return isTextableChannelType(channel.type);
13+
}
14+
15+
export function isTextableChannelType(type: ChannelTypes): type is TextableChannels {
16+
return TextableChannelTypes.includes(type as TextableChannels);
17+
}
18+
19+
export function isTextableGuildChannel(channel: Channel): channel is AnyTextableGuildChannel {
20+
return isTextableGuildChannelType(channel.type);
21+
}
22+
23+
export function isTextableGuildChannelType(type: ChannelTypes): type is TextableGuildChannels {
24+
return TextableGuildChannelTypes.includes(type as TextableGuildChannels);
25+
}
26+
27+
export function isUndeletableMessageType(type: MessageTypes): type is typeof UndeletableMessageTypes[number] {
28+
return UndeletableMessageTypes.includes(type as typeof UndeletableMessageTypes[number]);
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// everything where an escape is valid and the character sometimes affects formatting
2+
// (all punctuation seems to be possible to escape?)
3+
// perhaps this is not the best way to do this but it should work everywhere apart from inside codeblocks
4+
const FORMATTING_REGEX = /[\\/*_\-`#@<>.~|:[\]()]/g;
5+
const ZWSP = "\u200B";
6+
7+
/**
8+
* Escape all characters used for markdown formatting.
9+
* Mainly used whenever a username is being displayed.
10+
* Make sure that the field supports formatting - a common mistake is to use in embed titles which do not have foramtting applied!
11+
* !! Do not use as a replacement for allowedMentions or SUPRESS_EMBEDS !!
12+
*/
13+
export function escapeMarkdown(input: string): string {
14+
return input.replace(FORMATTING_REGEX, "\\$&");
15+
}
16+
17+
export function makeMarkdownInlineCodeblock(input: string): string {
18+
if (input.length === 0)
19+
return "`" + ZWSP + "`";
20+
21+
return "``" + input.replaceAll("`", ZWSP + "`" + ZWSP) + "``";
22+
}
23+
24+
export function makeMarkdownMultilineCodeblock(input: string): string {
25+
return "```\n" + input.replaceAll("`", ZWSP + "`" + ZWSP) + "\n```";
26+
}
27+
28+
export function makeMarkdownQuote(input: string): string {
29+
return "> " + input.replaceAll("\n", "\n> ");
30+
}

0 commit comments

Comments
 (0)