diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index a4ca0bb9..8ab6ab5a 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,11 +1,11 @@
blank_issues_enabled: true
contact_links:
- - name: Fosscord Documentation
- url: https://docs.fosscord.com/
- about: Need documentation and examples for the Fosscord? Head over to Fosscord's official documentation.
- - name: Discord's Developer Documentation
- url: https://discord.com/developers/docs/intro
- about: Need help with the Discord resources? Head here instead of asking on Fosscord!
- - name: Fosscord' Official Discord server
- url: https://discord.com/invite/Ms5Ev7S6bF
- about: Need help with the server? Talk with us in our official server.
+ - name: Fosscord Documentation
+ url: https://docs.fosscord.com/
+ about: Need documentation and examples for the Fosscord? Head over to Fosscord's official documentation.
+ - name: Discord's Developer Documentation
+ url: https://discord.com/developers/docs/intro
+ about: Need help with the Discord resources? Head here instead of asking on Fosscord!
+ - name: Fosscord' Official Discord server
+ url: https://discord.com/invite/Ms5Ev7S6bF
+ about: Need help with the server? Talk with us in our official server.
diff --git a/.github/relase_body_template.md b/.github/relase_body_template.md
index 994e83d3..c410b0c2 100644
--- a/.github/relase_body_template.md
+++ b/.github/relase_body_template.md
@@ -1,13 +1,17 @@
## Notes
## Additions
--
+
+-
## Fixes
+
-
+
## Download
-- [Windows]()
-- [MacOS]()
-- [Linux]()
+
+- [Windows]()
+- [MacOS]()
+- [Linux]()
After (extracting) and starting the server executable you can access your own Fosscord server on http://localhost:3001/
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..49bc63ad
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+assets
+dist
\ No newline at end of file
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 00000000..7f0d7f20
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,11 @@
+{
+ "trailingComma": "all",
+ "tabWidth": 4,
+ "semi": true,
+ "arrowParens": "always",
+ "bracketSameLine": false,
+ "bracketSpacing": true,
+ "quoteProps": "as-needed",
+ "useTabs": true,
+ "singleQuote": false
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 86eb202e..09a69132 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -8,21 +8,17 @@
"name": "Launch current file",
"program": "${relativeFile}",
"request": "launch",
- "skipFiles": [
- "<node_internals>/**"
- ],
+ "skipFiles": ["<node_internals>/**"],
"type": "node"
},
{
"type": "node",
"request": "launch",
"name": "Bundle",
- "skipFiles": [
- "<node_internals>/**"
- ],
+ "skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/src/bundle/start.ts",
- "outFiles": [ "${workspaceFolder}/dist/**/*.js" ],
+ "outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "tsc: build - tsconfig.json"
}
]
-}
\ No newline at end of file
+}
diff --git a/package-lock.json b/package-lock.json
index fd43bef5..4b19a8bf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -60,6 +60,7 @@
"@types/sharp": "^0.31.0",
"@types/ws": "^8.5.3",
"express": "^4.18.1",
+ "prettier": "^2.7.1",
"typescript": "^4.8.3"
},
"optionalDependencies": {
@@ -4715,6 +4716,21 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
+ "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -9846,6 +9862,12 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w=="
},
+ "prettier": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
+ "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+ "dev": true
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
diff --git a/package.json b/package.json
index 8a737424..9c86f918 100644
--- a/package.json
+++ b/package.json
@@ -40,11 +40,16 @@
"@types/sharp": "^0.31.0",
"@types/ws": "^8.5.3",
"express": "^4.18.1",
+ "prettier": "^2.7.1",
"typescript": "^4.8.3"
},
"dependencies": {
+ "@aws-sdk/client-s3": "^3.178.0",
+ "@sentry/node": "^7.13.0",
+ "@sentry/tracing": "^7.13.0",
"ajv": "^8.6.2",
"ajv-formats": "^2.1.1",
+ "amqplib": "^0.10.3",
"bcrypt": "^5.0.1",
"cheerio": "^1.0.0-rc.12",
"cookie-parser": "^1.4.6",
@@ -72,12 +77,7 @@
"sqlite3": "^5.1.1",
"typeorm": "^0.3.10",
"typescript-json-schema": "^0.50.1",
- "ws": "^8.9.0",
-
- "@aws-sdk/client-s3": "^3.178.0",
- "@sentry/node": "^7.13.0",
- "@sentry/tracing": "^7.13.0",
- "amqplib": "^0.10.3"
+ "ws": "^8.9.0"
},
"optionalDependencies": {
"@yukikaze-bot/erlpack": "^1.0.1"
diff --git a/scripts/benchmark/connections.js b/scripts/benchmark/connections.js
index 515f76e9..1a30d49f 100644
--- a/scripts/benchmark/connections.js
+++ b/scripts/benchmark/connections.js
@@ -48,7 +48,7 @@ function connect() {
token,
properties: {},
},
- })
+ }),
);
break;
diff --git a/scripts/client.js b/scripts/client.js
index e73bc7ee..ed1a94b9 100644
--- a/scripts/client.js
+++ b/scripts/client.js
@@ -8,21 +8,21 @@ const BASE_URL = "https://discord.com";
// Manual for now
const INDEX_SCRIPTS = [
- "83ace7450e110d16319e", // 50
- "e02290aaa8dac5d195c2", // 1
- "4f3b3c576b879a5f75d1", // 0?
- "699456246fdfe7589855", // ~4500.
+ "83ace7450e110d16319e", // 50
+ "e02290aaa8dac5d195c2", // 1
+ "4f3b3c576b879a5f75d1", // 0?
+ "699456246fdfe7589855", // ~4500.
];
const doPatch = (content) => {
//remove nitro references
content = content.replace(/Discord Nitro/g, "Fosscord Premium");
- content = content.replace(/"Nitro"/g, "\"Premium\"");
+ content = content.replace(/"Nitro"/g, '"Premium"');
content = content.replace(/Nitro /g, "Premium ");
content = content.replace(/ Nitro/g, " Premium");
content = content.replace(/\[Nitro\]/g, "[Premium]");
content = content.replace(/\*Nitro\*/g, "*Premium*");
- content = content.replace(/\"Nitro \. /g, "\"Premium. ");
+ content = content.replace(/\"Nitro \. /g, '"Premium. ');
//remove discord references
content = content.replace(/ Discord /g, " Fosscord ");
@@ -35,11 +35,11 @@ const doPatch = (content) => {
content = content.replace(/\*Discord\*/g, "*Fosscord*");
//server -> guild
- content = content.replace(/"Server"/g, "\"Guild\"");
- content.replaceAll("server.\"", "guild.\"");
+ content = content.replace(/"Server"/g, '"Guild"');
+ content.replaceAll('server."', 'guild."');
content.replaceAll(" server ", " guild ");
content.replaceAll(" Server ", " Guild ");
- content.replaceAll("\"Server", "\"Guild");
+ content.replaceAll('"Server', '"Guild');
// //change some vars
// content = content.replace('dsn: "https://fa97a90475514c03a42f80cd36d147c4@sentry.io/140984"', "dsn: (/true/.test(localStorage.sentryOptIn)?'https://6bad92b0175d41a18a037a73d0cff282@sentry.thearcanebrony.net/12':'')");
@@ -52,8 +52,14 @@ const doPatch = (content) => {
// content = content.replace('width: n, height: o, viewBox: "0 0 28 20"', 'width: 48, height: 48, viewBox: "0 0 48 48"');
//save some time on load resolving asset urls...
- content = content.replaceAll('e.exports = n.p + "', 'e.exports = "/assets/');
- content = content.replaceAll('e.exports = r.p + "', 'e.exports = "/assets/');
+ content = content.replaceAll(
+ 'e.exports = n.p + "',
+ 'e.exports = "/assets/',
+ );
+ content = content.replaceAll(
+ 'e.exports = r.p + "',
+ 'e.exports = "/assets/',
+ );
return content;
};
@@ -66,7 +72,7 @@ const processFile = async (name) => {
await fs.writeFile(path.join(CACHE_PATH, `${name}.js`), text);
- return [...new Set(text.match((/[A-Fa-f0-9]{20}/g)))];
+ return [...new Set(text.match(/[A-Fa-f0-9]{20}/g))];
};
(async () => {
@@ -83,7 +89,9 @@ const processFile = async (name) => {
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
- process.stdout.write(`Scraping asset ${asset}. Remaining: ${INDEX_SCRIPTS.length}`);
+ process.stdout.write(
+ `Scraping asset ${asset}. Remaining: ${INDEX_SCRIPTS.length}`,
+ );
const newAssets = await processFile(asset);
assets.push(...newAssets);
@@ -103,15 +111,21 @@ const processFile = async (name) => {
}
while (rates.length > 20) rates.shift();
- const averageRate = rates.length ? rates.reduce((prev, curr) => prev + curr) / rates.length : 1;
- const finishTime = (averageRate * (assets.length - i));
+ const averageRate = rates.length
+ ? rates.reduce((prev, curr) => prev + curr) / rates.length
+ : 1;
+ const finishTime = averageRate * (assets.length - i);
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
process.stdout.write(
`Caching asset ${asset}. ` +
- `${i}/${assets.length - 1} = ${Math.floor((i / (assets.length - 1)) * 100)}% ` +
- `Finish at: ${new Date(Date.now() + finishTime).toLocaleTimeString()}`
+ `${i}/${assets.length - 1} = ${Math.floor(
+ (i / (assets.length - 1)) * 100,
+ )}% ` +
+ `Finish at: ${new Date(
+ Date.now() + finishTime,
+ ).toLocaleTimeString()}`,
);
await processFile(asset);
@@ -122,4 +136,4 @@ const processFile = async (name) => {
}
console.log(`\nDone`);
-})();
\ No newline at end of file
+})();
diff --git a/scripts/rights.js b/scripts/rights.js
index a4eb0b31..d0d1e163 100644
--- a/scripts/rights.js
+++ b/scripts/rights.js
@@ -1,4 +1,4 @@
-require('module-alias/register');
+require("module-alias/register");
const { Rights } = require("..");
const allRights = new Rights(1).bitfield;
@@ -18,4 +18,4 @@ discordLike -= Rights.FLAGS.BYPASS_RATE_LIMITS;
discordLike -= Rights.FLAGS.CREDITABLE;
discordLike -= Rights.FLAGS.MANAGE_GUILD_DIRECTORY;
discordLike -= Rights.FLAGS.SEND_BACKDATED_EVENTS;
-console.log(`Discord.com-like rights:`, discordLike);
\ No newline at end of file
+console.log(`Discord.com-like rights:`, discordLike);
diff --git a/scripts/schema.js b/scripts/schema.js
index de062d63..90a346c9 100644
--- a/scripts/schema.js
+++ b/scripts/schema.js
@@ -11,10 +11,10 @@ const settings = {
excludePrivate: true,
defaultNumberType: "integer",
noExtraProps: true,
- defaultProps: false
+ defaultProps: false,
};
const compilerOptions = {
- strictNullChecks: true
+ strictNullChecks: true,
};
const Excluded = [
"DefaultSchema",
@@ -47,11 +47,17 @@ function modify(obj) {
}
function main() {
- const program = TJS.programFromConfig("tsconfig.json")
+ const program = TJS.programFromConfig("tsconfig.json");
const generator = TJS.buildGenerator(program, settings);
if (!generator || !program) return;
- let schemas = generator.getUserSymbols().filter((x) => (x.endsWith("Schema") || x.endsWith("Response")) && !Excluded.includes(x));
+ let schemas = generator
+ .getUserSymbols()
+ .filter(
+ (x) =>
+ (x.endsWith("Schema") || x.endsWith("Response")) &&
+ !Excluded.includes(x),
+ );
console.log(schemas);
var definitions = {};
diff --git a/scripts/stresstest/src/login/index.js b/scripts/stresstest/src/login/index.js
index 96603652..291e2b8b 100644
--- a/scripts/stresstest/src/login/index.js
+++ b/scripts/stresstest/src/login/index.js
@@ -6,12 +6,12 @@ async function login(account) {
var body = {
fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw",
login: account.email,
- password: account.password
+ password: account.password,
};
var x = await fetch(config.url + "/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body)
+ body: JSON.stringify(body),
});
console.log(x);
x = await x.json();
diff --git a/scripts/stresstest/src/message/send.js b/scripts/stresstest/src/message/send.js
index d21560d7..1e5ea062 100644
--- a/scripts/stresstest/src/message/send.js
+++ b/scripts/stresstest/src/message/send.js
@@ -6,16 +6,19 @@ async function sendMessage(account) {
var body = {
fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw",
content: "Test",
- tts: false
+ tts: false,
};
- var x = await fetch(config.url + "/channels/" + config["text-channel"] + "/messages", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: account.token
+ var x = await fetch(
+ config.url + "/channels/" + config["text-channel"] + "/messages",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: account.token,
+ },
+ body: JSON.stringify(body),
},
- body: JSON.stringify(body)
- });
+ );
console.log(x);
x = await x.json();
console.log(x);
diff --git a/scripts/stresstest/src/register/index.js b/scripts/stresstest/src/register/index.js
index 86f908cd..38089dc7 100644
--- a/scripts/stresstest/src/register/index.js
+++ b/scripts/stresstest/src/register/index.js
@@ -4,7 +4,11 @@ var config = require("../../config.json");
module.exports = generate;
async function generate() {
var mail = (Math.random() + 10).toString(36).substring(2);
- mail = mail + "." + (Math.random() + 10).toString(36).substring(2) + "@stresstest.com";
+ mail =
+ mail +
+ "." +
+ (Math.random() + 10).toString(36).substring(2) +
+ "@stresstest.com";
var password =
(Math.random() * 69).toString(36).substring(-7) +
(Math.random() * 69).toString(36).substring(-7) +
@@ -20,12 +24,12 @@ async function generate() {
consent: true,
date_of_birth: "2000-04-04",
gift_code_sku_id: null,
- captcha_key: null
+ captcha_key: null,
};
var x = await fetch(config.url + "/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body)
+ body: JSON.stringify(body),
});
console.log(x);
x = await x.json();
diff --git a/src-slowcord/README.md b/src-slowcord/README.md
index 892b5763..ac42117a 100644
--- a/src-slowcord/README.md
+++ b/src-slowcord/README.md
@@ -1 +1 @@
-Additional resources/services for [Slowcord](https://slowcord.maddy.k.vu/login)
\ No newline at end of file
+Additional resources/services for [Slowcord](https://slowcord.maddy.k.vu/login)
diff --git a/src-slowcord/bot/.vscode/launch.json b/src-slowcord/bot/.vscode/launch.json
index 92765e22..c1c765a8 100644
--- a/src-slowcord/bot/.vscode/launch.json
+++ b/src-slowcord/bot/.vscode/launch.json
@@ -4,12 +4,9 @@
"name": "Slowcord Bot",
"program": "${workspaceFolder}/build/index.js",
"request": "launch",
- "skipFiles": [
- "<node_internals>/**"
- ],
+ "skipFiles": ["<node_internals>/**"],
"type": "node",
"preLaunchTask": "npm: build"
}
-
]
-}
\ No newline at end of file
+}
diff --git a/src-slowcord/bot/.vscode/tasks.json b/src-slowcord/bot/.vscode/tasks.json
index 356126d8..be345ecd 100644
--- a/src-slowcord/bot/.vscode/tasks.json
+++ b/src-slowcord/bot/.vscode/tasks.json
@@ -10,4 +10,4 @@
"detail": "tsc -b"
}
]
-}
\ No newline at end of file
+}
diff --git a/src-slowcord/bot/src/Bot.ts b/src-slowcord/bot/src/Bot.ts
index 45938846..cf3ff09f 100644
--- a/src-slowcord/bot/src/Bot.ts
+++ b/src-slowcord/bot/src/Bot.ts
@@ -1,11 +1,11 @@
import { Message } from "discord.js";
-import { Client } from "fosscord-gopnik/build/lib"; // huh? oh well. some bugs in my lib Ig
+import { Client } from "fosscord-gopnik/build/lib"; // huh? oh well. some bugs in my lib Ig
import { Command, getCommands } from "./commands/index.js";
export default class Bot {
client: Client;
- commands: { [key: string]: Command; } = {};
+ commands: { [key: string]: Command } = {};
constructor(client: Client) {
this.client = client;
@@ -17,10 +17,12 @@ export default class Bot {
console.log(`Logged in as ${this.client.user!.tag}`);
this.client.user!.setPresence({
- activities: [{
- name: "EVERYTHING",
- type: "WATCHING",
- }]
+ activities: [
+ {
+ name: "EVERYTHING",
+ type: "WATCHING",
+ },
+ ],
});
};
@@ -45,4 +47,4 @@ export default class Bot {
args: args,
});
};
-}
\ No newline at end of file
+}
diff --git a/src-slowcord/bot/src/commands/index.ts b/src-slowcord/bot/src/commands/index.ts
index d3b39e0f..0130b2bc 100644
--- a/src-slowcord/bot/src/commands/index.ts
+++ b/src-slowcord/bot/src/commands/index.ts
@@ -2,11 +2,11 @@ import { Message, GuildMember, Guild, User } from "discord.js";
import fs from "fs";
export type CommandContext = {
- user: User,
- guild: Guild | null,
- member: GuildMember | null,
- message: Message,
- args: string[],
+ user: User;
+ guild: Guild | null;
+ member: GuildMember | null;
+ message: Message;
+ args: string[];
};
export type Command = {
@@ -19,8 +19,7 @@ const walk = async (path: string) => {
const out = [];
for (var file of files) {
if (fs.statSync(`${path}/${file}`).isDirectory()) continue;
- if (file.indexOf("index") !== -1)
- continue;
+ if (file.indexOf("index") !== -1) continue;
if (file.indexOf(".js") !== file.length - 3) continue;
var imported = (await import(`./${file}`)).default;
out.push(imported);
diff --git a/src-slowcord/bot/src/commands/instance.ts b/src-slowcord/bot/src/commands/instance.ts
index ac0c9b2d..170d8f76 100644
--- a/src-slowcord/bot/src/commands/instance.ts
+++ b/src-slowcord/bot/src/commands/instance.ts
@@ -1,7 +1,7 @@
import { Command } from "./index.js";
import { User, Guild, Message } from "@fosscord/util";
-const cache: { [key: string]: number; } = {
+const cache: { [key: string]: number } = {
users: 0,
guilds: 0,
messages: 0,
@@ -11,7 +11,10 @@ const cache: { [key: string]: number; } = {
export default {
name: "instance",
exec: async ({ message }) => {
- if (Date.now() > cache.lastChecked + parseInt(process.env.CACHE_TTL as string)) {
+ if (
+ Date.now() >
+ cache.lastChecked + parseInt(process.env.CACHE_TTL as string)
+ ) {
cache.users = await User.count();
cache.guilds = await Guild.count();
cache.messages = await Message.count();
@@ -19,18 +22,35 @@ export default {
}
return message.reply({
- embeds: [{
- title: "Instance Stats",
- description: "For more indepth information, check out https://grafana.understars.dev",
- footer: {
- text: `Last checked: ${Math.floor((Date.now() - cache.lastChecked) / (1000 * 60))} minutes ago`,
+ embeds: [
+ {
+ title: "Instance Stats",
+ description:
+ "For more indepth information, check out https://grafana.understars.dev",
+ footer: {
+ text: `Last checked: ${Math.floor(
+ (Date.now() - cache.lastChecked) / (1000 * 60),
+ )} minutes ago`,
+ },
+ fields: [
+ {
+ inline: true,
+ name: "Total Users",
+ value: cache.users.toString(),
+ },
+ {
+ inline: true,
+ name: "Total Guilds",
+ value: cache.guilds.toString(),
+ },
+ {
+ inline: true,
+ name: "Total Messages",
+ value: cache.messages.toString(),
+ },
+ ],
},
- fields: [
- { inline: true, name: "Total Users", value: cache.users.toString() },
- { inline: true, name: "Total Guilds", value: cache.guilds.toString() },
- { inline: true, name: "Total Messages", value: cache.messages.toString() },
- ]
- }]
+ ],
});
- }
-} as Command;
\ No newline at end of file
+ },
+} as Command;
diff --git a/src-slowcord/bot/src/index.ts b/src-slowcord/bot/src/index.ts
index 253f759c..fa97313f 100644
--- a/src-slowcord/bot/src/index.ts
+++ b/src-slowcord/bot/src/index.ts
@@ -1,6 +1,6 @@
import "dotenv/config";
import Fosscord from "fosscord-gopnik";
-import Bot from "./Bot.js"; // huh?
+import Bot from "./Bot.js"; // huh?
import { initDatabase } from "fosscord-server/src/util";
const client = new Fosscord.Client({
@@ -21,4 +21,4 @@ client.on("messageCreate", bot.onMessageCreate);
(async () => {
await initDatabase();
await client.login(process.env.TOKEN);
-})();
\ No newline at end of file
+})();
diff --git a/src-slowcord/bot/tsconfig.json b/src-slowcord/bot/tsconfig.json
index 05bfc5c7..f055681a 100644
--- a/src-slowcord/bot/tsconfig.json
+++ b/src-slowcord/bot/tsconfig.json
@@ -1,101 +1,103 @@
{
- "compilerOptions": {
- /* Visit https://aka.ms/tsconfig.json to read more about this file */
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig.json to read more about this file */
- /* Projects */
- // "incremental": true, /* Enable incremental compilation */
- // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
- // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
- // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
- // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+ /* Projects */
+ // "incremental": true, /* Enable incremental compilation */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
- /* Language and Environment */
- "target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
- "lib": ["ES2021"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
- // "jsx": "preserve", /* Specify what JSX code is generated. */
- "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
- // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
- // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
- // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
- // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
- // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
- // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
- // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ /* Language and Environment */
+ "target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ "lib": [
+ "ES2021"
+ ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
+ // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
- /* Modules */
- "module": "CommonJS", /* Specify what module code is generated. */
- // "rootDir": "./", /* Specify the root folder within your source files. */
- "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
- // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
- // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
- // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
- // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
- // "types": [], /* Specify type package names to be included without being referenced in a source file. */
- // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
- // "resolveJsonModule": true, /* Enable importing .json files */
- // "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
+ /* Modules */
+ "module": "CommonJS" /* Specify what module code is generated. */,
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "resolveJsonModule": true, /* Enable importing .json files */
+ // "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
- /* JavaScript Support */
- // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
- // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
- // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
- /* Emit */
- // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
- // "declarationMap": true, /* Create sourcemaps for d.ts files. */
- // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- "sourceMap": true, /* Create source map files for emitted JavaScript files. */
- // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
- "outDir": "./build", /* Specify an output folder for all emitted files. */
- // "removeComments": true, /* Disable emitting comments. */
- // "noEmit": true, /* Disable emitting files from a compilation. */
- // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
- // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
- // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
- // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
- // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
- // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
- // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
- // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
- // "newLine": "crlf", /* Set the newline character for emitting files. */
- // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
- // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
- // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
- // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
- // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
- // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ "sourceMap": true /* Create source map files for emitted JavaScript files. */,
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./build" /* Specify an output folder for all emitted files. */,
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
- /* Interop Constraints */
- // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
- // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
- // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
- /* Type Checking */
- "strict": true, /* Enable all strict type-checking options. */
- // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
- // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
- // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
- // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
- "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */
- // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
- // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
- // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
- // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
- // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
- // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
- // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
- // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
- // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
- // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
- // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
- // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
- // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+ /* Type Checking */
+ "strict": true /* Enable all strict type-checking options. */,
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
+ // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
+ "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */,
+ // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
+ // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
- /* Completeness */
- // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
- }
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
}
diff --git a/src-slowcord/login/public/css/index.css b/src-slowcord/login/public/css/index.css
index d4f5a4b4..cf294ccb 100644
--- a/src-slowcord/login/public/css/index.css
+++ b/src-slowcord/login/public/css/index.css
@@ -4,13 +4,13 @@ html {
--background-primary: rgb(22, 23, 25);
--background-secondary: rgb(15, 16, 18);
--foreground-primary: rgb(200, 200, 200);
- --background-login-discord: #5865F2;
+ --background-login-discord: #5865f2;
background: url("https://slowcord.maddy.k.vu/assets/background.png");
background-size: 100% 100%;
background-repeat: no-repeat;
- font-family: 'Montserrat', sans-serif;
+ font-family: "Montserrat", sans-serif;
color: var(--foreground-primary);
}
@@ -55,7 +55,8 @@ html {
text-align: center;
}
-.header-subtext a, .header-subtext p {
+.header-subtext a,
+.header-subtext p {
display: inline-block;
margin: 0 10px 0 10px;
}
@@ -109,4 +110,4 @@ label {
display: flex;
justify-content: center;
margin-top: 10px;
-}
\ No newline at end of file
+}
diff --git a/src-slowcord/login/public/js/handler.js b/src-slowcord/login/public/js/handler.js
index 68a656b4..2d7b164b 100644
--- a/src-slowcord/login/public/js/handler.js
+++ b/src-slowcord/login/public/js/handler.js
@@ -29,13 +29,12 @@ const handleSubmit = async (path, body) => {
}
// Very fun error message here lol
- const error =
- json.errors
- ? Object.values(json.errors)[0]._errors[0].message
- : (
- json.captcha_key ? "Captcha required" : json.message
- );
+ const error = json.errors
+ ? Object.values(json.errors)[0]._errors[0].message
+ : json.captcha_key
+ ? "Captcha required"
+ : json.message;
failureMessage.innerHTML = error;
failureMessage.style.display = "block";
-};
\ No newline at end of file
+};
diff --git a/src-slowcord/login/public/login.html b/src-slowcord/login/public/login.html
index 8cecd20b..fa367b70 100644
--- a/src-slowcord/login/public/login.html
+++ b/src-slowcord/login/public/login.html
@@ -1,103 +1,127 @@
<html lang="en">
-
-<head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Slowcord</title>
-
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet">
-
- <link rel="stylesheet" href="./css/index.css">
- <script src="js/handler.js"></script>
-</head>
-
-<body>
- <div class="content">
- <div class="login">
- <div class="header">
- <h1>Welcome to Slowcord</h1>
- <div class="header-subtext">
- <p>Glad to see you <3 </p>
- <a href="/register">Wait, I'm new!</a>
+ <head>
+ <meta charset="UTF-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Slowcord</title>
+
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+ <link
+ href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap"
+ rel="stylesheet"
+ />
+
+ <link rel="stylesheet" href="./css/index.css" />
+ <script src="js/handler.js"></script>
+ </head>
+
+ <body>
+ <div class="content">
+ <div class="login">
+ <div class="header">
+ <h1>Welcome to Slowcord</h1>
+ <div class="header-subtext">
+ <p>Glad to see you <3</p>
+ <a href="/register">Wait, I'm new!</a>
+ </div>
+
+ <p id="failure">Login failed</p>
</div>
- <p id="failure">Login failed</p>
+ <form action="javascript:void(0);" name="login">
+ <label for="email">Email</label>
+ <input type="email" name="email" />
+
+ <label for="password">Password</label>
+ <input type="password" name="password" />
+
+ <input type="submit" value="Login" />
+
+ <a
+ id="loginDiscord"
+ class="oauth"
+ href="https://discord.com/api/oauth2/authorize?client_id=991688571415175198&redirect_uri=https%3A%2F%2Fslowcord.maddy.k.vu%2Foauth%2Fdiscord&response_type=code&scope=identify%20email"
+ >
+ Login with Discord
+ </a>
+
+ <div
+ class="h-captcha"
+ data-sitekey="fa3163ea-79a7-4b7b-b752-b58c545906c8"
+ data-theme="dark"
+ ></div>
+ <script
+ src="https://js.hcaptcha.com/1/api.js"
+ async
+ defer
+ ></script>
+ </form>
+
+ <form
+ action="javascript:void(0);"
+ name="2fa"
+ style="display: none"
+ >
+ <label for="code">2FA Code</label>
+ <input type="number" name="code" />
+
+ <input type="hidden" name="ticket" />
+
+ <input type="submit" value="Login" />
+ </form>
</div>
-
- <form action="javascript:void(0);" name="login">
- <label for="email">Email</label>
- <input type="email" name="email" />
-
- <label for="password">Password</label>
- <input type="password" name="password" />
-
- <input type="submit" value="Login" />
-
- <a id="loginDiscord" class="oauth"
- href="https://discord.com/api/oauth2/authorize?client_id=991688571415175198&redirect_uri=https%3A%2F%2Fslowcord.maddy.k.vu%2Foauth%2Fdiscord&response_type=code&scope=identify%20email">
- Login with Discord
- </a>
-
- <div class="h-captcha" data-sitekey="fa3163ea-79a7-4b7b-b752-b58c545906c8" data-theme="dark"></div>
- <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
- </form>
-
- <form action="javascript:void(0);" name="2fa" style="display: none">
- <label for="code">2FA Code</label>
- <input type="number" name="code" />
-
- <input type="hidden" name="ticket" />
-
- <input type="submit" value="Login"/>
- </form>
</div>
- </div>
-
- <script>
- /* https://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript */
- const getCookieValue = (name) => (
- document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || ''
- );
-
- let token = getCookieValue("token");
- if (token.trim().length) {
- /* https://stackoverflow.com/a/27374365 */
- // why is clearing cookies so weird? wtf
- document.cookie.split(";").forEach(function (c) { document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); });
- window.localStorage.setItem("token", `"${token}"`);
- window.location.href = "/app";
- }
-
- token = window.localStorage.getItem("token");
- if (token) window.location.href = "/app";
-
- document.forms["login"].addEventListener("submit", async (e) => {
- const data = new FormData(e.target);
- const email = data.get("email");
- const password = data.get("password");
- const hcaptcha = data.get("h-captcha-response");
-
- await handleSubmit("/api/v9/auth/login", {
- login: email,
- password: password,
- captcha_key: hcaptcha,
+
+ <script>
+ /* https://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript */
+ const getCookieValue = (name) =>
+ document.cookie
+ .match("(^|;)\\s*" + name + "\\s*=\\s*([^;]+)")
+ ?.pop() || "";
+
+ let token = getCookieValue("token");
+ if (token.trim().length) {
+ /* https://stackoverflow.com/a/27374365 */
+ // why is clearing cookies so weird? wtf
+ document.cookie.split(";").forEach(function (c) {
+ document.cookie = c
+ .replace(/^ +/, "")
+ .replace(
+ /=.*/,
+ "=;expires=" + new Date().toUTCString() + ";path=/",
+ );
+ });
+ window.localStorage.setItem("token", `"${token}"`);
+ window.location.href = "/app";
+ }
+
+ token = window.localStorage.getItem("token");
+ if (token) window.location.href = "/app";
+
+ document.forms["login"].addEventListener("submit", async (e) => {
+ const data = new FormData(e.target);
+ const email = data.get("email");
+ const password = data.get("password");
+ const hcaptcha = data.get("h-captcha-response");
+
+ await handleSubmit("/api/v9/auth/login", {
+ login: email,
+ password: password,
+ captcha_key: hcaptcha,
+ });
});
- })
- document.forms["2fa"].addEventListener("submit", async (e) => {
- const data = new FormData(e.target);
- const code = data.get("code");
- const ticket = data.get("ticket");
+ document.forms["2fa"].addEventListener("submit", async (e) => {
+ const data = new FormData(e.target);
+ const code = data.get("code");
+ const ticket = data.get("ticket");
- await handleSubmit("/api/v9/auth/mfa/totp", {
- code: code,
- ticket: ticket,
+ await handleSubmit("/api/v9/auth/mfa/totp", {
+ code: code,
+ ticket: ticket,
+ });
});
- })
- </script>
-</body>
-
-</html>
\ No newline at end of file
+ </script>
+ </body>
+</html>
diff --git a/src-slowcord/login/public/register.html b/src-slowcord/login/public/register.html
index 40eecdbe..30b560a4 100644
--- a/src-slowcord/login/public/register.html
+++ b/src-slowcord/login/public/register.html
@@ -1,78 +1,88 @@
<html lang="en">
-
-<head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Slowcord</title>
-
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet">
-
- <link rel="stylesheet" href="./css/index.css">
- <script src="js/handler.js"></script>
-</head>
-
-<body>
- <div class="content">
- <div class="login">
- <div class="header">
- <h1>Welcome to Slowcord</h1>
- <div class="header-subtext">
- <p>You're new?</p>
- <a href="/login">Actually, I'm not!</a>
+ <head>
+ <meta charset="UTF-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Slowcord</title>
+
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+ <link
+ href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap"
+ rel="stylesheet"
+ />
+
+ <link rel="stylesheet" href="./css/index.css" />
+ <script src="js/handler.js"></script>
+ </head>
+
+ <body>
+ <div class="content">
+ <div class="login">
+ <div class="header">
+ <h1>Welcome to Slowcord</h1>
+ <div class="header-subtext">
+ <p>You're new?</p>
+ <a href="/login">Actually, I'm not!</a>
+ </div>
+
+ <p id="failure">Register failed</p>
</div>
- <p id="failure">Register failed</p>
+ <form action="javascript:void(0);">
+ <label for="email">Email</label>
+ <input type="email" name="email" />
+
+ <label for="username">Username</label>
+ <input type="username" name="username" />
+
+ <label for="password">Password</label>
+ <input type="password" name="password" />
+
+ <label for="dob">Date of Birth</label>
+ <input type="date" name="dob" />
+
+ <input type="submit" value="Register" />
+
+ <a
+ id="loginDiscord"
+ class="oauth"
+ href="https://discord.com/api/oauth2/authorize?client_id=991688571415175198&redirect_uri=https%3A%2F%2Fslowcord.maddy.k.vu%2Foauth%2Fdiscord&response_type=code&scope=identify%20email"
+ >
+ Login with Discord
+ </a>
+
+ <div
+ class="h-captcha"
+ data-sitekey="fa3163ea-79a7-4b7b-b752-b58c545906c8"
+ ></div>
+ <script
+ src="https://js.hcaptcha.com/1/api.js"
+ async
+ defer
+ ></script>
+ </form>
</div>
-
-
- <form action="javascript:void(0);">
- <label for="email">Email</label>
- <input type="email" name="email" />
-
- <label for="username">Username</label>
- <input type="username" name="username" />
-
- <label for="password">Password</label>
- <input type="password" name="password" />
-
- <label for="dob">Date of Birth</label>
- <input type="date" name="dob" />
-
- <input type="submit" value="Register" />
-
- <a id="loginDiscord" class="oauth"
- href="https://discord.com/api/oauth2/authorize?client_id=991688571415175198&redirect_uri=https%3A%2F%2Fslowcord.maddy.k.vu%2Foauth%2Fdiscord&response_type=code&scope=identify%20email">
- Login with Discord
- </a>
-
- <div class="h-captcha" data-sitekey="fa3163ea-79a7-4b7b-b752-b58c545906c8"></div>
- <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
- </form>
</div>
- </div>
-
- <script>
- document.forms[0].addEventListener("submit", async (e) => {
- const data = new FormData(e.target);
- const email = data.get("email");
- const username = data.get("username");
- const password = data.get("password");
- const dob = data.get("dob");
- const hcaptcha = data.get("h-captcha-response")
- await handleSubmit("/api/v9/auth/register", {
- consent: true,
- email: email,
- username: username,
- password: password,
- date_of_birth: dob,
- captcha_key: hcaptcha,
+ <script>
+ document.forms[0].addEventListener("submit", async (e) => {
+ const data = new FormData(e.target);
+ const email = data.get("email");
+ const username = data.get("username");
+ const password = data.get("password");
+ const dob = data.get("dob");
+ const hcaptcha = data.get("h-captcha-response");
+
+ await handleSubmit("/api/v9/auth/register", {
+ consent: true,
+ email: email,
+ username: username,
+ password: password,
+ date_of_birth: dob,
+ captcha_key: hcaptcha,
+ });
});
- })
- </script>
-</body>
-
-</html>
\ No newline at end of file
+ </script>
+ </body>
+</html>
diff --git a/src-slowcord/login/src/index.ts b/src-slowcord/login/src/index.ts
index 56d0a687..ced35dcf 100644
--- a/src-slowcord/login/src/index.ts
+++ b/src-slowcord/login/src/index.ts
@@ -1,7 +1,13 @@
import "dotenv/config";
import express, { Request, Response } from "express";
import cookieParser from "cookie-parser";
-import { initDatabase, generateToken, User, Config, handleFile } from "fosscord-server/src/util";
+import {
+ initDatabase,
+ generateToken,
+ User,
+ Config,
+ handleFile,
+} from "fosscord-server/src/util";
import path from "path";
import fetch from "node-fetch";
@@ -16,8 +22,8 @@ app.use(cookieParser());
const port = process.env.PORT;
// ip -> unix epoch that requests will be accepted again
-const rateLimits: { [ip: string]: number; } = {};
-const allowRequestsEveryMs = 0.5 * 1000; // every half second
+const rateLimits: { [ip: string]: number } = {};
+const allowRequestsEveryMs = 0.5 * 1000; // every half second
const allowedRequestsPerSecond = 50;
let requestsThisSecond = 0;
@@ -36,23 +42,25 @@ class Discord {
static getAccessToken = async (req: Request, res: Response) => {
const { code } = req.query;
- const body = new URLSearchParams(Object.entries({
- client_id: process.env.DISCORD_CLIENT_ID as string,
- client_secret: process.env.DISCORD_SECRET as string,
- redirect_uri: process.env.DISCORD_REDIRECT as string,
- code: code as string,
- grant_type: "authorization_code",
- })).toString();
+ const body = new URLSearchParams(
+ Object.entries({
+ client_id: process.env.DISCORD_CLIENT_ID as string,
+ client_secret: process.env.DISCORD_SECRET as string,
+ redirect_uri: process.env.DISCORD_REDIRECT as string,
+ code: code as string,
+ grant_type: "authorization_code",
+ }),
+ ).toString();
const resp = await fetch("https://discord.com/api/oauth2/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
- body: body
+ body: body,
});
- const json = await resp.json() as any;
+ const json = (await resp.json()) as any;
if (json.error) return null;
return {
@@ -67,24 +75,26 @@ class Discord {
static getUserDetails = async (token: string) => {
const resp = await fetch("https://discord.com/api/users/@me", {
headers: {
- "Authorization": `Bearer ${token}`,
- }
+ Authorization: `Bearer ${token}`,
+ },
});
- const json = await resp.json() as any;
- if (!json.username || !json.email) return null; // eh, deal with bad code later
+ const json = (await resp.json()) as any;
+ if (!json.username || !json.email) return null; // eh, deal with bad code later
return {
id: json.id,
email: json.email,
username: json.username,
- avatar_url: json.avatar ? `https://cdn.discordapp.com/avatars/${json.id}/${json.avatar}?size=2048` : null,
+ avatar_url: json.avatar
+ ? `https://cdn.discordapp.com/avatars/${json.id}/${json.avatar}?size=2048`
+ : null,
};
};
}
-const handlers: { [key: string]: any; } = {
- "discord": Discord,
+const handlers: { [key: string]: any } = {
+ discord: Discord,
};
app.get("/oauth/:type", async (req, res) => {
@@ -92,17 +102,21 @@ app.get("/oauth/:type", async (req, res) => {
if (requestsThisSecond > allowedRequestsPerSecond)
return res.sendStatus(429);
- const ip = (req.headers["x-forwarded-for"] as string) || req.socket.remoteAddress as string;
+ const ip =
+ (req.headers["x-forwarded-for"] as string) ||
+ (req.socket.remoteAddress as string);
console.log(`${ip}`);
if (!rateLimits[ip]) {
rateLimits[ip] = Date.now() + allowRequestsEveryMs;
- }
- else if (rateLimits[ip] > Date.now()) {
+ } else if (rateLimits[ip] > Date.now()) {
rateLimits[ip] += allowRequestsEveryMs;
- console.log(`${new Date()} : user ${ip} was timed out for ${(rateLimits[ip] - Date.now()) / 1000}s`);
+ console.log(
+ `${new Date()} : user ${ip} was timed out for ${
+ (rateLimits[ip] - Date.now()) / 1000
+ }s`,
+ );
return res.sendStatus(429);
- }
- else {
+ } else {
delete rateLimits[ip];
}
@@ -121,16 +135,18 @@ app.get("/oauth/:type", async (req, res) => {
user = await User.register({
email: details.email,
username: details.username,
- req
+ req,
});
if (details.avatar_url) {
try {
- const avatar = await handleFile(`/avatars/${user.id}`, await toDataURL(details.avatar_url) as string);
+ const avatar = await handleFile(
+ `/avatars/${user.id}`,
+ (await toDataURL(details.avatar_url)) as string,
+ );
user.avatar = avatar;
await user.save();
- }
- catch (e) {
+ } catch (e) {
console.error(e);
}
}
@@ -152,4 +168,4 @@ app.use(express.static("public", { extensions: ["html"] }));
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
-})();
\ No newline at end of file
+})();
diff --git a/src-slowcord/login/tsconfig.json b/src-slowcord/login/tsconfig.json
index b8a5965d..4d96714c 100644
--- a/src-slowcord/login/tsconfig.json
+++ b/src-slowcord/login/tsconfig.json
@@ -1,10 +1,6 @@
{
- "exclude": [
- "node_modules"
- ],
- "include": [
- "src/**/*.ts"
- ],
+ "exclude": ["node_modules"],
+ "include": ["src/**/*.ts"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
@@ -15,10 +11,12 @@
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
- "target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
- "lib": ["ES2021"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ "target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ "lib": [
+ "ES2021"
+ ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
- "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
@@ -27,14 +25,16 @@
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
- "module": "ES2020", /* Specify what module code is generated. */
+ "module": "ES2020" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
- "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
+ "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
- "types": ["node"], /* Specify type package names to be included without being referenced in a source file. */
+ "types": [
+ "node"
+ ] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
@@ -46,9 +46,9 @@
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ "sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
- "outDir": "./build", /* Specify an output folder for all emitted files. */
+ "outDir": "./build" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
@@ -69,16 +69,16 @@
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
- "strict": true, /* Enable all strict type-checking options. */
+ "strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
- "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */
+ "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */,
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
@@ -94,6 +94,6 @@
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
}
-}
\ No newline at end of file
+}
diff --git a/src-slowcord/rules.md b/src-slowcord/rules.md
index ffae0846..1097060a 100644
--- a/src-slowcord/rules.md
+++ b/src-slowcord/rules.md
@@ -3,11 +3,12 @@
Slowcord is a heavily modded Fosscord instance. You can browse it's source here: https://github.com/MaddyUnderStars/fosscord-server/tree/slowcord
## Here are some general instance-wide rules:
-* **Harassment, homophobia, transphobia, etc, violence, and hate speech are forbidden.**
-* Behaviour that harms the service - be it malicious/intentional or not - is strictly forbidden. This may include API abuse/spam, exploits, etc.
-* * If you do discover an exploit/bug, it would be greatly appreciated if you could create an issue in the above repo, or DM @MaddyUnderStars#0000.
-* Any content that would be considered illegal in Australia is also forbidden. Additionally, if it is illegal in your own country, it shouldn't be here.
-* Bots/selfbots are allowed. If you would like an account to be given bot status, DM @MaddyUnderStars#0000.
+
+- **Harassment, homophobia, transphobia, etc, violence, and hate speech are forbidden.**
+- Behaviour that harms the service - be it malicious/intentional or not - is strictly forbidden. This may include API abuse/spam, exploits, etc.
+- - If you do discover an exploit/bug, it would be greatly appreciated if you could create an issue in the above repo, or DM @MaddyUnderStars#0000.
+- Any content that would be considered illegal in Australia is also forbidden. Additionally, if it is illegal in your own country, it shouldn't be here.
+- Bots/selfbots are allowed. If you would like an account to be given bot status, DM @MaddyUnderStars#0000.
These rules are non-exhaustive, but should give a good idea of what will be enforced.
@@ -16,5 +17,6 @@ Permanent Slowcord guild invite: https://slowcord.understars.dev/invite/slowcord
### If a message or user breaks these rules, you can report it here: https://forms.gle/sd6RkdM7gRgJLV368
#### Lastly ( and not rules ):
-* If you use BetterDiscord or Powercord, and want an easier time accessing Slowcord and other Fosscord instances, check out https://github.com/maddyunderstars/fosscord-bd!
-* Also, if you're on Android, you can download the mobile client at https://slowcord.understars.dev/assets/slowcord.apk
+
+- If you use BetterDiscord or Powercord, and want an easier time accessing Slowcord and other Fosscord instances, check out https://github.com/maddyunderstars/fosscord-bd!
+- Also, if you're on Android, you can download the mobile client at https://slowcord.understars.dev/assets/slowcord.apk
diff --git a/src-slowcord/status/src/gateway.ts b/src-slowcord/status/src/gateway.ts
index 14944d09..bc00c2d4 100644
--- a/src-slowcord/status/src/gateway.ts
+++ b/src-slowcord/status/src/gateway.ts
@@ -5,14 +5,22 @@ import mysql from "mysql2";
import fetch from "node-fetch";
const dbConn = mysql.createConnection(process.env.DATABASE as string);
-const executePromise = (sql: string, args: any[]) => new Promise((resolve, reject) => dbConn.execute(sql, args, (err, res) => { if (err) reject(err); else resolve(res); }));
+const executePromise = (sql: string, args: any[]) =>
+ new Promise((resolve, reject) =>
+ dbConn.execute(sql, args, (err, res) => {
+ if (err) reject(err);
+ else resolve(res);
+ }),
+ );
const savePerf = async (time: number, name: string, error?: string | Error) => {
if (error && typeof error != "string") error = error.message;
try {
- await executePromise("INSERT INTO performance (value, endpoint, timestamp, error) VALUES (?, ?, ?, ?)", [time ?? 0, name, new Date(), error ?? null]);
+ await executePromise(
+ "INSERT INTO performance (value, endpoint, timestamp, error) VALUES (?, ?, ?, ?)",
+ [time ?? 0, name, new Date(), error ?? null],
+ );
// await executePromise("DELETE FROM performance WHERE DATE(timestamp) < now() - interval ? DAY", [process.env.RETENTION_DAYS]);
- }
- catch (e) {
+ } catch (e) {
console.error(e);
}
};
@@ -23,7 +31,11 @@ const doMeasurements = async (channel: Discord.TextChannel) => {
timestamp = Date.now();
await channel.send("hello this is a special message kthxbye");
- setTimeout(doMeasurements, parseInt(process.env.MEASURE_INTERVAL as string), channel);
+ setTimeout(
+ doMeasurements,
+ parseInt(process.env.MEASURE_INTERVAL as string),
+ channel,
+ );
};
const instance = {
@@ -37,8 +49,8 @@ const client = new Fosscord.Client({
intents: [],
http: {
api: instance.api,
- cdn: instance.cdn
- }
+ cdn: instance.cdn,
+ },
});
client.on("ready", async () => {
@@ -52,19 +64,24 @@ client.on("ready", async () => {
client.on("messageCreate", async (msg: Discord.Message) => {
if (!timestamp) return;
- if (msg.author.id != "992745947417141682"
- || msg.channel.id != "1019955729054267764"
- || msg.content != "hello this is a special message kthxbye")
+ if (
+ msg.author.id != "992745947417141682" ||
+ msg.channel.id != "1019955729054267764" ||
+ msg.content != "hello this is a special message kthxbye"
+ )
return;
await savePerf(Date.now() - timestamp, "messageCreate", undefined);
timestamp = undefined;
- await fetch(`${instance.api}/channels/1019955729054267764/messages/${msg.id}`, {
- method: "DELETE",
- headers: {
- authorization: instance.token
- }
- })
+ await fetch(
+ `${instance.api}/channels/1019955729054267764/messages/${msg.id}`,
+ {
+ method: "DELETE",
+ headers: {
+ authorization: instance.token,
+ },
+ },
+ );
});
client.on("error", (error: any) => {
@@ -79,4 +96,4 @@ client.on("warn", (msg: any) => {
await new Promise((resolve) => dbConn.connect(resolve));
console.log("Connected to db");
await client.login(instance.token);
-})();
\ No newline at end of file
+})();
diff --git a/src-slowcord/status/src/index.ts b/src-slowcord/status/src/index.ts
index 735b8a9b..979469b6 100644
--- a/src-slowcord/status/src/index.ts
+++ b/src-slowcord/status/src/index.ts
@@ -4,7 +4,13 @@ import mysql from "mysql2";
import fetch from "node-fetch";
const dbConn = mysql.createConnection(process.env.DATABASE as string);
-const executePromise = (sql: string, args: any[]) => new Promise((resolve, reject) => dbConn.execute(sql, args, (err, res) => { if (err) reject(err); else resolve(res); }));
+const executePromise = (sql: string, args: any[]) =>
+ new Promise((resolve, reject) =>
+ dbConn.execute(sql, args, (err, res) => {
+ if (err) reject(err);
+ else resolve(res);
+ }),
+ );
const instance = {
app: process.env.INSTANCE_WEB_APP as string,
@@ -16,73 +22,86 @@ const instance = {
const savePerf = async (time: number, name: string, error?: string | Error) => {
if (error && typeof error != "string") error = error.message;
try {
- await executePromise("INSERT INTO performance (value, endpoint, timestamp, error) VALUES (?, ?, ?, ?)", [time ?? 0, name, new Date(), error ?? null]);
+ await executePromise(
+ "INSERT INTO performance (value, endpoint, timestamp, error) VALUES (?, ?, ?, ?)",
+ [time ?? 0, name, new Date(), error ?? null],
+ );
// await executePromise("DELETE FROM performance WHERE DATE(timestamp) < now() - interval ? DAY", [process.env.RETENTION_DAYS]);
- }
- catch (e) {
+ } catch (e) {
console.error(e);
}
};
-const saveSystemUsage = async (load: number, procUptime: number, sysUptime: number, ram: number, sessions: number) => {
+const saveSystemUsage = async (
+ load: number,
+ procUptime: number,
+ sysUptime: number,
+ ram: number,
+ sessions: number,
+) => {
try {
- await executePromise("INSERT INTO monitor (time, cpu, procUp, sysUp, ram, sessions) VALUES (?, ?, ?, ?, ?, ?)", [new Date(), load, procUptime, sysUptime, ram, sessions]);
- }
- catch (e) {
+ await executePromise(
+ "INSERT INTO monitor (time, cpu, procUp, sysUp, ram, sessions) VALUES (?, ?, ?, ?, ?, ?)",
+ [new Date(), load, procUptime, sysUptime, ram, sessions],
+ );
+ } catch (e) {
console.error(e);
}
};
-const makeTimedRequest = (path: string, body?: object): Promise<number> => new Promise((resolve, reject) => {
- const opts = {
- hostname: new URL(path).hostname,
- port: 443,
- path: new URL(path).pathname,
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- "Authorization": instance.token,
- },
- timeout: 1000,
- };
-
- let start: number, end: number;
- const req = https.request(opts, res => {
- if (res.statusCode! < 200 || res.statusCode! > 300) {
- return reject(`${res.statusCode} ${res.statusMessage}`);
- }
-
- res.on("data", (data) => {
+const makeTimedRequest = (path: string, body?: object): Promise<number> =>
+ new Promise((resolve, reject) => {
+ const opts = {
+ hostname: new URL(path).hostname,
+ port: 443,
+ path: new URL(path).pathname,
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: instance.token,
+ },
+ timeout: 1000,
+ };
+
+ let start: number, end: number;
+ const req = https.request(opts, (res) => {
+ if (res.statusCode! < 200 || res.statusCode! > 300) {
+ return reject(`${res.statusCode} ${res.statusMessage}`);
+ }
+
+ res.on("data", (data) => {});
+
+ res.on("end", () => {
+ end = Date.now();
+ resolve(end - start);
+ });
});
- res.on("end", () => {
- end = Date.now();
- resolve(end - start);
+ req.on("finish", () => {
+ if (body) req.write(JSON.stringify(body));
+ start = Date.now();
});
- });
- req.on("finish", () => {
- if (body) req.write(JSON.stringify(body));
- start = Date.now();
- });
+ req.on("error", (error) => {
+ reject(error);
+ });
- req.on("error", (error) => {
- reject(error);
+ req.end();
});
- req.end();
-});
-
const measureApi = async (name: string, path: string, body?: object) => {
- let error, time = -1;
+ let error,
+ time = -1;
try {
time = await makeTimedRequest(path, body);
- }
- catch (e) {
+ } catch (e) {
error = e as Error | string;
}
- console.log(`${name} took ${time}ms ${(error ? "with error" : "")}`, error ?? "");
+ console.log(
+ `${name} took ${time}ms ${error ? "with error" : ""}`,
+ error ?? "",
+ );
await savePerf(time, name, error);
};
@@ -100,7 +119,11 @@ const app = async () => {
console.log("Connected to db");
// await client.login(instance.token);
- console.log(`Monitoring performance for instance at ${new URL(instance.api).hostname}`);
+ console.log(
+ `Monitoring performance for instance at ${
+ new URL(instance.api).hostname
+ }`,
+ );
const doMeasurements = async () => {
await measureApi("ping", `${instance.api}/ping`);
@@ -112,18 +135,25 @@ const app = async () => {
const res = await fetch(`${instance.api}/-/monitorz`, {
headers: {
Authorization: process.env.INSTANCE_TOKEN as string,
- }
+ },
});
- const json = await res.json() as monitorzSchema;
- await saveSystemUsage(json.load[1], json.procUptime, json.sysUptime, json.memPercent, json.sessions);
- }
- catch (e) {
- }
-
- setTimeout(doMeasurements, parseInt(process.env.MEASURE_INTERVAL as string));
+ const json = (await res.json()) as monitorzSchema;
+ await saveSystemUsage(
+ json.load[1],
+ json.procUptime,
+ json.sysUptime,
+ json.memPercent,
+ json.sessions,
+ );
+ } catch (e) {}
+
+ setTimeout(
+ doMeasurements,
+ parseInt(process.env.MEASURE_INTERVAL as string),
+ );
};
doMeasurements();
};
-app();
\ No newline at end of file
+app();
diff --git a/src-slowcord/status/tsconfig.json b/src-slowcord/status/tsconfig.json
index 6d6ec56d..4d96714c 100644
--- a/src-slowcord/status/tsconfig.json
+++ b/src-slowcord/status/tsconfig.json
@@ -1,10 +1,6 @@
{
- "exclude": [
- "node_modules"
- ],
- "include": [
- "src/**/*.ts"
- ],
+ "exclude": ["node_modules"],
+ "include": ["src/**/*.ts"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
@@ -15,10 +11,12 @@
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
- "target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
- "lib": ["ES2021"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ "target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ "lib": [
+ "ES2021"
+ ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
- "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
@@ -27,14 +25,16 @@
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
- "module": "ES2020", /* Specify what module code is generated. */
+ "module": "ES2020" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
- "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
+ "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
- "types": ["node"], /* Specify type package names to be included without being referenced in a source file. */
+ "types": [
+ "node"
+ ] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
@@ -46,9 +46,9 @@
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ "sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
- "outDir": "./build", /* Specify an output folder for all emitted files. */
+ "outDir": "./build" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
@@ -69,16 +69,16 @@
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
- "strict": true, /* Enable all strict type-checking options. */
+ "strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
- "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */
+ "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */,
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
@@ -96,4 +96,4 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
-}
\ No newline at end of file
+}
diff --git a/src/api/Server.ts b/src/api/Server.ts
index 4cf0917d..bd9bc4b9 100644
--- a/src/api/Server.ts
+++ b/src/api/Server.ts
@@ -12,7 +12,7 @@ import { initTranslation } from "./middlewares/Translation";
import morgan from "morgan";
import { initInstance } from "./util/handlers/Instance";
import { registerRoutes } from "@fosscord/util";
-import { red } from "picocolors"
+import { red } from "picocolors";
export interface FosscordServerOptions extends ServerOptions {}
@@ -44,13 +44,18 @@ export class FosscordServer extends Server {
this.app.use(
morgan("combined", {
skip: (req, res) => {
- var skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
- if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
+ var skip = !(
+ process.env["LOG_REQUESTS"]?.includes(
+ res.statusCode.toString(),
+ ) ?? false
+ );
+ if (process.env["LOG_REQUESTS"]?.charAt(0) == "-")
+ skip = !skip;
return skip;
- }
- })
+ },
+ }),
);
- };
+ }
this.app.use(CORS);
this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
@@ -63,16 +68,22 @@ export class FosscordServer extends Server {
await initRateLimits(api);
await initTranslation(api);
- this.routes = await registerRoutes(this, path.join(__dirname, "routes", "/"));
+ this.routes = await registerRoutes(
+ this,
+ path.join(__dirname, "routes", "/"),
+ );
- api.use("*", (error: any, req: Request, res: Response, next: NextFunction) => {
- if (error) return next(error);
- res.status(404).json({
- message: "404 endpoint not found",
- code: 0
- });
- next();
- });
+ api.use(
+ "*",
+ (error: any, req: Request, res: Response, next: NextFunction) => {
+ if (error) return next(error);
+ res.status(404).json({
+ message: "404 endpoint not found",
+ code: 0,
+ });
+ next();
+ },
+ );
this.app = app;
@@ -87,8 +98,13 @@ export class FosscordServer extends Server {
this.app.use(ErrorHandler);
TestClient(this.app);
- if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`));
-
+ if (logRequests)
+ console.log(
+ red(
+ `Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`,
+ ),
+ );
+
return super.start();
}
-};
\ No newline at end of file
+}
diff --git a/src/api/index.ts b/src/api/index.ts
index 09663452..adc7649c 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -1,3 +1,3 @@
export * from "./Server";
export * from "./middlewares/";
-export * from "./util/";
\ No newline at end of file
+export * from "./util/";
diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts
index 1df7911b..50048b12 100644
--- a/src/api/middlewares/Authentication.ts
+++ b/src/api/middlewares/Authentication.ts
@@ -10,7 +10,7 @@ export const NO_AUTHORIZATION_ROUTES = [
"/auth/mfa/totp",
// Routes with a seperate auth system
"/webhooks/",
- // Public information endpoints
+ // Public information endpoints
"/ping",
"/gateway",
"/experiments",
@@ -26,7 +26,7 @@ export const NO_AUTHORIZATION_ROUTES = [
// Public policy pages
"/policies/instance",
// Asset delivery
- /\/guilds\/\d+\/widget\.(json|png)/
+ /\/guilds\/\d+\/widget\.(json|png)/,
];
export const API_PREFIX = /^\/api(\/v\d+)?/;
@@ -43,7 +43,11 @@ declare global {
}
}
-export async function Authentication(req: Request, res: Response, next: NextFunction) {
+export async function Authentication(
+ req: Request,
+ res: Response,
+ next: NextFunction,
+) {
if (req.method === "OPTIONS") return res.sendStatus(204);
const url = req.url.replace(API_PREFIX, "");
if (url.startsWith("/invites") && req.method === "GET") return next();
@@ -54,12 +58,16 @@ export async function Authentication(req: Request, res: Response, next: NextFunc
})
)
return next();
- if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
+ if (!req.headers.authorization)
+ return next(new HTTPError("Missing Authorization Header", 401));
try {
const { jwtSecret } = Config.get().security;
- const { decoded, user }: any = await checkToken(req.headers.authorization, jwtSecret);
+ const { decoded, user }: any = await checkToken(
+ req.headers.authorization,
+ jwtSecret,
+ );
req.token = decoded;
req.user_id = decoded.id;
diff --git a/src/api/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts
index 4cb376bc..7741f1fd 100644
--- a/src/api/middlewares/BodyParser.ts
+++ b/src/api/middlewares/BodyParser.ts
@@ -6,7 +6,8 @@ export function BodyParser(opts?: OptionsJson) {
const jsonParser = bodyParser.json(opts);
return (req: Request, res: Response, next: NextFunction) => {
- if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
+ if (!req.headers["content-type"])
+ req.headers["content-type"] = "application/json";
jsonParser(req, res, (err) => {
if (err) {
diff --git a/src/api/middlewares/CORS.ts b/src/api/middlewares/CORS.ts
index 20260cf9..2dce51c6 100644
--- a/src/api/middlewares/CORS.ts
+++ b/src/api/middlewares/CORS.ts
@@ -7,10 +7,16 @@ export function CORS(req: Request, res: Response, next: NextFunction) {
// TODO: use better CSP
res.set(
"Content-security-policy",
- "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';"
+ "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';",
+ );
+ res.set(
+ "Access-Control-Allow-Headers",
+ req.header("Access-Control-Request-Headers") || "*",
+ );
+ res.set(
+ "Access-Control-Allow-Methods",
+ req.header("Access-Control-Request-Methods") || "*",
);
- res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
- res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*");
next();
}
diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index 2012b91c..bf3b011a 100644
--- a/src/api/middlewares/ErrorHandler.ts
+++ b/src/api/middlewares/ErrorHandler.ts
@@ -3,7 +3,12 @@ import { HTTPError } from "lambert-server";
import { ApiError, FieldError } from "@fosscord/util";
const EntityNotFoundErrorRegex = /"(\w+)"/;
-export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) {
+export function ErrorHandler(
+ error: Error,
+ req: Request,
+ res: Response,
+ next: NextFunction,
+) {
if (!error) return next();
try {
@@ -12,20 +17,28 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
let message = error?.toString();
let errors = undefined;
- if (error instanceof HTTPError && error.code) code = httpcode = error.code;
+ if (error instanceof HTTPError && error.code)
+ code = httpcode = error.code;
else if (error instanceof ApiError) {
code = error.code;
message = error.message;
httpcode = error.httpStatus;
} else if (error.name === "EntityNotFoundError") {
- message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`;
+ message = `${
+ error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"
+ } could not be found`;
code = httpcode = 404;
} else if (error instanceof FieldError) {
code = Number(error.code);
message = error.message;
errors = error.errors;
} else {
- console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body);
+ console.error(
+ `[Error] ${code} ${req.url}\n`,
+ errors || error,
+ "\nbody:",
+ req.body,
+ );
if (req.server?.options?.production) {
// don't expose internal errors to the user, instead human errors should be thrown as HTTPError
@@ -39,6 +52,8 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
res.status(httpcode).json({ code: code, message, errors });
} catch (error) {
console.error(`[Internal Server Error] 500`, error);
- return res.status(500).json({ code: 500, message: "Internal Server Error" });
+ return res
+ .status(500)
+ .json({ code: 500, message: "Internal Server Error" });
}
}
diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 57645c0b..b3976a16 100644
--- a/src/api/middlewares/RateLimit.ts
+++ b/src/api/middlewares/RateLimit.ts
@@ -40,21 +40,32 @@ export default function rateLimit(opts: {
success?: boolean;
onlyIp?: boolean;
}): any {
- return async (req: Request, res: Response, next: NextFunction): Promise<any> => {
+ return async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+ ): Promise<any> => {
// exempt user? if so, immediately short circuit
if (req.user_id) {
const rights = await getRights(req.user_id);
if (rights.has("BYPASS_RATE_LIMITS")) return next();
}
- const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
+ const bucket_id =
+ opts.bucket ||
+ req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
let executor_id = getIpAdress(req);
if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
let max_hits = opts.count;
if (opts.bot && req.user_bot) max_hits = opts.bot;
- if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
- else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
+ if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method))
+ max_hits = opts.GET;
+ else if (
+ opts.MODIFY &&
+ ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)
+ )
+ max_hits = opts.MODIFY;
let offender = Cache.get(executor_id + bucket_id);
@@ -75,11 +86,15 @@ export default function rateLimit(opts: {
const global = bucket_id === "global";
// each block violation pushes the expiry one full window further
reset += opts.window * 1000;
- offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
+ offender.expires_at = new Date(
+ offender.expires_at.getTime() + opts.window * 1000,
+ );
resetAfterMs = reset - Date.now();
resetAfterSec = Math.ceil(resetAfterMs / 1000);
- console.log(`blocked bucket: ${bucket_id} ${executor_id}`, { resetAfterMs });
+ console.log(`blocked bucket: ${bucket_id} ${executor_id}`, {
+ resetAfterMs,
+ });
return (
res
.status(429)
@@ -91,20 +106,33 @@ export default function rateLimit(opts: {
.set("Retry-After", `${Math.ceil(resetAfterSec)}`)
.set("X-RateLimit-Bucket", `${bucket_id}`)
// TODO: error rate limit message translation
- .send({ message: "You are being rate limited.", retry_after: resetAfterSec, global })
+ .send({
+ message: "You are being rate limited.",
+ retry_after: resetAfterSec,
+ global,
+ })
);
}
}
next();
- const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window };
+ const hitRouteOpts = {
+ bucket_id,
+ executor_id,
+ max_hits,
+ window: opts.window,
+ };
if (opts.error || opts.success) {
res.once("finish", () => {
// check if error and increment error rate limit
if (res.statusCode >= 400 && opts.error) {
return hitRoute(hitRouteOpts);
- } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
+ } else if (
+ res.statusCode >= 200 &&
+ res.statusCode < 300 &&
+ opts.success
+ ) {
return hitRoute(hitRouteOpts);
}
});
@@ -141,8 +169,8 @@ export async function initRateLimits(app: Router) {
rateLimit({
bucket: "global",
onlyIp: true,
- ...ip
- })
+ ...ip,
+ }),
);
app.use(rateLimit({ bucket: "global", ...global }));
app.use(
@@ -150,17 +178,25 @@ export async function initRateLimits(app: Router) {
bucket: "error",
error: true,
onlyIp: true,
- ...error
- })
+ ...error,
+ }),
);
app.use("/guilds/:id", rateLimit(routes.guild));
app.use("/webhooks/:id", rateLimit(routes.webhook));
app.use("/channels/:id", rateLimit(routes.channel));
app.use("/auth/login", rateLimit(routes.auth.login));
- app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register }));
+ app.use(
+ "/auth/register",
+ rateLimit({ onlyIp: true, success: true, ...routes.auth.register }),
+ );
}
-async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number; }) {
+async function hitRoute(opts: {
+ executor_id: string;
+ bucket_id: string;
+ max_hits: number;
+ window: number;
+}) {
const id = opts.executor_id + opts.bucket_id;
let limit = Cache.get(id);
if (!limit) {
@@ -169,7 +205,7 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
executor_id: opts.executor_id,
expires_at: new Date(Date.now() + opts.window * 1000),
hits: 0,
- blocked: false
+ blocked: false,
};
Cache.set(id, limit);
}
@@ -205,4 +241,4 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
}
await ratelimit.save();
*/
-}
\ No newline at end of file
+}
diff --git a/src/api/middlewares/Translation.ts b/src/api/middlewares/Translation.ts
index c0b7a4b8..05038040 100644
--- a/src/api/middlewares/Translation.ts
+++ b/src/api/middlewares/Translation.ts
@@ -9,8 +9,12 @@ const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets");
export async function initTranslation(router: Router) {
const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales"));
- const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en"));
- const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5));
+ const namespaces = fs.readdirSync(
+ path.join(ASSET_FOLDER_PATH, "locales", "en"),
+ );
+ const ns = namespaces
+ .filter((x) => x.endsWith(".json"))
+ .map((x) => x.slice(0, x.length - 5));
await i18next
.use(i18nextBackend)
@@ -21,9 +25,11 @@ export async function initTranslation(router: Router) {
fallbackLng: "en",
ns,
backend: {
- loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
+ loadPath:
+ path.join(ASSET_FOLDER_PATH, "locales") +
+ "/{{lng}}/{{ns}}.json",
},
- load: "all"
+ load: "all",
});
router.use(i18nextMiddleware.handle(i18next, {}));
diff --git a/src/api/routes/-/monitorz.ts b/src/api/routes/-/monitorz.ts
index f85cd099..630a832b 100644
--- a/src/api/routes/-/monitorz.ts
+++ b/src/api/routes/-/monitorz.ts
@@ -5,14 +5,18 @@ import os from "os";
const router = Router();
-router.get("/", route({ right: "OPERATOR" }), async (req: Request, res: Response) => {
- return res.json({
- load: os.loadavg(),
- procUptime: process.uptime(),
- sysUptime: os.uptime(),
- memPercent: 100 - ((os.freemem() / os.totalmem()) * 100),
- sessions: await Session.count(),
- })
-})
+router.get(
+ "/",
+ route({ right: "OPERATOR" }),
+ async (req: Request, res: Response) => {
+ return res.json({
+ load: os.loadavg(),
+ procUptime: process.uptime(),
+ sysUptime: os.uptime(),
+ memPercent: 100 - (os.freemem() / os.totalmem()) * 100,
+ sessions: await Session.count(),
+ });
+ },
+);
-export default router;
\ No newline at end of file
+export default router;
diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts
index f4c2bd16..0ae946ed 100644
--- a/src/api/routes/auth/location-metadata.ts
+++ b/src/api/routes/auth/location-metadata.ts
@@ -3,11 +3,15 @@ import { route } from "@fosscord/api";
import { getIpAdress, IPAnalysis } from "@fosscord/api";
const router = Router();
-router.get("/",route({}), async (req: Request, res: Response) => {
- //TODO
- //Note: It's most likely related to legal. At the moment Discord hasn't finished this too
- const country_code = (await IPAnalysis(getIpAdress(req))).country_code;
- res.json({ consent_required: false, country_code: country_code, promotional_email_opt_in: { required: true, pre_checked: false}});
+router.get("/", route({}), async (req: Request, res: Response) => {
+ //TODO
+ //Note: It's most likely related to legal. At the moment Discord hasn't finished this too
+ const country_code = (await IPAnalysis(getIpAdress(req))).country_code;
+ res.json({
+ consent_required: false,
+ country_code: country_code,
+ promotional_email_opt_in: { required: true, pre_checked: false },
+ });
});
export default router;
diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts
index 9bed5aab..9ea2606c 100644
--- a/src/api/routes/auth/login.ts
+++ b/src/api/routes/auth/login.ts
@@ -1,84 +1,127 @@
import { Request, Response, Router } from "express";
import { route, getIpAdress, verifyCaptcha } from "@fosscord/api";
import bcrypt from "bcrypt";
-import { Config, User, generateToken, adjustEmail, FieldErrors, LoginSchema } from "@fosscord/util";
+import {
+ Config,
+ User,
+ generateToken,
+ adjustEmail,
+ FieldErrors,
+ LoginSchema,
+} from "@fosscord/util";
import crypto from "crypto";
const router: Router = Router();
export default router;
-router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Response) => {
- const { login, password, captcha_key, undelete } = req.body as LoginSchema;
- const email = adjustEmail(login);
- console.log("login", email);
+router.post(
+ "/",
+ route({ body: "LoginSchema" }),
+ async (req: Request, res: Response) => {
+ const { login, password, captcha_key, undelete } =
+ req.body as LoginSchema;
+ const email = adjustEmail(login);
+ console.log("login", email);
+
+ const config = Config.get();
+
+ if (config.login.requireCaptcha && config.security.captcha.enabled) {
+ const { sitekey, service } = config.security.captcha;
+ if (!captcha_key) {
+ return res.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+
+ const ip = getIpAdress(req);
+ const verify = await verifyCaptcha(captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- const config = Config.get();
+ const user = await User.findOneOrFail({
+ where: [{ phone: login }, { email: login }],
+ select: [
+ "data",
+ "id",
+ "disabled",
+ "deleted",
+ "settings",
+ "totp_secret",
+ "mfa_enabled",
+ ],
+ }).catch((e) => {
+ throw FieldErrors({
+ login: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ });
+ });
+
+ if (undelete) {
+ // undelete refers to un'disable' here
+ if (user.disabled)
+ await User.update({ id: user.id }, { disabled: false });
+ if (user.deleted)
+ await User.update({ id: user.id }, { deleted: false });
+ } else {
+ if (user.deleted)
+ return res.status(400).json({
+ message: "This account is scheduled for deletion.",
+ code: 20011,
+ });
+ if (user.disabled)
+ return res.status(400).json({
+ message: req.t("auth:login.ACCOUNT_DISABLED"),
+ code: 20013,
+ });
+ }
- if (config.login.requireCaptcha && config.security.captcha.enabled) {
- const { sitekey, service } = config.security.captcha;
- if (!captcha_key) {
- return res.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service
+ // the salt is saved in the password refer to bcrypt docs
+ const same_password = await bcrypt.compare(
+ password,
+ user.data.hash || "",
+ );
+ if (!same_password) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
});
}
- const ip = getIpAdress(req);
- const verify = await verifyCaptcha(captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service
+ if (user.mfa_enabled) {
+ // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
+ const ticket = crypto.randomBytes(40).toString("hex");
+
+ await User.update({ id: user.id }, { totp_last_ticket: ticket });
+
+ return res.json({
+ ticket: ticket,
+ mfa: true,
+ sms: false, // TODO
+ token: null,
});
}
- }
-
- const user = await User.findOneOrFail({
- where: [{ phone: login }, { email: login }],
- select: ["data", "id", "disabled", "deleted", "settings", "totp_secret", "mfa_enabled"]
- }).catch((e) => {
- throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } });
- });
-
- if (undelete) {
- // undelete refers to un'disable' here
- if (user.disabled) await User.update({ id: user.id }, { disabled: false });
- if (user.deleted) await User.update({ id: user.id }, { deleted: false });
- } else {
- if (user.deleted) return res.status(400).json({ message: "This account is scheduled for deletion.", code: 20011 });
- if (user.disabled) return res.status(400).json({ message: req.t("auth:login.ACCOUNT_DISABLED"), code: 20013 });
- }
-
- // the salt is saved in the password refer to bcrypt docs
- const same_password = await bcrypt.compare(password, user.data.hash || "");
- if (!same_password) {
- throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
- }
-
- if (user.mfa_enabled) {
- // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
- const ticket = crypto.randomBytes(40).toString("hex");
-
- await User.update({ id: user.id }, { totp_last_ticket: ticket });
-
- return res.json({
- ticket: ticket,
- mfa: true,
- sms: false, // TODO
- token: null,
- })
- }
-
- const token = await generateToken(user.id);
-
- // Notice this will have a different token structure, than discord
- // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package
- // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
-
- res.json({ token, settings: user.settings });
-});
+
+ const token = await generateToken(user.id);
+
+ // Notice this will have a different token structure, than discord
+ // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package
+ // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
+
+ res.json({ token, settings: user.settings });
+ },
+);
/**
* POST /auth/login
diff --git a/src/api/routes/auth/logout.ts b/src/api/routes/auth/logout.ts
index e806fed9..e1bdbea3 100644
--- a/src/api/routes/auth/logout.ts
+++ b/src/api/routes/auth/logout.ts
@@ -10,7 +10,8 @@ router.post("/", route({}), async (req: Request, res: Response) => {
} else {
delete req.body.provider;
delete req.body.voip_provider;
- if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
+ if (Object.keys(req.body).length != 0)
+ console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
}
res.status(204).send();
-});
\ No newline at end of file
+});
diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts
index 96a48b66..83cf7648 100644
--- a/src/api/routes/auth/mfa/totp.ts
+++ b/src/api/routes/auth/mfa/totp.ts
@@ -5,45 +5,48 @@ import { verifyToken } from "node-2fa";
import { HTTPError } from "lambert-server";
const router = Router();
-router.post("/", route({ body: "TotpSchema" }), async (req: Request, res: Response) => {
- const { code, ticket, gift_code_sku_id, login_source } = req.body as TotpSchema;
+router.post(
+ "/",
+ route({ body: "TotpSchema" }),
+ async (req: Request, res: Response) => {
+ const { code, ticket, gift_code_sku_id, login_source } =
+ req.body as TotpSchema;
- const user = await User.findOneOrFail({
- where: {
- totp_last_ticket: ticket,
- },
- select: [
- "id",
- "totp_secret",
- "settings",
- ],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ totp_last_ticket: ticket,
+ },
+ select: ["id", "totp_secret", "settings"],
+ });
- const backup = await BackupCode.findOne({
- where: {
- code: code,
- expired: false,
- consumed: false,
- user: { id: user.id }
- }
- });
+ const backup = await BackupCode.findOne({
+ where: {
+ code: code,
+ expired: false,
+ consumed: false,
+ user: { id: user.id },
+ },
+ });
- if (!backup) {
- const ret = verifyToken(user.totp_secret!, code);
- if (!ret || ret.delta != 0)
- throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- }
- else {
- backup.consumed = true;
- await backup.save();
- }
+ if (!backup) {
+ const ret = verifyToken(user.totp_secret!, code);
+ if (!ret || ret.delta != 0)
+ throw new HTTPError(
+ req.t("auth:login.INVALID_TOTP_CODE"),
+ 60008,
+ );
+ } else {
+ backup.consumed = true;
+ await backup.save();
+ }
- await User.update({ id: user.id }, { totp_last_ticket: "" });
+ await User.update({ id: user.id }, { totp_last_ticket: "" });
- return res.json({
- token: await generateToken(user.id),
- user_settings: user.settings,
- });
-});
+ return res.json({
+ token: await generateToken(user.id),
+ user_settings: user.settings,
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index 84f8f838..3479c4a0 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -1,156 +1,215 @@
import { Request, Response, Router } from "express";
-import { Config, generateToken, Invite, FieldErrors, User, adjustEmail, RegisterSchema } from "@fosscord/util";
-import { route, getIpAdress, IPAnalysis, isProxy, verifyCaptcha } from "@fosscord/api";
+import {
+ Config,
+ generateToken,
+ Invite,
+ FieldErrors,
+ User,
+ adjustEmail,
+ RegisterSchema,
+} from "@fosscord/util";
+import {
+ route,
+ getIpAdress,
+ IPAnalysis,
+ isProxy,
+ verifyCaptcha,
+} from "@fosscord/api";
import bcrypt from "bcrypt";
import { HTTPError } from "lambert-server";
const router: Router = Router();
-router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Response) => {
- const body = req.body as RegisterSchema;
- const { register, security } = Config.get();
- const ip = getIpAdress(req);
-
- // email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick
- let email = adjustEmail(body.email);
-
- // check if registration is allowed
- if (!register.allowNewRegistration) {
- throw FieldErrors({
- email: { code: "REGISTRATION_DISABLED", message: req.t("auth:register.REGISTRATION_DISABLED") }
- });
- }
-
- // check if the user agreed to the Terms of Service
- if (!body.consent) {
- throw FieldErrors({
- consent: { code: "CONSENT_REQUIRED", message: req.t("auth:register.CONSENT_REQUIRED") }
- });
- }
-
- if (register.disabled) {
- throw FieldErrors({
- email: {
- code: "DISABLED",
- message: "registration is disabled on this instance"
- }
- });
- }
-
- if (register.requireCaptcha && security.captcha.enabled) {
- const { sitekey, service } = security.captcha;
- if (!body.captcha_key) {
- return res?.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service
+router.post(
+ "/",
+ route({ body: "RegisterSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as RegisterSchema;
+ const { register, security } = Config.get();
+ const ip = getIpAdress(req);
+
+ // email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick
+ let email = adjustEmail(body.email);
+
+ // check if registration is allowed
+ if (!register.allowNewRegistration) {
+ throw FieldErrors({
+ email: {
+ code: "REGISTRATION_DISABLED",
+ message: req.t("auth:register.REGISTRATION_DISABLED"),
+ },
});
}
- const verify = await verifyCaptcha(body.captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service
+ // check if the user agreed to the Terms of Service
+ if (!body.consent) {
+ throw FieldErrors({
+ consent: {
+ code: "CONSENT_REQUIRED",
+ message: req.t("auth:register.CONSENT_REQUIRED"),
+ },
});
}
- }
-
- if (!register.allowMultipleAccounts) {
- // TODO: check if fingerprint was eligible generated
- const exists = await User.findOne({ where: { fingerprints: body.fingerprint }, select: ["id"] });
- if (exists) {
+ if (register.disabled) {
throw FieldErrors({
email: {
- code: "EMAIL_ALREADY_REGISTERED",
- message: req.t("auth:register.EMAIL_ALREADY_REGISTERED")
- }
+ code: "DISABLED",
+ message: "registration is disabled on this instance",
+ },
});
}
- }
- if (register.blockProxies) {
- if (isProxy(await IPAnalysis(ip))) {
- console.log(`proxy ${ip} blocked from registration`);
- throw new HTTPError("Your IP is blocked from registration");
+ if (register.requireCaptcha && security.captcha.enabled) {
+ const { sitekey, service } = security.captcha;
+ if (!body.captcha_key) {
+ return res?.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+
+ const verify = await verifyCaptcha(body.captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
}
- }
- // TODO: gift_code_sku_id?
- // TODO: check password strength
+ if (!register.allowMultipleAccounts) {
+ // TODO: check if fingerprint was eligible generated
+ const exists = await User.findOne({
+ where: { fingerprints: body.fingerprint },
+ select: ["id"],
+ });
+
+ if (exists) {
+ throw FieldErrors({
+ email: {
+ code: "EMAIL_ALREADY_REGISTERED",
+ message: req.t(
+ "auth:register.EMAIL_ALREADY_REGISTERED",
+ ),
+ },
+ });
+ }
+ }
- if (email) {
- // replace all dots and chars after +, if its a gmail.com email
- if (!email) {
- throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req?.t("auth:register.INVALID_EMAIL") } });
+ if (register.blockProxies) {
+ if (isProxy(await IPAnalysis(ip))) {
+ console.log(`proxy ${ip} blocked from registration`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
}
- // check if there is already an account with this email
- const exists = await User.findOne({ where: { email: email } });
+ // TODO: gift_code_sku_id?
+ // TODO: check password strength
+
+ if (email) {
+ // replace all dots and chars after +, if its a gmail.com email
+ if (!email) {
+ throw FieldErrors({
+ email: {
+ code: "INVALID_EMAIL",
+ message: req?.t("auth:register.INVALID_EMAIL"),
+ },
+ });
+ }
- if (exists) {
+ // check if there is already an account with this email
+ const exists = await User.findOne({ where: { email: email } });
+
+ if (exists) {
+ throw FieldErrors({
+ email: {
+ code: "EMAIL_ALREADY_REGISTERED",
+ message: req.t(
+ "auth:register.EMAIL_ALREADY_REGISTERED",
+ ),
+ },
+ });
+ }
+ } else if (register.email.required) {
throw FieldErrors({
email: {
- code: "EMAIL_ALREADY_REGISTERED",
- message: req.t("auth:register.EMAIL_ALREADY_REGISTERED")
- }
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
});
}
- } else if (register.email.required) {
- throw FieldErrors({
- email: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
- });
- }
-
- if (register.dateOfBirth.required && !body.date_of_birth) {
- throw FieldErrors({
- date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
- });
- } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) {
- const minimum = new Date();
- minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum);
- body.date_of_birth = new Date(body.date_of_birth as Date);
-
- // higher is younger
- if (body.date_of_birth > minimum) {
+
+ if (register.dateOfBirth.required && !body.date_of_birth) {
throw FieldErrors({
date_of_birth: {
- code: "DATE_OF_BIRTH_UNDERAGE",
- message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { years: register.dateOfBirth.minimum })
- }
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ } else if (
+ register.dateOfBirth.required &&
+ register.dateOfBirth.minimum
+ ) {
+ const minimum = new Date();
+ minimum.setFullYear(
+ minimum.getFullYear() - register.dateOfBirth.minimum,
+ );
+ body.date_of_birth = new Date(body.date_of_birth as Date);
+
+ // higher is younger
+ if (body.date_of_birth > minimum) {
+ throw FieldErrors({
+ date_of_birth: {
+ code: "DATE_OF_BIRTH_UNDERAGE",
+ message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", {
+ years: register.dateOfBirth.minimum,
+ }),
+ },
+ });
+ }
+ }
+
+ if (body.password) {
+ // the salt is saved in the password refer to bcrypt docs
+ body.password = await bcrypt.hash(body.password, 12);
+ } else if (register.password.required) {
+ throw FieldErrors({
+ password: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
+
+ if (
+ !body.invite &&
+ (register.requireInvite ||
+ (register.guestsRequireInvite && !register.email))
+ ) {
+ // require invite to register -> e.g. for organizations to send invites to their employees
+ throw FieldErrors({
+ email: {
+ code: "INVITE_ONLY",
+ message: req.t("auth:register.INVITE_ONLY"),
+ },
});
}
- }
-
- if (body.password) {
- // the salt is saved in the password refer to bcrypt docs
- body.password = await bcrypt.hash(body.password, 12);
- } else if (register.password.required) {
- throw FieldErrors({
- password: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
- });
- }
-
- if (!body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) {
- // require invite to register -> e.g. for organizations to send invites to their employees
- throw FieldErrors({
- email: { code: "INVITE_ONLY", message: req.t("auth:register.INVITE_ONLY") }
- });
- }
-
- const user = await User.register({ ...body, req });
-
- if (body.invite) {
- // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible)
- await Invite.joinGuild(user.id, body.invite);
- }
-
- console.log("register", body.email, body.username, ip);
-
- return res.json({ token: await generateToken(user.id) });
-});
+
+ const user = await User.register({ ...body, req });
+
+ if (body.invite) {
+ // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible)
+ await Invite.joinGuild(user.id, body.invite);
+ }
+
+ console.log("register", body.email, body.username, ip);
+
+ return res.json({ token: await generateToken(user.id) });
+ },
+);
export default router;
diff --git a/src/api/routes/auth/verify/view-backup-codes-challenge.ts b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
index 24de8ec5..65f0a57c 100644
--- a/src/api/routes/auth/verify/view-backup-codes-challenge.ts
+++ b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
@@ -4,19 +4,31 @@ import { FieldErrors, User, BackupCodesChallengeSchema } from "@fosscord/util";
import bcrypt from "bcrypt";
const router = Router();
-router.post("/", route({ body: "BackupCodesChallengeSchema" }), async (req: Request, res: Response) => {
- const { password } = req.body as BackupCodesChallengeSchema;
+router.post(
+ "/",
+ route({ body: "BackupCodesChallengeSchema" }),
+ async (req: Request, res: Response) => {
+ const { password } = req.body as BackupCodesChallengeSchema;
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- if (!await bcrypt.compare(password, user.data.hash || "")) {
- throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
- }
+ if (!(await bcrypt.compare(password, user.data.hash || ""))) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- return res.json({
- nonce: "NoncePlaceholder",
- regenerate_nonce: "RegenNoncePlaceholder",
- });
-});
+ return res.json({
+ nonce: "NoncePlaceholder",
+ regenerate_nonce: "RegenNoncePlaceholder",
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts
index 8dbefe1b..a164fff6 100644
--- a/src/api/routes/channels/#channel_id/index.ts
+++ b/src/api/routes/channels/#channel_id/index.ts
@@ -6,7 +6,7 @@ import {
emitEvent,
Recipient,
handleFile,
- ChannelModifySchema
+ ChannelModifySchema,
} from "@fosscord/util";
import { Request, Response, Router } from "express";
import { route } from "@fosscord/api";
@@ -15,56 +15,89 @@ const router: Router = Router();
// TODO: delete channel
// TODO: Get channel
-router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+router.get(
+ "/",
+ route({ permission: "VIEW_CHANNEL" }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- return res.send(channel);
-});
+ return res.send(channel);
+ },
+);
-router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+router.delete(
+ "/",
+ route({ permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients"],
+ });
- if (channel.type === ChannelType.DM) {
- const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } });
- recipient.closed = true;
- await Promise.all([
- recipient.save(),
- emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent)
- ]);
- } else if (channel.type === ChannelType.GROUP_DM) {
- await Channel.removeRecipientFromChannel(channel, req.user_id);
- } else {
- await Promise.all([
- Channel.delete({ id: channel_id }),
- emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent)
- ]);
- }
+ if (channel.type === ChannelType.DM) {
+ const recipient = await Recipient.findOneOrFail({
+ where: { channel_id: channel_id, user_id: req.user_id },
+ });
+ recipient.closed = true;
+ await Promise.all([
+ recipient.save(),
+ emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel,
+ user_id: req.user_id,
+ } as ChannelDeleteEvent),
+ ]);
+ } else if (channel.type === ChannelType.GROUP_DM) {
+ await Channel.removeRecipientFromChannel(channel, req.user_id);
+ } else {
+ await Promise.all([
+ Channel.delete({ id: channel_id }),
+ emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel,
+ channel_id,
+ } as ChannelDeleteEvent),
+ ]);
+ }
- res.send(channel);
-});
+ res.send(channel);
+ },
+);
-router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- var payload = req.body as ChannelModifySchema;
- const { channel_id } = req.params;
- if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
+router.patch(
+ "/",
+ route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ var payload = req.body as ChannelModifySchema;
+ const { channel_id } = req.params;
+ if (payload.icon)
+ payload.icon = await handleFile(
+ `/channel-icons/${channel_id}`,
+ payload.icon,
+ );
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- channel.assign(payload);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ channel.assign(payload);
- await Promise.all([
- channel.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- data: channel,
- channel_id
- } as ChannelUpdateEvent)
- ]);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id,
+ } as ChannelUpdateEvent),
+ ]);
- res.send(channel);
-});
+ res.send(channel);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts
index 246a2c69..afaabf47 100644
--- a/src/api/routes/channels/#channel_id/invites.ts
+++ b/src/api/routes/channels/#channel_id/invites.ts
@@ -2,16 +2,33 @@ import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
import { random } from "@fosscord/api";
-import { Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util";
+import {
+ Channel,
+ Invite,
+ InviteCreateEvent,
+ emitEvent,
+ User,
+ Guild,
+ PublicInviteRelation,
+} from "@fosscord/util";
import { isTextChannel } from "./messages";
const router: Router = Router();
-router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE", right: "CREATE_INVITES" }),
+router.post(
+ "/",
+ route({
+ body: "InviteCreateSchema",
+ permission: "CREATE_INSTANT_INVITE",
+ right: "CREATE_INVITES",
+ }),
async (req: Request, res: Response) => {
const { user_id } = req;
const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ select: ["id", "name", "type", "guild_id"],
+ });
isTextChannel(channel.type);
if (!channel.guild_id) {
@@ -31,30 +48,44 @@ router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT
created_at: new Date(),
guild_id,
channel_id: channel_id,
- inviter_id: user_id
+ inviter_id: user_id,
}).save();
const data = invite.toJSON();
data.inviter = await User.getPublicUser(req.user_id);
data.guild = await Guild.findOne({ where: { id: guild_id } });
data.channel = channel;
- await emitEvent({ event: "INVITE_CREATE", data, guild_id } as InviteCreateEvent);
+ await emitEvent({
+ event: "INVITE_CREATE",
+ data,
+ guild_id,
+ } as InviteCreateEvent);
res.status(201).send(data);
- });
+ },
+);
-router.get("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- const { user_id } = req;
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+router.get(
+ "/",
+ route({ permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req;
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- if (!channel.guild_id) {
- throw new HTTPError("This channel doesn't exist", 404);
- }
- const { guild_id } = channel;
+ if (!channel.guild_id) {
+ throw new HTTPError("This channel doesn't exist", 404);
+ }
+ const { guild_id } = channel;
- const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation });
+ const invites = await Invite.find({
+ where: { guild_id },
+ relations: PublicInviteRelation,
+ });
- res.status(200).send(invites);
-});
+ res.status(200).send(invites);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
index bedd453c..1a30143f 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -1,4 +1,9 @@
-import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util";
+import {
+ emitEvent,
+ getPermission,
+ MessageAckEvent,
+ ReadState,
+} from "@fosscord/util";
import { Request, Response, Router } from "express";
import { route } from "@fosscord/api";
@@ -8,29 +13,40 @@ const router = Router();
// TODO: send read state event to all channel members
// TODO: advance-only notification cursor
-router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+router.post(
+ "/",
+ route({ body: "MessageAcknowledgeSchema" }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const permission = await getPermission(req.user_id, undefined, channel_id);
- permission.hasThrow("VIEW_CHANNEL");
-
- let read_state = await ReadState.findOne({ where: { user_id: req.user_id, channel_id } });
- if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
- read_state.last_message_id = message_id;
-
- await read_state.save();
-
- await emitEvent({
- event: "MESSAGE_ACK",
- user_id: req.user_id,
- data: {
+ const permission = await getPermission(
+ req.user_id,
+ undefined,
channel_id,
- message_id,
- version: 3763
- }
- } as MessageAckEvent);
-
- res.json({ token: null });
-});
+ );
+ permission.hasThrow("VIEW_CHANNEL");
+
+ let read_state = await ReadState.findOne({
+ where: { user_id: req.user_id, channel_id },
+ });
+ if (!read_state)
+ read_state = ReadState.create({ user_id: req.user_id, channel_id });
+ read_state.last_message_id = message_id;
+
+ await read_state.save();
+
+ await emitEvent({
+ event: "MESSAGE_ACK",
+ user_id: req.user_id,
+ data: {
+ channel_id,
+ message_id,
+ version: 3763,
+ },
+ } as MessageAckEvent);
+
+ res.json({ token: null });
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
index b2cb6763..d8b55ccd 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
@@ -3,26 +3,36 @@ import { route } from "@fosscord/api";
const router = Router();
-router.post("/", route({ permission: "MANAGE_MESSAGES" }), (req: Request, res: Response) => {
- // TODO:
- res.json({
- id: "",
- type: 0,
- content: "",
- channel_id: "",
- author: { id: "", username: "", avatar: "", discriminator: "", public_flags: 64 },
- attachments: [],
- embeds: [],
- mentions: [],
- mention_roles: [],
- pinned: false,
- mention_everyone: false,
- tts: false,
- timestamp: "",
- edited_timestamp: null,
- flags: 1,
- components: []
- }).status(200);
-});
+router.post(
+ "/",
+ route({ permission: "MANAGE_MESSAGES" }),
+ (req: Request, res: Response) => {
+ // TODO:
+ res.json({
+ id: "",
+ type: 0,
+ content: "",
+ channel_id: "",
+ author: {
+ id: "",
+ username: "",
+ avatar: "",
+ discriminator: "",
+ public_flags: 64,
+ },
+ attachments: [],
+ embeds: [],
+ mentions: [],
+ mention_roles: [],
+ pinned: false,
+ mention_everyone: false,
+ tts: false,
+ timestamp: "",
+ edited_timestamp: null,
+ flags: 1,
+ components: [],
+ }).status(200);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
index 46b0d6bd..3abfebe8 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -26,55 +26,69 @@ const messageUpload = multer({
limits: {
fileSize: 1024 * 1024 * 100,
fields: 10,
- files: 1
+ files: 1,
},
- storage: multer.memoryStorage()
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
-router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- var body = req.body as MessageCreateSchema;
+router.patch(
+ "/",
+ route({
+ body: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_MESSAGES",
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ var body = req.body as MessageCreateSchema;
- const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ relations: ["attachments"],
+ });
- const permissions = await getPermission(req.user_id, undefined, channel_id);
+ const permissions = await getPermission(
+ req.user_id,
+ undefined,
+ channel_id,
+ );
- const rights = await getRights(req.user_id);
+ const rights = await getRights(req.user_id);
- if ((req.user_id !== message.author_id)) {
- if (!rights.has("MANAGE_MESSAGES")) {
- permissions.hasThrow("MANAGE_MESSAGES");
- body = { flags: body.flags };
- // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins
- }
- } else rights.hasThrow("SELF_EDIT_MESSAGES");
-
- const new_message = await handleMessage({
- ...message,
- // TODO: should message_reference be overridable?
- // @ts-ignore
- message_reference: message.message_reference,
- ...body,
- author_id: message.author_id,
- channel_id,
- id: message_id,
- edited_timestamp: new Date()
- });
-
- await Promise.all([
- new_message!.save(),
- await emitEvent({
- event: "MESSAGE_UPDATE",
+ if (req.user_id !== message.author_id) {
+ if (!rights.has("MANAGE_MESSAGES")) {
+ permissions.hasThrow("MANAGE_MESSAGES");
+ body = { flags: body.flags };
+ // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins
+ }
+ } else rights.hasThrow("SELF_EDIT_MESSAGES");
+
+ const new_message = await handleMessage({
+ ...message,
+ // TODO: should message_reference be overridable?
+ // @ts-ignore
+ message_reference: message.message_reference,
+ ...body,
+ author_id: message.author_id,
channel_id,
- data: { ...new_message, nonce: undefined }
- } as MessageUpdateEvent)
- ]);
+ id: message_id,
+ edited_timestamp: new Date(),
+ });
- postHandleMessage(message);
+ await Promise.all([
+ new_message!.save(),
+ await emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: { ...new_message, nonce: undefined },
+ } as MessageUpdateEvent),
+ ]);
- return res.json(message);
-});
+ postHandleMessage(message);
+ return res.json(message);
+ },
+);
// Backfill message with specific timestamp
router.put(
@@ -87,7 +101,11 @@ router.put(
next();
},
- route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_BACKDATED_EVENTS" }),
+ route({
+ body: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_BACKDATED_EVENTS",
+ }),
async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
var body = req.body as MessageCreateSchema;
@@ -107,20 +125,30 @@ router.put(
throw FosscordApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE;
}
- const exists = await Message.findOne({ where: { id: message_id, channel_id: channel_id } });
+ const exists = await Message.findOne({
+ where: { id: message_id, channel_id: channel_id },
+ });
if (exists) {
throw FosscordApiErrors.CANNOT_REPLACE_BY_BACKFILL;
}
if (req.file) {
try {
- const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ const file = await uploadFile(
+ `/attachments/${req.params.channel_id}`,
+ req.file,
+ );
+ attachments.push(
+ Attachment.create({ ...file, proxy_url: file.url }),
+ );
} catch (error) {
return res.status(400).json(error);
}
}
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients", "recipients.user"],
+ });
const embeds = body.embeds || [];
if (body.embed) embeds.push(body.embed);
@@ -142,27 +170,43 @@ router.put(
await Promise.all([
message.save(),
- emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent),
- channel.save()
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ channel.save(),
]);
- postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error
return res.json(message);
- }
+ },
);
-router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+router.get(
+ "/",
+ route({ permission: "VIEW_CHANNEL" }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
- const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ relations: ["attachments"],
+ });
- const permissions = await getPermission(req.user_id, undefined, channel_id);
+ const permissions = await getPermission(
+ req.user_id,
+ undefined,
+ channel_id,
+ );
- if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY");
+ if (message.author_id !== req.user_id)
+ permissions.hasThrow("READ_MESSAGE_HISTORY");
- return res.json(message);
-});
+ return res.json(message);
+ },
+);
router.delete("/", route({}), async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
@@ -172,9 +216,13 @@ router.delete("/", route({}), async (req: Request, res: Response) => {
const rights = await getRights(req.user_id);
- if ((message.author_id !== req.user_id)) {
+ if (message.author_id !== req.user_id) {
if (!rights.has("MANAGE_MESSAGES")) {
- const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
+ const permission = await getPermission(
+ req.user_id,
+ channel.guild_id,
+ channel_id,
+ );
permission.hasThrow("MANAGE_MESSAGES");
}
} else rights.hasThrow("SELF_DELETE_MESSAGES");
@@ -187,8 +235,8 @@ router.delete("/", route({}), async (req: Request, res: Response) => {
data: {
id: message_id,
channel_id,
- guild_id: channel.guild_id
- }
+ guild_id: channel.guild_id,
+ },
} as MessageDeleteEvent);
res.sendStatus(204);
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
index c3cca05d..9f774682 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -11,7 +11,7 @@ import {
MessageReactionRemoveEvent,
PartialEmoji,
PublicUserProjection,
- User
+ User,
} from "@fosscord/util";
import { route } from "@fosscord/api";
import { Router, Response, Request } from "express";
@@ -27,159 +27,224 @@ function getEmoji(emoji: string): PartialEmoji {
if (parts)
return {
name: parts[0],
- id: parts[1]
+ id: parts[1],
};
return {
id: undefined,
- name: emoji
+ name: emoji,
};
}
-router.delete("/", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+router.delete(
+ "/",
+ route({ permission: "MANAGE_MESSAGES" }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- await Message.update({ id: message_id, channel_id }, { reactions: [] });
+ await Message.update({ id: message_id, channel_id }, { reactions: [] });
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE_ALL",
- channel_id,
- data: {
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE_ALL",
channel_id,
- message_id,
- guild_id: channel.guild_id
+ data: {
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageReactionRemoveAllEvent);
+
+ res.sendStatus(204);
+ },
+);
+
+router.delete(
+ "/:emoji",
+ route({ permission: "MANAGE_MESSAGES" }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ const emoji = getEmoji(req.params.emoji);
+
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
+
+ const already_added = message.reactions.find(
+ (x) =>
+ (x.emoji.id === emoji.id && emoji.id) ||
+ x.emoji.name === emoji.name,
+ );
+ if (!already_added) throw new HTTPError("Reaction not found", 404);
+ message.reactions.remove(already_added);
+
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_REACTION_REMOVE_EMOJI",
+ channel_id,
+ data: {
+ channel_id,
+ message_id,
+ guild_id: message.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEmojiEvent),
+ ]);
+
+ res.sendStatus(204);
+ },
+);
+
+router.get(
+ "/:emoji",
+ route({ permission: "VIEW_CHANNEL" }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ const emoji = getEmoji(req.params.emoji);
+
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
+ const reaction = message.reactions.find(
+ (x) =>
+ (x.emoji.id === emoji.id && emoji.id) ||
+ x.emoji.name === emoji.name,
+ );
+ if (!reaction) throw new HTTPError("Reaction not found", 404);
+
+ const users = await User.find({
+ where: {
+ id: In(reaction.user_ids),
+ },
+ select: PublicUserProjection,
+ });
+
+ res.json(users);
+ },
+);
+
+router.put(
+ "/:emoji/:user_id",
+ route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id, user_id } = req.params;
+ if (user_id !== "@me") throw new HTTPError("Invalid user");
+ const emoji = getEmoji(req.params.emoji);
+
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
+ const already_added = message.reactions.find(
+ (x) =>
+ (x.emoji.id === emoji.id && emoji.id) ||
+ x.emoji.name === emoji.name,
+ );
+
+ if (!already_added) req.permission!.hasThrow("ADD_REACTIONS");
+
+ if (emoji.id) {
+ const external_emoji = await Emoji.findOneOrFail({
+ where: { id: emoji.id },
+ });
+ if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS");
+ emoji.animated = external_emoji.animated;
+ emoji.name = external_emoji.name;
}
- } as MessageReactionRemoveAllEvent);
-
- res.sendStatus(204);
-});
-router.delete("/:emoji", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
-
- const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
-
- const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!already_added) throw new HTTPError("Reaction not found", 404);
- message.reactions.remove(already_added);
-
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_REACTION_REMOVE_EMOJI",
+ if (already_added) {
+ if (already_added.user_ids.includes(req.user_id))
+ return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
+ already_added.count++;
+ } else
+ message.reactions.push({
+ count: 1,
+ emoji,
+ user_ids: [req.user_id],
+ });
+
+ await message.save();
+
+ const member =
+ channel.guild_id &&
+ (await Member.findOneOrFail({ where: { id: req.user_id } }));
+
+ await emitEvent({
+ event: "MESSAGE_REACTION_ADD",
channel_id,
data: {
+ user_id: req.user_id,
channel_id,
message_id,
- guild_id: message.guild_id,
- emoji
- }
- } as MessageReactionRemoveEmojiEvent)
- ]);
-
- res.sendStatus(204);
-});
-
-router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
-
- const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
- const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!reaction) throw new HTTPError("Reaction not found", 404);
-
- const users = await User.find({
- where: {
- id: In(reaction.user_ids)
- },
- select: PublicUserProjection
- });
-
- res.json(users);
-});
-
-router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }), async (req: Request, res: Response) => {
- const { message_id, channel_id, user_id } = req.params;
- if (user_id !== "@me") throw new HTTPError("Invalid user");
- const emoji = getEmoji(req.params.emoji);
-
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
- const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
-
- if (!already_added) req.permission!.hasThrow("ADD_REACTIONS");
-
- if (emoji.id) {
- const external_emoji = await Emoji.findOneOrFail({ where: { id: emoji.id } });
- if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS");
- emoji.animated = external_emoji.animated;
- emoji.name = external_emoji.name;
- }
-
- if (already_added) {
- if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
- already_added.count++;
- } else message.reactions.push({ count: 1, emoji, user_ids: [req.user_id] });
-
- await message.save();
-
- const member = channel.guild_id && (await Member.findOneOrFail({ where: { id: req.user_id } }));
-
- await emitEvent({
- event: "MESSAGE_REACTION_ADD",
- channel_id,
- data: {
- user_id: req.user_id,
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji,
- member
+ guild_id: channel.guild_id,
+ emoji,
+ member,
+ },
+ } as MessageReactionAddEvent);
+
+ res.sendStatus(204);
+ },
+);
+
+router.delete(
+ "/:emoji/:user_id",
+ route({}),
+ async (req: Request, res: Response) => {
+ var { message_id, channel_id, user_id } = req.params;
+
+ const emoji = getEmoji(req.params.emoji);
+
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
+
+ if (user_id === "@me") user_id = req.user_id;
+ else {
+ const permissions = await getPermission(
+ req.user_id,
+ undefined,
+ channel_id,
+ );
+ permissions.hasThrow("MANAGE_MESSAGES");
}
- } as MessageReactionAddEvent);
- res.sendStatus(204);
-});
+ const already_added = message.reactions.find(
+ (x) =>
+ (x.emoji.id === emoji.id && emoji.id) ||
+ x.emoji.name === emoji.name,
+ );
+ if (!already_added || !already_added.user_ids.includes(user_id))
+ throw new HTTPError("Reaction not found", 404);
-router.delete("/:emoji/:user_id", route({}), async (req: Request, res: Response) => {
- var { message_id, channel_id, user_id } = req.params;
+ already_added.count--;
- const emoji = getEmoji(req.params.emoji);
+ if (already_added.count <= 0) message.reactions.remove(already_added);
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
+ await message.save();
- if (user_id === "@me") user_id = req.user_id;
- else {
- const permissions = await getPermission(req.user_id, undefined, channel_id);
- permissions.hasThrow("MANAGE_MESSAGES");
- }
-
- const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
-
- already_added.count--;
-
- if (already_added.count <= 0) message.reactions.remove(already_added);
-
- await message.save();
-
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE",
- channel_id,
- data: {
- user_id: req.user_id,
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE",
channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji
- }
- } as MessageReactionRemoveEvent);
-
- res.sendStatus(204);
-});
+ data: {
+ user_id: req.user_id,
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEvent);
+
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
index 6493c16a..553ab17e 100644
--- a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
+++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -1,5 +1,13 @@
import { Router, Response, Request } from "express";
-import { Channel, Config, emitEvent, getPermission, getRights, MessageDeleteBulkEvent, Message } from "@fosscord/util";
+import {
+ Channel,
+ Config,
+ emitEvent,
+ getPermission,
+ getRights,
+ MessageDeleteBulkEvent,
+ Message,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
@@ -10,33 +18,48 @@ export default router;
// should users be able to bulk delete messages or only bots? ANSWER: all users
// should this request fail, if you provide messages older than 14 days/invalid ids? ANSWER: NO
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages
-router.post("/", route({ body: "BulkDeleteSchema" }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400);
-
- const rights = await getRights(req.user_id);
- rights.hasThrow("SELF_DELETE_MESSAGES");
-
- let superuser = rights.has("MANAGE_MESSAGES");
- const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
-
- const { maxBulkDelete } = Config.get().limits.message;
-
- const { messages } = req.body as { messages: string[] };
- if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete");
- if (!superuser) {
- permission.hasThrow("MANAGE_MESSAGES");
- if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
- }
-
- await Message.delete(messages);
-
- await emitEvent({
- event: "MESSAGE_DELETE_BULK",
- channel_id,
- data: { ids: messages, channel_id, guild_id: channel.guild_id }
- } as MessageDeleteBulkEvent);
-
- res.sendStatus(204);
-});
+router.post(
+ "/",
+ route({ body: "BulkDeleteSchema" }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id)
+ throw new HTTPError("Can't bulk delete dm channel messages", 400);
+
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("SELF_DELETE_MESSAGES");
+
+ let superuser = rights.has("MANAGE_MESSAGES");
+ const permission = await getPermission(
+ req.user_id,
+ channel?.guild_id,
+ channel_id,
+ );
+
+ const { maxBulkDelete } = Config.get().limits.message;
+
+ const { messages } = req.body as { messages: string[] };
+ if (messages.length === 0)
+ throw new HTTPError("You must specify messages to bulk delete");
+ if (!superuser) {
+ permission.hasThrow("MANAGE_MESSAGES");
+ if (messages.length > maxBulkDelete)
+ throw new HTTPError(
+ `You cannot delete more than ${maxBulkDelete} messages`,
+ );
+ }
+
+ await Message.delete(messages);
+
+ await emitEvent({
+ event: "MESSAGE_DELETE_BULK",
+ channel_id,
+ data: { ids: messages, channel_id, guild_id: channel.guild_id },
+ } as MessageDeleteBulkEvent);
+
+ res.sendStatus(204);
+ },
+);
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index bee93e80..631074c6 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -61,36 +61,50 @@ router.get("/", async (req: Request, res: Response) => {
const before = req.query.before ? `${req.query.before}` : undefined;
const after = req.query.after ? `${req.query.after}` : undefined;
const limit = Number(req.query.limit) || 50;
- if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
+ if (limit < 1 || limit > 100)
+ throw new HTTPError("limit must be between 1 and 100", 422);
var halfLimit = Math.floor(limit / 2);
- const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
+ const permissions = await getPermission(
+ req.user_id,
+ channel.guild_id,
+ channel_id,
+ );
permissions.hasThrow("VIEW_CHANNEL");
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
- var query: FindManyOptions<Message> & { where: { id?: any; }; } = {
+ var query: FindManyOptions<Message> & { where: { id?: any } } = {
order: { timestamp: "DESC" },
take: limit,
where: { channel_id },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"]
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ ],
};
if (after) {
- if (BigInt(after) > BigInt(Snowflake.generate())) return res.status(422);
+ if (BigInt(after) > BigInt(Snowflake.generate()))
+ return res.status(422);
query.where.id = MoreThan(after);
- }
- else if (before) {
- if (BigInt(before) < BigInt(req.params.channel_id)) return res.status(422);
+ } else if (before) {
+ if (BigInt(before) < BigInt(req.params.channel_id))
+ return res.status(422);
query.where.id = LessThan(before);
- }
- else if (around) {
+ } else if (around) {
query.where.id = [
MoreThan((BigInt(around) - BigInt(halfLimit)).toString()),
- LessThan((BigInt(around) + BigInt(halfLimit)).toString())
+ LessThan((BigInt(around) + BigInt(halfLimit)).toString()),
];
- return res.json([]); // TODO: fix around
+ return res.json([]); // TODO: fix around
}
const messages = await Message.find(query);
@@ -105,11 +119,22 @@ router.get("/", async (req: Request, res: Response) => {
delete x.user_ids;
});
// @ts-ignore
- if (!x.author) x.author = { id: "4", discriminator: "0000", username: "Fosscord Ghost", public_flags: "0", avatar: null };
+ if (!x.author)
+ x.author = {
+ id: "4",
+ discriminator: "0000",
+ username: "Fosscord Ghost",
+ public_flags: "0",
+ avatar: null,
+ };
x.attachments?.forEach((y: any) => {
// dynamically set attachment proxy_url in case the endpoint changed
- const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
- y.proxy_url = `${endpoint == null ? "" : endpoint}${new URL(uri).pathname}`;
+ const uri = y.proxy_url.startsWith("http")
+ ? y.proxy_url
+ : `https://example.org${y.proxy_url}`;
+ y.proxy_url = `${endpoint == null ? "" : endpoint}${
+ new URL(uri).pathname
+ }`;
});
/**
@@ -123,7 +148,7 @@ router.get("/", async (req: Request, res: Response) => {
// }
return x;
- })
+ }),
);
});
@@ -134,7 +159,7 @@ const messageUpload = multer({
fields: 10,
// files: 1
},
- storage: multer.memoryStorage()
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
/**
TODO: dynamically change limit of MessageCreateSchema with config
@@ -155,24 +180,38 @@ router.post(
next();
},
- route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }),
+ route({
+ body: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_MESSAGES",
+ }),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
var body = req.body as MessageCreateSchema;
const attachments: Attachment[] = [];
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients", "recipients.user"],
+ });
if (!channel.isWritable()) {
- throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400);
+ throw new HTTPError(
+ `Cannot send messages to channel of type ${channel.type}`,
+ 400,
+ );
}
- const files = req.files as Express.Multer.File[] ?? [];
+ const files = (req.files as Express.Multer.File[]) ?? [];
for (var currFile of files) {
try {
- const file = await uploadFile(`/attachments/${channel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
- }
- catch (error) {
+ const file = await uploadFile(
+ `/attachments/${channel.id}`,
+ currFile,
+ );
+ attachments.push(
+ Attachment.create({ ...file, proxy_url: file.url }),
+ );
+ } catch (error) {
return res.status(400).json(error);
}
}
@@ -188,7 +227,7 @@ router.post(
channel_id,
attachments,
edited_timestamp: undefined,
- timestamp: new Date()
+ timestamp: new Date(),
});
channel.last_message_id = message.id;
@@ -205,32 +244,47 @@ router.post(
recipient.save(),
emitEvent({
event: "CHANNEL_CREATE",
- data: channel_dto.excludedRecipients([recipient.user_id]),
- user_id: recipient.user_id
- })
+ data: channel_dto.excludedRecipients([
+ recipient.user_id,
+ ]),
+ user_id: recipient.user_id,
+ }),
]);
}
- })
+ }),
);
}
- const member = await Member.findOneOrFail({ where: { id: req.user_id }, relations: ["roles"] });
- member.roles = member.roles.filter((role: Role) => {
- return role.id !== role.guild_id;
- }).map((role: Role) => {
- return role.id;
- }) as any;
+ const member = await Member.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["roles"],
+ });
+ member.roles = member.roles
+ .filter((role: Role) => {
+ return role.id !== role.guild_id;
+ })
+ .map((role: Role) => {
+ return role.id;
+ }) as any;
await Promise.all([
message.save(),
- emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent),
- message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null,
- channel.save()
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ message.guild_id
+ ? Member.update(
+ { id: req.user_id, guild_id: message.guild_id },
+ { last_message_id: message.id },
+ )
+ : null,
+ channel.save(),
]);
- postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error
return res.json(message);
- }
+ },
);
-
diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts
index e74a0255..89be843f 100644
--- a/src/api/routes/channels/#channel_id/permissions.ts
+++ b/src/api/routes/channels/#channel_id/permissions.ts
@@ -6,7 +6,7 @@ import {
emitEvent,
getPermission,
Member,
- Role
+ Role,
} from "@fosscord/util";
import { Router, Response, Request } from "express";
import { HTTPError } from "lambert-server";
@@ -16,69 +16,90 @@ const router: Router = Router();
// TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel)
-export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite { }
+export interface ChannelPermissionOverwriteSchema
+ extends ChannelPermissionOverwrite {}
router.put(
"/:overwrite_id",
- route({ body: "ChannelPermissionOverwriteSchema", permission: "MANAGE_ROLES" }),
+ route({
+ body: "ChannelPermissionOverwriteSchema",
+ permission: "MANAGE_ROLES",
+ }),
async (req: Request, res: Response) => {
const { channel_id, overwrite_id } = req.params;
const body = req.body as ChannelPermissionOverwriteSchema;
- var channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ var channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
if (body.type === 0) {
- if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
+ if (!(await Role.count({ where: { id: overwrite_id } })))
+ throw new HTTPError("role not found", 404);
} else if (body.type === 1) {
- if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404);
+ if (!(await Member.count({ where: { id: overwrite_id } })))
+ throw new HTTPError("user not found", 404);
} else throw new HTTPError("type not supported", 501);
- // @ts-ignore
- var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id);
+ //@ts-ignore
+ var overwrite: ChannelPermissionOverwrite =
+ channel.permission_overwrites?.find((x) => x.id === overwrite_id);
if (!overwrite) {
// @ts-ignore
overwrite = {
id: overwrite_id,
- type: body.type
+ type: body.type,
};
channel.permission_overwrites!.push(overwrite);
}
- overwrite.allow = String(req.permission!.bitfield & (BigInt(body.allow) || BigInt("0")));
- overwrite.deny = String(req.permission!.bitfield & (BigInt(body.deny) || BigInt("0")));
+ overwrite.allow = String(
+ req.permission!.bitfield & (BigInt(body.allow) || BigInt("0")),
+ );
+ overwrite.deny = String(
+ req.permission!.bitfield & (BigInt(body.deny) || BigInt("0")),
+ );
await Promise.all([
channel.save(),
emitEvent({
event: "CHANNEL_UPDATE",
channel_id,
- data: channel
- } as ChannelUpdateEvent)
+ data: channel,
+ } as ChannelUpdateEvent),
]);
return res.sendStatus(204);
- }
+ },
);
// TODO: check permission hierarchy
-router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { channel_id, overwrite_id } = req.params;
+router.delete(
+ "/:overwrite_id",
+ route({ permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { channel_id, overwrite_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
- channel.permission_overwrites = channel.permission_overwrites!.filter((x) => x.id === overwrite_id);
+ channel.permission_overwrites = channel.permission_overwrites!.filter(
+ (x) => x.id === overwrite_id,
+ );
- await Promise.all([
- channel.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- channel_id,
- data: channel
- } as ChannelUpdateEvent)
- ]);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ channel_id,
+ data: channel,
+ } as ChannelUpdateEvent),
+ ]);
- return res.sendStatus(204);
-});
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts
index 30507c71..d3f6960a 100644
--- a/src/api/routes/channels/#channel_id/pins.ts
+++ b/src/api/routes/channels/#channel_id/pins.ts
@@ -6,7 +6,7 @@ import {
getPermission,
Message,
MessageUpdateEvent,
- DiscordApiErrors
+ DiscordApiErrors,
} from "@fosscord/util";
import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
@@ -14,77 +14,100 @@ import { route } from "@fosscord/api";
const router: Router = Router();
-router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
-
- const message = await Message.findOneOrFail({ where: { id: message_id } });
-
- // * in dm channels anyone can pin messages -> only check for guilds
- if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
-
- const pinned_count = await Message.count({ where: { channel: { id: channel_id }, pinned: true } });
- const { maxPins } = Config.get().limits.channel;
- if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
-
- await Promise.all([
- Message.update({ id: message_id }, { pinned: true }),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
+router.put(
+ "/:message_id",
+ route({ permission: "VIEW_CHANNEL" }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
+
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ });
+
+ // * in dm channels anyone can pin messages -> only check for guilds
+ if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
+
+ const pinned_count = await Message.count({
+ where: { channel: { id: channel_id }, pinned: true },
+ });
+ const { maxPins } = Config.get().limits.channel;
+ if (pinned_count >= maxPins)
+ throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
+
+ await Promise.all([
+ Message.update({ id: message_id }, { pinned: true }),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined
- }
- } as ChannelPinsUpdateEvent)
- ]);
-
- res.sendStatus(204);
-});
-
-router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
-
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
-
- const message = await Message.findOneOrFail({ where: { id: message_id } });
- message.pinned = false;
-
- await Promise.all([
- message.save(),
-
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message
- } as MessageUpdateEvent),
-
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
channel_id,
- guild_id: channel.guild_id,
- last_pin_timestamp: undefined
- }
- } as ChannelPinsUpdateEvent)
- ]);
-
- res.sendStatus(204);
-});
-
-router.get("/", route({ permission: ["READ_MESSAGE_HISTORY"] }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
-
- let pins = await Message.find({ where: { channel_id: channel_id, pinned: true } });
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ ]);
+
+ res.sendStatus(204);
+ },
+);
+
+router.delete(
+ "/:message_id",
+ route({ permission: "VIEW_CHANNEL" }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
+
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
+
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ });
+ message.pinned = false;
+
+ await Promise.all([
+ message.save(),
+
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
- res.send(pins);
-});
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: channel.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ ]);
+
+ res.sendStatus(204);
+ },
+);
+
+router.get(
+ "/",
+ route({ permission: ["READ_MESSAGE_HISTORY"] }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+
+ let pins = await Message.find({
+ where: { channel_id: channel_id, pinned: true },
+ });
+
+ res.send(pins);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/purge.ts b/src/api/routes/channels/#channel_id/purge.ts
index 9fe6b658..a9f88662 100644
--- a/src/api/routes/channels/#channel_id/purge.ts
+++ b/src/api/routes/channels/#channel_id/purge.ts
@@ -21,52 +21,79 @@ export default router;
/**
TODO: apply the delete bit by bit to prevent client and database stress
**/
-router.post("/", route({ /*body: "PurgeSchema",*/ }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+router.post(
+ "/",
+ route({
+ /*body: "PurgeSchema",*/
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400);
- isTextChannel(channel.type);
+ if (!channel.guild_id)
+ throw new HTTPError("Can't purge dm channels", 400);
+ isTextChannel(channel.type);
- const rights = await getRights(req.user_id);
- if (!rights.has("MANAGE_MESSAGES")) {
- const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
- permissions.hasThrow("MANAGE_MESSAGES");
- permissions.hasThrow("MANAGE_CHANNELS");
- }
+ const rights = await getRights(req.user_id);
+ if (!rights.has("MANAGE_MESSAGES")) {
+ const permissions = await getPermission(
+ req.user_id,
+ channel.guild_id,
+ channel_id,
+ );
+ permissions.hasThrow("MANAGE_MESSAGES");
+ permissions.hasThrow("MANAGE_CHANNELS");
+ }
- const { before, after } = req.body as PurgeSchema;
+ const { before, after } = req.body as PurgeSchema;
- // TODO: send the deletion event bite-by-bite to prevent client stress
-
- var query: FindManyOptions<Message> & { where: { id?: any; }; } = {
- order: { id: "ASC" },
- // take: limit,
- where: {
- channel_id,
- id: Between(after, before), // the right way around
- author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id)
- // if you lack the right of self-deletion, you can't delete your own messages, even in purges
- },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"]
- };
+ // TODO: send the deletion event bite-by-bite to prevent client stress
+ var query: FindManyOptions<Message> & { where: { id?: any } } = {
+ order: { id: "ASC" },
+ // take: limit,
+ where: {
+ channel_id,
+ id: Between(after, before), // the right way around
+ author_id: rights.has("SELF_DELETE_MESSAGES")
+ ? undefined
+ : Not(req.user_id),
+ // if you lack the right of self-deletion, you can't delete your own messages, even in purges
+ },
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ ],
+ };
- const messages = await Message.find(query);
- const endpoint = Config.get().cdn.endpointPublic;
+ const messages = await Message.find(query);
+ const endpoint = Config.get().cdn.endpointPublic;
- if (messages.length == 0) {
- res.sendStatus(304);
- return;
- }
+ if (messages.length == 0) {
+ res.sendStatus(304);
+ return;
+ }
- await Message.delete(messages.map((x) => x.id));
+ await Message.delete(messages.map((x) => x.id));
- await emitEvent({
- event: "MESSAGE_DELETE_BULK",
- channel_id,
- data: { ids: messages.map(x => x.id), channel_id, guild_id: channel.guild_id }
- } as MessageDeleteBulkEvent);
+ await emitEvent({
+ event: "MESSAGE_DELETE_BULK",
+ channel_id,
+ data: {
+ ids: messages.map((x) => x.id),
+ channel_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageDeleteBulkEvent);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts
index 25854415..cc7e5756 100644
--- a/src/api/routes/channels/#channel_id/recipients.ts
+++ b/src/api/routes/channels/#channel_id/recipients.ts
@@ -8,7 +8,7 @@ import {
emitEvent,
PublicUserProjection,
Recipient,
- User
+ User,
} from "@fosscord/util";
import { route } from "@fosscord/api";
@@ -16,34 +16,48 @@ const router: Router = Router();
router.put("/:user_id", route({}), async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients"],
+ });
if (channel.type !== ChannelType.GROUP_DM) {
- const recipients = [...channel.recipients!.map((r) => r.user_id), user_id].unique();
+ const recipients = [
+ ...channel.recipients!.map((r) => r.user_id),
+ user_id,
+ ].unique();
- const new_channel = await Channel.createDMChannel(recipients, req.user_id);
+ const new_channel = await Channel.createDMChannel(
+ recipients,
+ req.user_id,
+ );
return res.status(201).json(new_channel);
} else {
if (channel.recipients!.map((r) => r.user_id).includes(user_id)) {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
}
- channel.recipients!.push(Recipient.create({ channel_id: channel_id, user_id: user_id }));
+ channel.recipients!.push(
+ Recipient.create({ channel_id: channel_id, user_id: user_id }),
+ );
await channel.save();
await emitEvent({
event: "CHANNEL_CREATE",
data: await DmChannelDTO.from(channel, [user_id]),
- user_id: user_id
+ user_id: user_id,
});
await emitEvent({
event: "CHANNEL_RECIPIENT_ADD",
data: {
channel_id: channel_id,
- user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection })
+ user: await User.findOneOrFail({
+ where: { id: user_id },
+ select: PublicUserProjection,
+ }),
},
- channel_id: channel_id
+ channel_id: channel_id,
} as ChannelRecipientAddEvent);
return res.sendStatus(204);
}
@@ -51,8 +65,16 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => {
router.delete("/:user_id", route({}), async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
- if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id)))
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients"],
+ });
+ if (
+ !(
+ channel.type === ChannelType.GROUP_DM &&
+ (channel.owner_id === req.user_id || user_id === req.user_id)
+ )
+ )
throw DiscordApiErrors.MISSING_PERMISSIONS;
if (!channel.recipients!.map((r) => r.user_id).includes(user_id)) {
diff --git a/src/api/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts
index 99460f6e..03f76205 100644
--- a/src/api/routes/channels/#channel_id/typing.ts
+++ b/src/api/routes/channels/#channel_id/typing.ts
@@ -4,26 +4,42 @@ import { Router, Request, Response } from "express";
const router: Router = Router();
-router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const user_id = req.user_id;
- const timestamp = Date.now();
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- const member = await Member.findOne({ where: { id: user_id, guild_id: channel.guild_id }, relations: ["roles", "user"] });
+router.post(
+ "/",
+ route({ permission: "SEND_MESSAGES" }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const user_id = req.user_id;
+ const timestamp = Date.now();
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const member = await Member.findOne({
+ where: { id: user_id, guild_id: channel.guild_id },
+ relations: ["roles", "user"],
+ });
- await emitEvent({
- event: "TYPING_START",
- channel_id: channel_id,
- data: {
- ...(member ? { member: { ...member, roles: member?.roles?.map((x) => x.id) } } : null),
- channel_id,
- timestamp,
- user_id,
- guild_id: channel.guild_id
- }
- } as TypingStartEvent);
+ await emitEvent({
+ event: "TYPING_START",
+ channel_id: channel_id,
+ data: {
+ ...(member
+ ? {
+ member: {
+ ...member,
+ roles: member?.roles?.map((x) => x.id),
+ },
+ }
+ : null),
+ channel_id,
+ timestamp,
+ user_id,
+ guild_id: channel.guild_id,
+ },
+ } as TypingStartEvent);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index 99c104ca..da8fe73c 100644
--- a/src/api/routes/channels/#channel_id/webhooks.ts
+++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -13,22 +13,29 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
// TODO: use Image Data Type for avatar instead of String
-router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => {
- const channel_id = req.params.channel_id;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
-
- isTextChannel(channel.type);
- if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400);
-
- const webhook_count = await Webhook.count({ where: { channel_id } });
- const { maxWebhooks } = Config.get().limits.channel;
- if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
-
- var { avatar, name } = req.body as { name: string; avatar?: string };
- name = trimSpecial(name);
- if (name === "clyde") throw new HTTPError("Invalid name", 400);
-
- // TODO: save webhook in database and send response
-});
+router.post(
+ "/",
+ route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }),
+ async (req: Request, res: Response) => {
+ const channel_id = req.params.channel_id;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+
+ isTextChannel(channel.type);
+ if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400);
+
+ const webhook_count = await Webhook.count({ where: { channel_id } });
+ const { maxWebhooks } = Config.get().limits.channel;
+ if (webhook_count > maxWebhooks)
+ throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
+
+ var { avatar, name } = req.body as { name: string; avatar?: string };
+ name = trimSpecial(name);
+ if (name === "clyde") throw new HTTPError("Invalid name", 400);
+
+ // TODO: save webhook in database and send response
+ },
+);
export default router;
diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts
index 383e2b24..0e7cfbab 100644
--- a/src/api/routes/discoverable-guilds.ts
+++ b/src/api/routes/discoverable-guilds.ts
@@ -17,19 +17,33 @@ router.get("/", route({}), async (req: Request, res: Response) => {
if (categories == undefined) {
guilds = showAllGuilds
? await Guild.find({ take: Math.abs(Number(limit || configLimit)) })
- : await Guild.find({ where: { features: Like(`%DISCOVERABLE%`) }, take: Math.abs(Number(limit || configLimit)) });
+ : await Guild.find({
+ where: { features: Like(`%DISCOVERABLE%`) },
+ take: Math.abs(Number(limit || configLimit)),
+ });
} else {
guilds = showAllGuilds
- ? await Guild.find({ where: { primary_category_id: categories.toString() }, take: Math.abs(Number(limit || configLimit)) })
+ ? await Guild.find({
+ where: { primary_category_id: categories.toString() },
+ take: Math.abs(Number(limit || configLimit)),
+ })
: await Guild.find({
- where: { primary_category_id: categories.toString(), features: Like("%DISCOVERABLE%") },
- take: Math.abs(Number(limit || configLimit))
- });
+ where: {
+ primary_category_id: categories.toString(),
+ features: Like("%DISCOVERABLE%"),
+ },
+ take: Math.abs(Number(limit || configLimit)),
+ });
}
const total = guilds ? guilds.length : undefined;
- res.send({ total: total, guilds: guilds, offset: Number(offset || Config.get().guild.discovery.offset), limit: Number(limit || configLimit) });
+ res.send({
+ total: total,
+ guilds: guilds,
+ offset: Number(offset || Config.get().guild.discovery.offset),
+ limit: Number(limit || configLimit),
+ });
});
export default router;
diff --git a/src/api/routes/discovery.ts b/src/api/routes/discovery.ts
index 6ab2cc13..90450035 100644
--- a/src/api/routes/discovery.ts
+++ b/src/api/routes/discovery.ts
@@ -10,7 +10,9 @@ router.get("/categories", route({}), async (req: Request, res: Response) => {
const { locale, primary_only } = req.query;
- const out = primary_only ? await Categories.find() : await Categories.find({ where: { is_primary: true } });
+ const out = primary_only
+ ? await Categories.find()
+ : await Categories.find({ where: { is_primary: true } });
res.send(out);
});
diff --git a/src/api/routes/downloads.ts b/src/api/routes/downloads.ts
index df3df911..bc0750f7 100644
--- a/src/api/routes/downloads.ts
+++ b/src/api/routes/downloads.ts
@@ -10,9 +10,12 @@ router.get("/:branch", route({}), async (req: Request, res: Response) => {
const { platform } = req.query;
//TODO
- if (!platform || !["linux", "osx", "win"].includes(platform.toString())) return res.status(404);
+ if (!platform || !["linux", "osx", "win"].includes(platform.toString()))
+ return res.status(404);
- const release = await Release.findOneOrFail({ where: { name: client.releases.upstreamVersion } });
+ const release = await Release.findOneOrFail({
+ where: { name: client.releases.upstreamVersion },
+ });
res.redirect(release[`win_url`]);
});
diff --git a/src/api/routes/experiments.ts b/src/api/routes/experiments.ts
index 7be86fb8..b2b7d724 100644
--- a/src/api/routes/experiments.ts
+++ b/src/api/routes/experiments.ts
@@ -5,7 +5,7 @@ const router = Router();
router.get("/", route({}), (req: Request, res: Response) => {
// TODO:
- res.send({ fingerprint: "", assignments: [], guild_experiments:[] });
+ res.send({ fingerprint: "", assignments: [], guild_experiments: [] });
});
export default router;
diff --git a/src/api/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts
index f1dbb9df..2e26d019 100644
--- a/src/api/routes/gateway/bot.ts
+++ b/src/api/routes/gateway/bot.ts
@@ -18,9 +18,9 @@ export interface GatewayBotResponse {
const options: RouteOptions = {
test: {
response: {
- body: "GatewayBotResponse"
- }
- }
+ body: "GatewayBotResponse",
+ },
+ },
};
router.get("/", route(options), (req: Request, res: Response) => {
@@ -32,8 +32,8 @@ router.get("/", route(options), (req: Request, res: Response) => {
total: 1000,
remaining: 999,
reset_after: 14400000,
- max_concurrency: 1
- }
+ max_concurrency: 1,
+ },
});
});
diff --git a/src/api/routes/gateway/index.ts b/src/api/routes/gateway/index.ts
index 9bad7478..a6ed9dc4 100644
--- a/src/api/routes/gateway/index.ts
+++ b/src/api/routes/gateway/index.ts
@@ -11,14 +11,16 @@ export interface GatewayResponse {
const options: RouteOptions = {
test: {
response: {
- body: "GatewayResponse"
- }
- }
+ body: "GatewayResponse",
+ },
+ },
};
router.get("/", route(options), (req: Request, res: Response) => {
const { endpointPublic } = Config.get().gateway;
- res.json({ url: endpointPublic || process.env.GATEWAY || "ws://localhost:3002" });
+ res.json({
+ url: endpointPublic || process.env.GATEWAY || "ws://localhost:3002",
+ });
});
export default router;
diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts
index c7468641..54352215 100644
--- a/src/api/routes/gifs/search.ts
+++ b/src/api/routes/gifs/search.ts
@@ -1,6 +1,6 @@
import { Router, Response, Request } from "express";
import fetch from "node-fetch";
-import ProxyAgent from 'proxy-agent';
+import ProxyAgent from "proxy-agent";
import { route } from "@fosscord/api";
import { getGifApiKey, parseGifResult } from "./trending";
@@ -11,16 +11,19 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { q, media_format, locale } = req.query;
const apiKey = getGifApiKey();
-
+
const agent = new ProxyAgent();
- const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" }
- });
+ const response = await fetch(
+ `https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
- const { results } = await response.json() as any; // TODO: types
+ const { results } = (await response.json()) as any; // TODO: types
res.json(results.map(parseGifResult)).status(200);
});
diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts
index 52a8969d..e4b28e24 100644
--- a/src/api/routes/gifs/trending-gifs.ts
+++ b/src/api/routes/gifs/trending-gifs.ts
@@ -1,6 +1,6 @@
import { Router, Response, Request } from "express";
import fetch from "node-fetch";
-import ProxyAgent from 'proxy-agent';
+import ProxyAgent from "proxy-agent";
import { route } from "@fosscord/api";
import { getGifApiKey, parseGifResult } from "./trending";
@@ -11,16 +11,19 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { media_format, locale } = req.query;
const apiKey = getGifApiKey();
-
+
const agent = new ProxyAgent();
- const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" }
- });
+ const response = await fetch(
+ `https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
- const { results } = await response.json() as any; // TODO: types
+ const { results } = (await response.json()) as any; // TODO: types
res.json(results.map(parseGifResult)).status(200);
});
diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index aa976c3f..58044ea5 100644
--- a/src/api/routes/gifs/trending.ts
+++ b/src/api/routes/gifs/trending.ts
@@ -1,6 +1,6 @@
import { Router, Response, Request } from "express";
import fetch from "node-fetch";
-import ProxyAgent from 'proxy-agent';
+import ProxyAgent from "proxy-agent";
import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
import { HTTPError } from "lambert-server";
@@ -16,14 +16,15 @@ export function parseGifResult(result: any) {
gif_src: result.media[0].gif.url,
width: result.media[0].mp4.dims[0],
height: result.media[0].mp4.dims[1],
- preview: result.media[0].mp4.preview
+ preview: result.media[0].mp4.preview,
};
}
export function getGifApiKey() {
const { enabled, provider, apiKey } = Config.get().gif;
if (!enabled) throw new HTTPError(`Gifs are disabled`);
- if (provider !== "tenor" || !apiKey) throw new HTTPError(`${provider} gif provider not supported`);
+ if (provider !== "tenor" || !apiKey)
+ throw new HTTPError(`${provider} gif provider not supported`);
return apiKey;
}
@@ -34,28 +35,37 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { media_format, locale } = req.query;
const apiKey = getGifApiKey();
-
+
const agent = new ProxyAgent();
const [responseSource, trendGifSource] = await Promise.all([
- fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" }
- }),
- fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" }
- })
+ fetch(
+ `https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ ),
+ fetch(
+ `https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ ),
]);
- const { tags } = await responseSource.json() as any; // TODO: types
- const { results } = await trendGifSource.json() as any; //TODO: types;
+ const { tags } = (await responseSource.json()) as any; // TODO: types
+ const { results } = (await trendGifSource.json()) as any; //TODO: types;
res.json({
- categories: tags.map((x: any) => ({ name: x.searchterm, src: x.image })),
- gifs: [parseGifResult(results[0])]
+ categories: tags.map((x: any) => ({
+ name: x.searchterm,
+ src: x.image,
+ })),
+ gifs: [parseGifResult(results[0])],
}).status(200);
});
diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts
index b851d710..bda37973 100644
--- a/src/api/routes/guild-recommendations.ts
+++ b/src/api/routes/guild-recommendations.ts
@@ -13,12 +13,21 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// TODO: implement this with default typeorm query
// const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) });
- const genLoadId = (size: Number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
+ const genLoadId = (size: Number) =>
+ [...Array(size)]
+ .map(() => Math.floor(Math.random() * 16).toString(16))
+ .join("");
const guilds = showAllGuilds
? await Guild.find({ take: Math.abs(Number(limit || 24)) })
- : await Guild.find({ where: { features: Like("%DISCOVERABLE%") }, take: Math.abs(Number(limit || 24)) });
- res.send({ recommended_guilds: guilds, load_id: `server_recs/${genLoadId(32)}` }).status(200);
+ : await Guild.find({
+ where: { features: Like("%DISCOVERABLE%") },
+ take: Math.abs(Number(limit || 24)),
+ });
+ res.send({
+ recommended_guilds: guilds,
+ load_id: `server_recs/${genLoadId(32)}`,
+ }).status(200);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts
index b54835fc..76a11f6b 100644
--- a/src/api/routes/guilds/#guild_id/audit-logs.ts
+++ b/src/api/routes/guilds/#guild_id/audit-logs.ts
@@ -11,7 +11,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
webhooks: [],
guild_scheduled_events: [],
threads: [],
- application_commands: []
+ application_commands: [],
});
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index ed00f9c0..930985d7 100644
--- a/src/api/routes/guilds/#guild_id/bans.ts
+++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -1,5 +1,15 @@
import { Request, Response, Router } from "express";
-import { DiscordApiErrors, emitEvent, GuildBanAddEvent, GuildBanRemoveEvent, Ban, User, Member, BanRegistrySchema, BanModeratorSchema } from "@fosscord/util";
+import {
+ DiscordApiErrors,
+ emitEvent,
+ GuildBanAddEvent,
+ GuildBanRemoveEvent,
+ Ban,
+ User,
+ Member,
+ BanRegistrySchema,
+ BanModeratorSchema,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { getIpAdress, route } from "@fosscord/api";
@@ -7,150 +17,184 @@ const router: Router = Router();
/* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */
-router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+router.get(
+ "/",
+ route({ permission: "BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- let bans = await Ban.find({ where: { guild_id: guild_id } });
- let promisesToAwait: object[] = [];
- const bansObj: object[] = [];
+ let bans = await Ban.find({ where: { guild_id: guild_id } });
+ let promisesToAwait: object[] = [];
+ const bansObj: object[] = [];
- bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
+ bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
- bans.forEach((ban) => {
- promisesToAwait.push(User.getPublicUser(ban.user_id));
- });
-
- const bannedUsers: object[] = await Promise.all(promisesToAwait);
-
- bans.forEach((ban, index) => {
- const user = bannedUsers[index] as User;
- bansObj.push({
- reason: ban.reason,
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- avatar: user.avatar,
- public_flags: user.public_flags
- }
+ bans.forEach((ban) => {
+ promisesToAwait.push(User.getPublicUser(ban.user_id));
});
- });
-
- return res.json(bansObj);
-});
-
-router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const user_id = req.params.ban;
-
- let ban = await Ban.findOneOrFail({ where: { guild_id: guild_id, user_id: user_id } }) as BanRegistrySchema;
-
- if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
- // pretend self-bans don't exist to prevent victim chasing
-
- /* Filter secret from registry. */
-
- ban = ban as BanModeratorSchema;
-
- delete ban.ip;
-
- return res.json(ban);
-});
-
-router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const banned_user_id = req.params.user_id;
- if ((req.user_id === banned_user_id) && (banned_user_id === req.permission!.cache.guild?.owner_id))
- throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
-
- if (req.permission!.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400);
-
- const banned_user = await User.getPublicUser(banned_user_id);
+ const bannedUsers: object[] = await Promise.all(promisesToAwait);
+
+ bans.forEach((ban, index) => {
+ const user = bannedUsers[index] as User;
+ bansObj.push({
+ reason: ban.reason,
+ user: {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ avatar: user.avatar,
+ public_flags: user.public_flags,
+ },
+ });
+ });
- const ban = Ban.create({
- user_id: banned_user_id,
- guild_id: guild_id,
- ip: getIpAdress(req),
- executor_id: req.user_id,
- reason: req.body.reason // || otherwise empty
- });
+ return res.json(bansObj);
+ },
+);
+
+router.get(
+ "/:user",
+ route({ permission: "BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const user_id = req.params.ban;
+
+ let ban = (await Ban.findOneOrFail({
+ where: { guild_id: guild_id, user_id: user_id },
+ })) as BanRegistrySchema;
+
+ if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
+ // pretend self-bans don't exist to prevent victim chasing
+
+ /* Filter secret from registry. */
+
+ ban = ban as BanModeratorSchema;
+
+ delete ban.ip;
+
+ return res.json(ban);
+ },
+);
+
+router.put(
+ "/:user_id",
+ route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const banned_user_id = req.params.user_id;
+
+ if (
+ req.user_id === banned_user_id &&
+ banned_user_id === req.permission!.cache.guild?.owner_id
+ )
+ throw new HTTPError(
+ "You are the guild owner, hence can't ban yourself",
+ 403,
+ );
+
+ if (req.permission!.cache.guild?.owner_id === banned_user_id)
+ throw new HTTPError("You can't ban the owner", 400);
+
+ const banned_user = await User.getPublicUser(banned_user_id);
+
+ const ban = Ban.create({
+ user_id: banned_user_id,
+ guild_id: guild_id,
+ ip: getIpAdress(req),
+ executor_id: req.user_id,
+ reason: req.body.reason, // || otherwise empty
+ });
- await Promise.all([
- Member.removeFromGuild(banned_user_id, guild_id),
- ban.save(),
- emitEvent({
- event: "GUILD_BAN_ADD",
- data: {
- guild_id: guild_id,
- user: banned_user
- },
- guild_id: guild_id
- } as GuildBanAddEvent)
- ]);
-
- return res.json(ban);
-});
-
-router.put("/@me", route({ body: "BanCreateSchema" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
-
- const banned_user = await User.getPublicUser(req.params.user_id);
-
- if (req.permission!.cache.guild?.owner_id === req.params.user_id)
- throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
-
- const ban = Ban.create({
- user_id: req.params.user_id,
- guild_id: guild_id,
- ip: getIpAdress(req),
- executor_id: req.params.user_id,
- reason: req.body.reason // || otherwise empty
- });
-
- await Promise.all([
- Member.removeFromGuild(req.user_id, guild_id),
- ban.save(),
- emitEvent({
- event: "GUILD_BAN_ADD",
- data: {
+ await Promise.all([
+ Member.removeFromGuild(banned_user_id, guild_id),
+ ban.save(),
+ emitEvent({
+ event: "GUILD_BAN_ADD",
+ data: {
+ guild_id: guild_id,
+ user: banned_user,
+ },
guild_id: guild_id,
- user: banned_user
- },
- guild_id: guild_id
- } as GuildBanAddEvent)
- ]);
+ } as GuildBanAddEvent),
+ ]);
+
+ return res.json(ban);
+ },
+);
+
+router.put(
+ "/@me",
+ route({ body: "BanCreateSchema" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+
+ const banned_user = await User.getPublicUser(req.params.user_id);
+
+ if (req.permission!.cache.guild?.owner_id === req.params.user_id)
+ throw new HTTPError(
+ "You are the guild owner, hence can't ban yourself",
+ 403,
+ );
+
+ const ban = Ban.create({
+ user_id: req.params.user_id,
+ guild_id: guild_id,
+ ip: getIpAdress(req),
+ executor_id: req.params.user_id,
+ reason: req.body.reason, // || otherwise empty
+ });
- return res.json(ban);
-});
+ await Promise.all([
+ Member.removeFromGuild(req.user_id, guild_id),
+ ban.save(),
+ emitEvent({
+ event: "GUILD_BAN_ADD",
+ data: {
+ guild_id: guild_id,
+ user: banned_user,
+ },
+ guild_id: guild_id,
+ } as GuildBanAddEvent),
+ ]);
-router.delete("/:user_id", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const { guild_id, user_id } = req.params;
+ return res.json(ban);
+ },
+);
- let ban = await Ban.findOneOrFail({ where: { guild_id: guild_id, user_id: user_id } });
+router.delete(
+ "/:user_id",
+ route({ permission: "BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, user_id } = req.params;
- if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
- // make self-bans irreversible and hide them from view to avoid victim chasing
+ let ban = await Ban.findOneOrFail({
+ where: { guild_id: guild_id, user_id: user_id },
+ });
- const banned_user = await User.getPublicUser(user_id);
+ if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
+ // make self-bans irreversible and hide them from view to avoid victim chasing
- await Promise.all([
- Ban.delete({
- user_id: user_id,
- guild_id
- }),
+ const banned_user = await User.getPublicUser(user_id);
- emitEvent({
- event: "GUILD_BAN_REMOVE",
- data: {
+ await Promise.all([
+ Ban.delete({
+ user_id: user_id,
guild_id,
- user: banned_user
- },
- guild_id
- } as GuildBanRemoveEvent)
- ]);
-
- return res.status(204).send();
-});
+ }),
+
+ emitEvent({
+ event: "GUILD_BAN_REMOVE",
+ data: {
+ guild_id,
+ user: banned_user,
+ },
+ guild_id,
+ } as GuildBanRemoveEvent),
+ ]);
+
+ return res.status(204).send();
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index 7a5b50d1..af17465d 100644
--- a/src/api/routes/guilds/#guild_id/channels.ts
+++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -1,5 +1,10 @@
import { Router, Response, Request } from "express";
-import { Channel, ChannelUpdateEvent, emitEvent, ChannelModifySchema } from "@fosscord/util";
+import {
+ Channel,
+ ChannelUpdateEvent,
+ emitEvent,
+ ChannelModifySchema,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
const router = Router();
@@ -11,49 +16,77 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.json(channels);
});
-router.post("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
- const { guild_id } = req.params;
- const body = req.body as ChannelModifySchema;
+router.post(
+ "/",
+ route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
+ const { guild_id } = req.params;
+ const body = req.body as ChannelModifySchema;
- const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
+ const channel = await Channel.createChannel(
+ { ...body, guild_id },
+ req.user_id,
+ );
- res.status(201).json(channel);
-});
+ res.status(201).json(channel);
+ },
+);
-export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string; }[];
+export type ChannelReorderSchema = {
+ id: string;
+ position?: number;
+ lock_permissions?: boolean;
+ parent_id?: string;
+}[];
-router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- // changes guild channel position
- const { guild_id } = req.params;
- const body = req.body as ChannelReorderSchema;
+router.patch(
+ "/",
+ route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ // changes guild channel position
+ const { guild_id } = req.params;
+ const body = req.body as ChannelReorderSchema;
- await Promise.all([
- body.map(async (x) => {
- if (x.position == null && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400);
+ await Promise.all([
+ body.map(async (x) => {
+ if (x.position == null && !x.parent_id)
+ throw new HTTPError(
+ `You need to at least specify position or parent_id`,
+ 400,
+ );
- const opts: any = {};
- if (x.position != null) opts.position = x.position;
+ const opts: any = {};
+ if (x.position != null) opts.position = x.position;
- if (x.parent_id) {
- opts.parent_id = x.parent_id;
- const parent_channel = await Channel.findOneOrFail({
- where: { id: x.parent_id, guild_id },
- select: ["permission_overwrites"]
- });
- if (x.lock_permissions) {
- opts.permission_overwrites = parent_channel.permission_overwrites;
+ if (x.parent_id) {
+ opts.parent_id = x.parent_id;
+ const parent_channel = await Channel.findOneOrFail({
+ where: { id: x.parent_id, guild_id },
+ select: ["permission_overwrites"],
+ });
+ if (x.lock_permissions) {
+ opts.permission_overwrites =
+ parent_channel.permission_overwrites;
+ }
}
- }
- await Channel.update({ guild_id, id: x.id }, opts);
- const channel = await Channel.findOneOrFail({ where: { guild_id, id: x.id } });
+ await Channel.update({ guild_id, id: x.id }, opts);
+ const channel = await Channel.findOneOrFail({
+ where: { guild_id, id: x.id },
+ });
- await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent);
- })
- ]);
+ await emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id: x.id,
+ guild_id,
+ } as ChannelUpdateEvent);
+ }),
+ ]);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index bd158c56..b951e4f4 100644
--- a/src/api/routes/guilds/#guild_id/delete.ts
+++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -1,4 +1,14 @@
-import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util";
+import {
+ Channel,
+ emitEvent,
+ GuildDeleteEvent,
+ Guild,
+ Member,
+ Message,
+ Role,
+ Invite,
+ Emoji,
+} from "@fosscord/util";
import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
@@ -10,18 +20,22 @@ const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
var { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] });
- if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401);
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: ["owner_id"],
+ });
+ if (guild.owner_id !== req.user_id)
+ throw new HTTPError("You are not the owner of this guild", 401);
await Promise.all([
Guild.delete({ id: guild_id }), // this will also delete all guild related data
emitEvent({
event: "GUILD_DELETE",
data: {
- id: guild_id
+ id: guild_id,
},
- guild_id: guild_id
- } as GuildDeleteEvent)
+ guild_id: guild_id,
+ } as GuildDeleteEvent),
]);
return res.sendStatus(204);
diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
index ad20633f..7e63c06b 100644
--- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts
+++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
@@ -6,33 +6,33 @@ import { route } from "@fosscord/api";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- // TODO:
- // Load from database
- // Admin control, but for now it allows anyone to be discoverable
+ const { guild_id } = req.params;
+ // TODO:
+ // Load from database
+ // Admin control, but for now it allows anyone to be discoverable
res.send({
guild_id: guild_id,
safe_environment: true,
- healthy: true,
- health_score_pending: false,
- size: true,
- nsfw_properties: {},
- protected: true,
- sufficient: true,
- sufficient_without_grace_period: true,
- valid_rules_channel: true,
- retention_healthy: true,
- engagement_healthy: true,
- age: true,
- minimum_age: 0,
- health_score: {
- avg_nonnew_participators: 0,
- avg_nonnew_communicators: 0,
- num_intentful_joiners: 0,
- perc_ret_w1_intentful: 0
- },
- minimum_size: 0
+ healthy: true,
+ health_score_pending: false,
+ size: true,
+ nsfw_properties: {},
+ protected: true,
+ sufficient: true,
+ sufficient_without_grace_period: true,
+ valid_rules_channel: true,
+ retention_healthy: true,
+ engagement_healthy: true,
+ age: true,
+ minimum_age: 0,
+ health_score: {
+ avg_nonnew_participators: 0,
+ avg_nonnew_communicators: 0,
+ num_intentful_joiners: 0,
+ perc_ret_w1_intentful: 0,
+ },
+ minimum_size: 0,
});
});
diff --git a/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index cf9d742a..6e8570eb 100644
--- a/src/api/routes/guilds/#guild_id/emojis.ts
+++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -1,5 +1,17 @@
import { Router, Request, Response } from "express";
-import { Config, DiscordApiErrors, emitEvent, Emoji, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User, EmojiCreateSchema, EmojiModifySchema } from "@fosscord/util";
+import {
+ Config,
+ DiscordApiErrors,
+ emitEvent,
+ Emoji,
+ GuildEmojisUpdateEvent,
+ handleFile,
+ Member,
+ Snowflake,
+ User,
+ EmojiCreateSchema,
+ EmojiModifySchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router = Router();
@@ -9,7 +21,10 @@ router.get("/", route({}), async (req: Request, res: Response) => {
await Member.IsInGuildOrFail(req.user_id, guild_id);
- const emojis = await Emoji.find({ where: { guild_id: guild_id }, relations: ["user"] });
+ const emojis = await Emoji.find({
+ where: { guild_id: guild_id },
+ relations: ["user"],
+ });
return res.json(emojis);
});
@@ -19,89 +34,115 @@ router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
await Member.IsInGuildOrFail(req.user_id, guild_id);
- const emoji = await Emoji.findOneOrFail({ where: { guild_id: guild_id, id: emoji_id }, relations: ["user"] });
+ const emoji = await Emoji.findOneOrFail({
+ where: { guild_id: guild_id, id: emoji_id },
+ relations: ["user"],
+ });
return res.json(emoji);
});
-router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as EmojiCreateSchema;
-
- const id = Snowflake.generate();
- const emoji_count = await Emoji.count({ where: { guild_id: guild_id } });
- const { maxEmojis } = Config.get().limits.guild;
-
- if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
- if (body.require_colons == null) body.require_colons = true;
-
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
-
- const emoji = await Emoji.create({
- id: id,
- guild_id: guild_id,
- ...body,
- require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
- user: user,
- managed: false,
- animated: false, // TODO: Add support animated emojis
- available: true,
- roles: []
- }).save();
-
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
+router.post(
+ "/",
+ route({
+ body: "EmojiCreateSchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as EmojiCreateSchema;
+
+ const id = Snowflake.generate();
+ const emoji_count = await Emoji.count({
+ where: { guild_id: guild_id },
+ });
+ const { maxEmojis } = Config.get().limits.guild;
+
+ if (emoji_count >= maxEmojis)
+ throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(
+ maxEmojis,
+ );
+ if (body.require_colons == null) body.require_colons = true;
+
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
+
+ const emoji = await Emoji.create({
+ id: id,
guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } })
- }
- } as GuildEmojisUpdateEvent);
+ ...body,
+ require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
+ user: user,
+ managed: false,
+ animated: false, // TODO: Add support animated emojis
+ available: true,
+ roles: [],
+ }).save();
- return res.status(201).json(emoji);
-});
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
+
+ return res.status(201).json(emoji);
+ },
+);
router.patch(
"/:emoji_id",
- route({ body: "EmojiModifySchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+ route({
+ body: "EmojiModifySchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ }),
async (req: Request, res: Response) => {
const { emoji_id, guild_id } = req.params;
const body = req.body as EmojiModifySchema;
- const emoji = await Emoji.create({ ...body, id: emoji_id, guild_id: guild_id }).save();
+ const emoji = await Emoji.create({
+ ...body,
+ id: emoji_id,
+ guild_id: guild_id,
+ }).save();
await emitEvent({
event: "GUILD_EMOJIS_UPDATE",
guild_id: guild_id,
data: {
guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } })
- }
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
} as GuildEmojisUpdateEvent);
return res.json(emoji);
- }
+ },
);
-router.delete("/:emoji_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
- const { emoji_id, guild_id } = req.params;
+router.delete(
+ "/:emoji_id",
+ route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+ async (req: Request, res: Response) => {
+ const { emoji_id, guild_id } = req.params;
- await Emoji.delete({
- id: emoji_id,
- guild_id: guild_id
- });
+ await Emoji.delete({
+ id: emoji_id,
+ guild_id: guild_id,
+ });
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } })
- }
- } as GuildEmojisUpdateEvent);
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index afeb0938..715a3835 100644
--- a/src/api/routes/guilds/#guild_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -1,5 +1,15 @@
import { Request, Response, Router } from "express";
-import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, handleFile, Member, GuildCreateSchema } from "@fosscord/util";
+import {
+ DiscordApiErrors,
+ emitEvent,
+ getPermission,
+ getRights,
+ Guild,
+ GuildUpdateEvent,
+ handleFile,
+ Member,
+ GuildCreateSchema,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
@@ -26,9 +36,13 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const [guild, member] = await Promise.all([
Guild.findOneOrFail({ where: { id: guild_id } }),
- Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })
+ Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
]);
- if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
+ if (!member)
+ throw new HTTPError(
+ "You are not a member of the guild you are trying to access",
+ 401,
+ );
// @ts-ignore
guild.joined_at = member?.joined_at;
@@ -36,39 +50,57 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.send(guild);
});
-router.patch("/", route({ body: "GuildUpdateSchema" }), async (req: Request, res: Response) => {
- const body = req.body as GuildUpdateSchema;
- const { guild_id } = req.params;
-
-
- const rights = await getRights(req.user_id);
- const permission = await getPermission(req.user_id, guild_id);
-
- if (!rights.has("MANAGE_GUILDS") || !permission.has("MANAGE_GUILD"))
- throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILD");
-
- // TODO: guild update check image
-
- if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
- if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
- if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
-
- var guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- relations: ["emojis", "roles", "stickers"]
- });
- // TODO: check if body ids are valid
- guild.assign(body);
-
- const data = guild.toJSON();
- // TODO: guild hashes
- // TODO: fix vanity_url_code, template_id
- delete data.vanity_url_code;
- delete data.template_id;
-
- await Promise.all([guild.save(), emitEvent({ event: "GUILD_UPDATE", data, guild_id } as GuildUpdateEvent)]);
-
- return res.json(data);
-});
+router.patch(
+ "/",
+ route({ body: "GuildUpdateSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as GuildUpdateSchema;
+ const { guild_id } = req.params;
+
+ const rights = await getRights(req.user_id);
+ const permission = await getPermission(req.user_id, guild_id);
+
+ if (!rights.has("MANAGE_GUILDS") || !permission.has("MANAGE_GUILD"))
+ throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(
+ "MANAGE_GUILD",
+ );
+
+ // TODO: guild update check image
+
+ if (body.icon)
+ body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
+ if (body.banner)
+ body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
+ if (body.splash)
+ body.splash = await handleFile(
+ `/splashes/${guild_id}`,
+ body.splash,
+ );
+
+ var guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ relations: ["emojis", "roles", "stickers"],
+ });
+ // TODO: check if body ids are valid
+ guild.assign(body);
+
+ const data = guild.toJSON();
+ // TODO: guild hashes
+ // TODO: fix vanity_url_code, template_id
+ delete data.vanity_url_code;
+ delete data.template_id;
+
+ await Promise.all([
+ guild.save(),
+ emitEvent({
+ event: "GUILD_UPDATE",
+ data,
+ guild_id,
+ } as GuildUpdateEvent),
+ ]);
+
+ return res.json(data);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index b7534e31..4d033e9c 100644
--- a/src/api/routes/guilds/#guild_id/invites.ts
+++ b/src/api/routes/guilds/#guild_id/invites.ts
@@ -4,12 +4,19 @@ import { Request, Response, Router } from "express";
const router = Router();
-router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+router.get(
+ "/",
+ route({ permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation });
+ const invites = await Invite.find({
+ where: { guild_id },
+ relations: PublicInviteRelation,
+ });
- return res.json(invites);
-});
+ return res.json(invites);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts
index 265a1b35..c2f946b2 100644
--- a/src/api/routes/guilds/#guild_id/member-verification.ts
+++ b/src/api/routes/guilds/#guild_id/member-verification.ts
@@ -2,12 +2,12 @@ import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
const router = Router();
-router.get("/",route({}), async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
// TODO: member verification
res.status(404).json({
message: "Unknown Guild Member Verification Form",
- code: 10068
+ code: 10068,
});
});
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index 407619d3..2d867920 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -1,5 +1,16 @@
import { Request, Response, Router } from "express";
-import { Member, getPermission, getRights, Role, GuildMemberUpdateEvent, emitEvent, Sticker, Emoji, Guild, MemberChangeSchema } from "@fosscord/util";
+import {
+ Member,
+ getPermission,
+ getRights,
+ Role,
+ GuildMemberUpdateEvent,
+ emitEvent,
+ Sticker,
+ Emoji,
+ Guild,
+ MemberChangeSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router = Router();
@@ -8,48 +19,63 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id, member_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
- const member = await Member.findOneOrFail({ where: { id: member_id, guild_id } });
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ });
return res.json(member);
});
-router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => {
- let { guild_id, member_id } = req.params;
- if (member_id === "@me") member_id = req.user_id;
- const body = req.body as MemberChangeSchema;
-
- const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] });
- const permission = await getPermission(req.user_id, guild_id);
- const everyone = await Role.findOneOrFail({ where: { guild_id: guild_id, name: "@everyone", position: 0 } });
-
- if (body.roles) {
- permission.hasThrow("MANAGE_ROLES");
-
- if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id);
- member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist
- }
-
- if ('nick' in body) {
- permission.hasThrow(req.user_id == member.user.id ? "CHANGE_NICKNAME" : "MANAGE_NICKNAMES");
- member.nick = body.nick?.trim() || undefined;
- }
-
- await member.save();
-
- member.roles = member.roles.filter((x) => x.id !== everyone.id);
-
- // do not use promise.all as we have to first write to db before emitting the event to catch errors
- await emitEvent({
- event: "GUILD_MEMBER_UPDATE",
- guild_id,
- data: { ...member, roles: member.roles.map((x) => x.id) }
- } as GuildMemberUpdateEvent);
-
- res.json(member);
-});
+router.patch(
+ "/",
+ route({ body: "MemberChangeSchema" }),
+ async (req: Request, res: Response) => {
+ let { guild_id, member_id } = req.params;
+ if (member_id === "@me") member_id = req.user_id;
+ const body = req.body as MemberChangeSchema;
+
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ relations: ["roles", "user"],
+ });
+ const permission = await getPermission(req.user_id, guild_id);
+ const everyone = await Role.findOneOrFail({
+ where: { guild_id: guild_id, name: "@everyone", position: 0 },
+ });
+
+ if (body.roles) {
+ permission.hasThrow("MANAGE_ROLES");
+
+ if (body.roles.indexOf(everyone.id) === -1)
+ body.roles.push(everyone.id);
+ member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist
+ }
+
+ if ("nick" in body) {
+ permission.hasThrow(
+ req.user_id == member.user.id
+ ? "CHANGE_NICKNAME"
+ : "MANAGE_NICKNAMES",
+ );
+ member.nick = body.nick?.trim() || undefined;
+ }
+
+ await member.save();
+
+ member.roles = member.roles.filter((x) => x.id !== everyone.id);
+
+ // do not use promise.all as we have to first write to db before emitting the event to catch errors
+ await emitEvent({
+ event: "GUILD_MEMBER_UPDATE",
+ guild_id,
+ data: { ...member, roles: member.roles.map((x) => x.id) },
+ } as GuildMemberUpdateEvent);
+
+ res.json(member);
+ },
+);
router.put("/", route({}), async (req: Request, res: Response) => {
-
// TODO: Lurker mode
const rights = await getRights(req.user_id);
@@ -59,23 +85,23 @@ router.put("/", route({}), async (req: Request, res: Response) => {
member_id = req.user_id;
rights.hasThrow("JOIN_GUILDS");
} else {
- // TODO: join others by controller
+ // TODO: join others by controller
}
var guild = await Guild.findOneOrFail({
- where: { id: guild_id }
+ where: { id: guild_id },
});
var emoji = await Emoji.find({
- where: { guild_id: guild_id }
+ where: { guild_id: guild_id },
});
var roles = await Role.find({
- where: { guild_id: guild_id }
+ where: { guild_id: guild_id },
});
var stickers = await Sticker.find({
- where: { guild_id: guild_id }
+ where: { guild_id: guild_id },
});
await Member.addToGuild(member_id, guild_id);
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index edd47605..20443821 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -4,19 +4,23 @@ import { Request, Response, Router } from "express";
const router = Router();
-router.patch("/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => {
- var { guild_id, member_id } = req.params;
- var permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
- if (member_id === "@me") {
- member_id = req.user_id;
- permissionString = "CHANGE_NICKNAME";
- }
+router.patch(
+ "/",
+ route({ body: "MemberNickChangeSchema" }),
+ async (req: Request, res: Response) => {
+ var { guild_id, member_id } = req.params;
+ var permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
+ if (member_id === "@me") {
+ member_id = req.user_id;
+ permissionString = "CHANGE_NICKNAME";
+ }
- const perms = await getPermission(req.user_id, guild_id);
- perms.hasThrow(permissionString);
+ const perms = await getPermission(req.user_id, guild_id);
+ perms.hasThrow(permissionString);
- await Member.changeNickname(member_id, guild_id, req.body.nick);
- res.status(200).send();
-});
+ await Member.changeNickname(member_id, guild_id, req.body.nick);
+ res.status(200).send();
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 8f5ca7ba..c0383912 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
@@ -4,18 +4,26 @@ import { Request, Response, Router } from "express";
const router = Router();
-router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { guild_id, role_id, member_id } = req.params;
+router.delete(
+ "/",
+ route({ permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id, member_id } = req.params;
- await Member.removeRole(member_id, guild_id, role_id);
- res.sendStatus(204);
-});
+ await Member.removeRole(member_id, guild_id, role_id);
+ res.sendStatus(204);
+ },
+);
-router.put("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { guild_id, role_id, member_id } = req.params;
+router.put(
+ "/",
+ route({ permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id, member_id } = req.params;
- await Member.addRole(member_id, guild_id, role_id);
- res.sendStatus(204);
-});
+ await Member.addRole(member_id, guild_id, role_id);
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index b730a4e7..b516b9e9 100644
--- a/src/api/routes/guilds/#guild_id/members/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -12,7 +12,8 @@ const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const limit = Number(req.query.limit) || 1;
- if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000");
+ if (limit > 1000 || limit < 1)
+ throw new HTTPError("Limit must be between 1 and 1000");
const after = `${req.query.after}`;
const query = after ? { id: MoreThan(after) } : {};
@@ -22,7 +23,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
where: { guild_id, ...query },
select: PublicMemberProjection,
take: limit,
- order: { id: "ASC" }
+ order: { id: "ASC" },
});
return res.json(members);
diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts
index a7516ebd..f2d8087e 100644
--- a/src/api/routes/guilds/#guild_id/messages/search.ts
+++ b/src/api/routes/guilds/#guild_id/messages/search.ts
@@ -10,36 +10,62 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const {
channel_id,
content,
- include_nsfw, // TODO
+ include_nsfw, // TODO
offset,
sort_order,
- sort_by, // TODO: Handle 'relevance'
+ sort_by, // TODO: Handle 'relevance'
limit,
author_id,
} = req.query;
const parsedLimit = Number(limit) || 50;
- if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
+ if (parsedLimit < 1 || parsedLimit > 100)
+ throw new HTTPError("limit must be between 1 and 100", 422);
if (sort_order) {
- if (typeof sort_order != "string"
- || ["desc", "asc"].indexOf(sort_order) == -1)
- throw FieldErrors({ sort_order: { message: "Value must be one of ('desc', 'asc').", code: "BASE_TYPE_CHOICES" } }); // todo this is wrong
+ if (
+ typeof sort_order != "string" ||
+ ["desc", "asc"].indexOf(sort_order) == -1
+ )
+ throw FieldErrors({
+ sort_order: {
+ message: "Value must be one of ('desc', 'asc').",
+ code: "BASE_TYPE_CHOICES",
+ },
+ }); // todo this is wrong
}
- const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string);
+ const permissions = await getPermission(
+ req.user_id,
+ req.params.guild_id,
+ channel_id as string,
+ );
permissions.hasThrow("VIEW_CHANNEL");
- if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 });
+ if (!permissions.has("READ_MESSAGE_HISTORY"))
+ return res.json({ messages: [], total_results: 0 });
var query: FindManyOptions<Message> = {
- order: { timestamp: sort_order ? sort_order.toUpperCase() as "ASC" | "DESC" : "DESC" },
+ order: {
+ timestamp: sort_order
+ ? (sort_order.toUpperCase() as "ASC" | "DESC")
+ : "DESC",
+ },
take: parsedLimit || 0,
where: {
guild: {
id: req.params.guild_id,
},
},
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ ],
skip: offset ? Number(offset) : 0,
};
//@ts-ignore
@@ -51,32 +77,34 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const messages: Message[] = await Message.find(query);
- const messagesDto = messages.map(x => [{
- id: x.id,
- type: x.type,
- content: x.content,
- channel_id: x.channel_id,
- author: {
- id: x.author?.id,
- username: x.author?.username,
- avatar: x.author?.avatar,
- avatar_decoration: null,
- discriminator: x.author?.discriminator,
- public_flags: x.author?.public_flags,
+ const messagesDto = messages.map((x) => [
+ {
+ id: x.id,
+ type: x.type,
+ content: x.content,
+ channel_id: x.channel_id,
+ author: {
+ id: x.author?.id,
+ username: x.author?.username,
+ avatar: x.author?.avatar,
+ avatar_decoration: null,
+ discriminator: x.author?.discriminator,
+ public_flags: x.author?.public_flags,
+ },
+ attachments: x.attachments,
+ embeds: x.embeds,
+ mentions: x.mentions,
+ mention_roles: x.mention_roles,
+ pinned: x.pinned,
+ mention_everyone: x.mention_everyone,
+ tts: x.tts,
+ timestamp: x.timestamp,
+ edited_timestamp: x.edited_timestamp,
+ flags: x.flags,
+ components: x.components,
+ hit: true,
},
- attachments: x.attachments,
- embeds: x.embeds,
- mentions: x.mentions,
- mention_roles: x.mention_roles,
- pinned: x.pinned,
- mention_everyone: x.mention_everyone,
- tts: x.tts,
- timestamp: x.timestamp,
- edited_timestamp: x.edited_timestamp,
- flags: x.flags,
- components: x.components,
- hit: true,
- }]);
+ ]);
return res.json({
messages: messagesDto,
@@ -84,4 +112,4 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
});
-export default router;
\ No newline at end of file
+export default router;
diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index 2e674349..d11244b1 100644
--- a/src/api/routes/guilds/#guild_id/prune.ts
+++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -5,7 +5,12 @@ import { route } from "@fosscord/api";
const router = Router();
//Returns all inactive members, respecting role hierarchy
-export const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => {
+export const inactiveMembers = async (
+ guild_id: string,
+ user_id: string,
+ days: number,
+ roles: string[] = [],
+) => {
var date = new Date();
date.setDate(date.getDate() - days);
//Snowflake should have `generateFromTime` method? Or similar?
@@ -19,21 +24,27 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n
where: [
{
guild_id,
- last_message_id: LessThan(minId.toString())
+ last_message_id: LessThan(minId.toString()),
},
{
- last_message_id: IsNull()
- }
+ last_message_id: IsNull(),
+ },
],
- relations: ["roles"]
+ relations: ["roles"],
});
console.log(members);
if (!members.length) return [];
//I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
- if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
-
- const me = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["roles"] });
+ if (roles.length && members.length)
+ members = members.filter((user) =>
+ user.roles?.some((role) => roles.includes(role.id)),
+ );
+
+ const me = await Member.findOneOrFail({
+ where: { id: user_id, guild_id },
+ relations: ["roles"],
+ });
const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || []));
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@@ -44,8 +55,8 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n
member.roles?.some(
(role) =>
role.position < myHighestRole || //roles higher than me can't be kicked
- me.id === guild.owner_id //owner can kick anyone
- )
+ me.id === guild.owner_id, //owner can kick anyone
+ ),
);
return members;
@@ -57,23 +68,39 @@ router.get("/", route({}), async (req: Request, res: Response) => {
var roles = req.query.include_roles;
if (typeof roles === "string") roles = [roles]; //express will return array otherwise
- const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]);
+ const members = await inactiveMembers(
+ req.params.guild_id,
+ req.user_id,
+ days,
+ roles as string[],
+ );
res.send({ pruned: members.length });
});
-router.post("/", route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const days = parseInt(req.body.days);
-
- var roles = req.query.include_roles;
- if (typeof roles === "string") roles = [roles];
-
- const { guild_id } = req.params;
- const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
-
- await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
-
- res.send({ purged: members.length });
-});
+router.post(
+ "/",
+ route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const days = parseInt(req.body.days);
+
+ var roles = req.query.include_roles;
+ if (typeof roles === "string") roles = [roles];
+
+ const { guild_id } = req.params;
+ const members = await inactiveMembers(
+ guild_id,
+ req.user_id,
+ days,
+ roles as string[],
+ );
+
+ await Promise.all(
+ members.map((x) => Member.removeFromGuild(x.id, guild_id)),
+ );
+
+ res.send({ purged: members.length });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index 308d5ee5..0b275ea4 100644
--- a/src/api/routes/guilds/#guild_id/regions.ts
+++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -9,7 +9,12 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
//TODO we should use an enum for guild's features and not hardcoded strings
- return res.json(await getVoiceRegions(getIpAdress(req), guild.features.includes("VIP_REGIONS")));
+ return res.json(
+ await getVoiceRegions(
+ getIpAdress(req),
+ guild.features.includes("VIP_REGIONS"),
+ ),
+ );
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
index 87cf5261..e274e3d0 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -1,5 +1,13 @@
import { Router, Request, Response } from "express";
-import { Role, Member, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, handleFile, RoleModifySchema } from "@fosscord/util";
+import {
+ Role,
+ Member,
+ GuildRoleUpdateEvent,
+ GuildRoleDeleteEvent,
+ emitEvent,
+ handleFile,
+ RoleModifySchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import { HTTPError } from "lambert-server";
@@ -12,57 +20,72 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.json(role);
});
-router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { guild_id, role_id } = req.params;
- if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role");
+router.delete(
+ "/",
+ route({ permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id } = req.params;
+ if (role_id === guild_id)
+ throw new HTTPError("You can't delete the @everyone role");
- await Promise.all([
- Role.delete({
- id: role_id,
- guild_id: guild_id
- }),
- emitEvent({
- event: "GUILD_ROLE_DELETE",
- guild_id,
- data: {
+ await Promise.all([
+ Role.delete({
+ id: role_id,
+ guild_id: guild_id,
+ }),
+ emitEvent({
+ event: "GUILD_ROLE_DELETE",
guild_id,
- role_id
- }
- } as GuildRoleDeleteEvent)
- ]);
+ data: {
+ guild_id,
+ role_id,
+ },
+ } as GuildRoleDeleteEvent),
+ ]);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
// TODO: check role hierarchy
-router.patch("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { role_id, guild_id } = req.params;
- const body = req.body as RoleModifySchema;
+router.patch(
+ "/",
+ route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { role_id, guild_id } = req.params;
+ const body = req.body as RoleModifySchema;
- if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
- else body.icon = undefined;
+ if (body.icon && body.icon.length)
+ body.icon = await handleFile(
+ `/role-icons/${role_id}`,
+ body.icon as string,
+ );
+ else body.icon = undefined;
- const role = Role.create({
- ...body,
- id: role_id,
- guild_id,
- permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0"))
- });
-
- await Promise.all([
- role.save(),
- emitEvent({
- event: "GUILD_ROLE_UPDATE",
+ const role = Role.create({
+ ...body,
+ id: role_id,
guild_id,
- data: {
+ permissions: String(
+ req.permission!.bitfield & BigInt(body.permissions || "0"),
+ ),
+ });
+
+ await Promise.all([
+ role.save(),
+ emitEvent({
+ event: "GUILD_ROLE_UPDATE",
guild_id,
- role
- }
- } as GuildRoleUpdateEvent)
- ]);
+ data: {
+ guild_id,
+ role,
+ },
+ } as GuildRoleUpdateEvent),
+ ]);
- res.json(role);
-});
+ res.json(role);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index c5a86400..e3c7373e 100644
--- a/src/api/routes/guilds/#guild_id/roles/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -29,70 +29,87 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.json(roles);
});
-router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
- const body = req.body as RoleModifySchema;
-
- const role_count = await Role.count({ where: { guild_id } });
- const { maxRoles } = Config.get().limits.guild;
-
- if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
-
- const role = Role.create({
- // values before ...body are default and can be overriden
- position: 0,
- hoist: false,
- color: 0,
- mentionable: false,
- ...body,
- guild_id: guild_id,
- managed: false,
- permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")),
- tags: undefined,
- icon: undefined,
- unicode_emoji: undefined
- });
-
- await Promise.all([
- role.save(),
- emitEvent({
- event: "GUILD_ROLE_CREATE",
- guild_id,
- data: {
- guild_id,
- role: role
- }
- } as GuildRoleCreateEvent)
- ]);
-
- res.json(role);
-});
-
-router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as RolePositionUpdateSchema;
-
- const perms = await getPermission(req.user_id, guild_id);
- perms.hasThrow("MANAGE_ROLES");
-
- await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));
-
- const roles = await Role.find({ where: body.map((x) => ({ id: x.id, guild_id })) });
-
- await Promise.all(
- roles.map((x) =>
+router.post(
+ "/",
+ route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const guild_id = req.params.guild_id;
+ const body = req.body as RoleModifySchema;
+
+ const role_count = await Role.count({ where: { guild_id } });
+ const { maxRoles } = Config.get().limits.guild;
+
+ if (role_count > maxRoles)
+ throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
+
+ const role = Role.create({
+ // values before ...body are default and can be overriden
+ position: 0,
+ hoist: false,
+ color: 0,
+ mentionable: false,
+ ...body,
+ guild_id: guild_id,
+ managed: false,
+ permissions: String(
+ req.permission!.bitfield & BigInt(body.permissions || "0"),
+ ),
+ tags: undefined,
+ icon: undefined,
+ unicode_emoji: undefined,
+ });
+
+ await Promise.all([
+ role.save(),
emitEvent({
- event: "GUILD_ROLE_UPDATE",
+ event: "GUILD_ROLE_CREATE",
guild_id,
data: {
guild_id,
- role: x
- }
- } as GuildRoleUpdateEvent)
- )
- );
-
- res.json(roles);
-});
+ role: role,
+ },
+ } as GuildRoleCreateEvent),
+ ]);
+
+ res.json(role);
+ },
+);
+
+router.patch(
+ "/",
+ route({ body: "RolePositionUpdateSchema" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as RolePositionUpdateSchema;
+
+ const perms = await getPermission(req.user_id, guild_id);
+ perms.hasThrow("MANAGE_ROLES");
+
+ await Promise.all(
+ body.map(async (x) =>
+ Role.update({ guild_id, id: x.id }, { position: x.position }),
+ ),
+ );
+
+ const roles = await Role.find({
+ where: body.map((x) => ({ id: x.id, guild_id })),
+ });
+
+ await Promise.all(
+ roles.map((x) =>
+ emitEvent({
+ event: "GUILD_ROLE_UPDATE",
+ guild_id,
+ data: {
+ guild_id,
+ role: x,
+ },
+ } as GuildRoleUpdateEvent),
+ ),
+ );
+
+ res.json(roles);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index fc0f49ab..3b1f5f8e 100644
--- a/src/api/routes/guilds/#guild_id/stickers.ts
+++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -26,15 +26,18 @@ const bodyParser = multer({
limits: {
fileSize: 1024 * 1024 * 100,
fields: 10,
- files: 1
+ files: 1,
},
- storage: multer.memoryStorage()
+ storage: multer.memoryStorage(),
}).single("file");
router.post(
"/",
bodyParser,
- route({ permission: "MANAGE_EMOJIS_AND_STICKERS", body: "ModifyGuildStickerSchema" }),
+ route({
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ body: "ModifyGuildStickerSchema",
+ }),
async (req: Request, res: Response) => {
if (!req.file) throw new HTTPError("missing file");
@@ -49,15 +52,15 @@ router.post(
id,
type: StickerType.GUILD,
format_type: getStickerFormat(req.file.mimetype),
- available: true
+ available: true,
}).save(),
- uploadFile(`/stickers/${id}`, req.file)
+ uploadFile(`/stickers/${id}`, req.file),
]);
await sendStickerUpdateEvent(guild_id);
res.json(sticker);
- }
+ },
);
export function getStickerFormat(mime_type: string) {
@@ -71,7 +74,9 @@ export function getStickerFormat(mime_type: string) {
case "image/gif":
return StickerFormatType.GIF;
default:
- throw new HTTPError("invalid sticker format: must be png, apng or lottie");
+ throw new HTTPError(
+ "invalid sticker format: must be png, apng or lottie",
+ );
}
}
@@ -79,21 +84,30 @@ router.get("/:sticker_id", route({}), async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
- res.json(await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }));
+ res.json(
+ await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }),
+ );
});
router.patch(
"/:sticker_id",
- route({ body: "ModifyGuildStickerSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+ route({
+ body: "ModifyGuildStickerSchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ }),
async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;
const body = req.body as ModifyGuildStickerSchema;
- const sticker = await Sticker.create({ ...body, guild_id, id: sticker_id }).save();
+ const sticker = await Sticker.create({
+ ...body,
+ guild_id,
+ id: sticker_id,
+ }).save();
await sendStickerUpdateEvent(guild_id);
return res.json(sticker);
- }
+ },
);
async function sendStickerUpdateEvent(guild_id: string) {
@@ -102,18 +116,22 @@ async function sendStickerUpdateEvent(guild_id: string) {
guild_id: guild_id,
data: {
guild_id: guild_id,
- stickers: await Sticker.find({ where: { guild_id: guild_id } })
- }
+ stickers: await Sticker.find({ where: { guild_id: guild_id } }),
+ },
} as GuildStickersUpdateEvent);
}
-router.delete("/:sticker_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
- const { guild_id, sticker_id } = req.params;
+router.delete(
+ "/:sticker_id",
+ route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, sticker_id } = req.params;
- await Sticker.delete({ guild_id, id: sticker_id });
- await sendStickerUpdateEvent(guild_id);
+ await Sticker.delete({ guild_id, id: sticker_id });
+ await sendStickerUpdateEvent(guild_id);
- return res.sendStatus(204);
-});
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index 628321f5..3b5eddaa 100644
--- a/src/api/routes/guilds/#guild_id/templates.ts
+++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -20,63 +20,97 @@ const TemplateGuildProjection: (keyof Guild)[] = [
"afk_channel_id",
"system_channel_id",
"system_channel_flags",
- "icon"
+ "icon",
];
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- var templates = await Template.find({ where: { source_guild_id: guild_id } });
-
- return res.json(templates);
-});
-
-router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
- const exists = await Template.findOneOrFail({ where: { id: guild_id } }).catch((e) => { });
- if (exists) throw new HTTPError("Template already exists", 400);
-
- const template = await Template.create({
- ...req.body,
- code: generateCode(),
- creator_id: req.user_id,
- created_at: new Date(),
- updated_at: new Date(),
- source_guild_id: guild_id,
- serialized_source_guild: guild
- }).save();
-
- res.json(template);
-});
-
-router.delete("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
-
- const template = await Template.delete({
- code,
- source_guild_id: guild_id
+ var templates = await Template.find({
+ where: { source_guild_id: guild_id },
});
- res.json(template);
-});
-
-router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
-
- const template = await Template.create({ code, serialized_source_guild: guild }).save();
-
- res.json(template);
+ return res.json(templates);
});
-router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
- const { name, description } = req.body;
-
- const template = await Template.create({ code, name: name, description: description, source_guild_id: guild_id }).save();
-
- res.json(template);
-});
+router.post(
+ "/",
+ route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: TemplateGuildProjection,
+ });
+ const exists = await Template.findOneOrFail({
+ where: { id: guild_id },
+ }).catch((e) => {});
+ if (exists) throw new HTTPError("Template already exists", 400);
+
+ const template = await Template.create({
+ ...req.body,
+ code: generateCode(),
+ creator_id: req.user_id,
+ created_at: new Date(),
+ updated_at: new Date(),
+ source_guild_id: guild_id,
+ serialized_source_guild: guild,
+ }).save();
+
+ res.json(template);
+ },
+);
+
+router.delete(
+ "/:code",
+ route({ permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
+
+ const template = await Template.delete({
+ code,
+ source_guild_id: guild_id,
+ });
+
+ res.json(template);
+ },
+);
+
+router.put(
+ "/:code",
+ route({ permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: TemplateGuildProjection,
+ });
+
+ const template = await Template.create({
+ code,
+ serialized_source_guild: guild,
+ }).save();
+
+ res.json(template);
+ },
+);
+
+router.patch(
+ "/:code",
+ route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
+ const { name, description } = req.body;
+
+ const template = await Template.create({
+ code,
+ name: name,
+ description: description,
+ source_guild_id: guild_id,
+ }).save();
+
+ res.json(template);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index d1fe4726..9a96b066 100644
--- a/src/api/routes/guilds/#guild_id/vanity-url.ts
+++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -1,4 +1,10 @@
-import { Channel, ChannelType, Guild, Invite, VanityUrlSchema } from "@fosscord/util";
+import {
+ Channel,
+ ChannelType,
+ Guild,
+ Invite,
+ VanityUrlSchema,
+} from "@fosscord/util";
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
import { HTTPError } from "lambert-server";
@@ -7,52 +13,70 @@ const router = Router();
const InviteRegex = /\W/g;
-router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
-
- if (!guild.features.includes("ALIASABLE_NAMES")) {
- const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } });
- if (!invite) return res.json({ code: null });
-
- return res.json({ code: invite.code, uses: invite.uses });
- } else {
- const invite = await Invite.find({ where: { guild_id: guild_id, vanity_url: true } });
- if (!invite || invite.length == 0) return res.json({ code: null });
-
- return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
- }
-});
-
-router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as VanityUrlSchema;
- const code = body.code?.replace(InviteRegex, "");
-
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
-
- if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
-
- const invite = await Invite.findOne({ where: { code } });
- if (invite) throw new HTTPError("Invite already exists");
-
- const { id } = await Channel.findOneOrFail({ where: { guild_id, type: ChannelType.GUILD_TEXT } });
-
- await Invite.create({
- vanity_url: true,
- code: code,
- temporary: false,
- uses: 0,
- max_uses: 0,
- max_age: 0,
- created_at: new Date(),
- expires_at: new Date(),
- guild_id: guild_id,
- channel_id: id
- }).save();
-
- return res.json({ code: code });
-});
+router.get(
+ "/",
+ route({ permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+
+ if (!guild.features.includes("ALIASABLE_NAMES")) {
+ const invite = await Invite.findOne({
+ where: { guild_id: guild_id, vanity_url: true },
+ });
+ if (!invite) return res.json({ code: null });
+
+ return res.json({ code: invite.code, uses: invite.uses });
+ } else {
+ const invite = await Invite.find({
+ where: { guild_id: guild_id, vanity_url: true },
+ });
+ if (!invite || invite.length == 0) return res.json({ code: null });
+
+ return res.json(
+ invite.map((x) => ({ code: x.code, uses: x.uses })),
+ );
+ }
+ },
+);
+
+router.patch(
+ "/",
+ route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as VanityUrlSchema;
+ const code = body.code?.replace(InviteRegex, "");
+
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ if (!guild.features.includes("VANITY_URL"))
+ throw new HTTPError("Your guild doesn't support vanity urls");
+
+ if (!code || code.length === 0)
+ throw new HTTPError("Code cannot be null or empty");
+
+ const invite = await Invite.findOne({ where: { code } });
+ if (invite) throw new HTTPError("Invite already exists");
+
+ const { id } = await Channel.findOneOrFail({
+ where: { guild_id, type: ChannelType.GUILD_TEXT },
+ });
+
+ await Invite.create({
+ vanity_url: true,
+ code: code,
+ temporary: false,
+ uses: 0,
+ max_uses: 0,
+ max_age: 0,
+ created_at: new Date(),
+ expires_at: new Date(),
+ guild_id: guild_id,
+ channel_id: id,
+ }).save();
+
+ return res.json({ code: code });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index 006e997f..af03a07e 100644
--- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -1,52 +1,71 @@
-import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent, VoiceStateUpdateSchema } from "@fosscord/util";
+import {
+ Channel,
+ ChannelType,
+ DiscordApiErrors,
+ emitEvent,
+ getPermission,
+ VoiceState,
+ VoiceStateUpdateEvent,
+ VoiceStateUpdateSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import { Request, Response, Router } from "express";
const router = Router();
//TODO need more testing when community guild and voice stage channel are working
-router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => {
- const body = req.body as VoiceStateUpdateSchema;
- var { guild_id, user_id } = req.params;
- if (user_id === "@me") user_id = req.user_id;
+router.patch(
+ "/",
+ route({ body: "VoiceStateUpdateSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as VoiceStateUpdateSchema;
+ var { guild_id, user_id } = req.params;
+ if (user_id === "@me") user_id = req.user_id;
- const perms = await getPermission(req.user_id, guild_id, body.channel_id);
+ const perms = await getPermission(
+ req.user_id,
+ guild_id,
+ body.channel_id,
+ );
- /*
+ /*
From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state
You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself.
You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak.
*/
- if (body.suppress && user_id !== req.user_id) {
- perms.hasThrow("MUTE_MEMBERS");
- }
- if (!body.suppress) body.request_to_speak_timestamp = new Date();
- if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
-
- const voice_state = await VoiceState.findOne({
- where: {
- guild_id,
- channel_id: body.channel_id,
- user_id
+ if (body.suppress && user_id !== req.user_id) {
+ perms.hasThrow("MUTE_MEMBERS");
+ }
+ if (!body.suppress) body.request_to_speak_timestamp = new Date();
+ if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
+
+ const voice_state = await VoiceState.findOne({
+ where: {
+ guild_id,
+ channel_id: body.channel_id,
+ user_id,
+ },
+ });
+ if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
+
+ voice_state.assign(body);
+ const channel = await Channel.findOneOrFail({
+ where: { guild_id, id: body.channel_id },
+ });
+ if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
+ throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
}
- });
- if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
-
- voice_state.assign(body);
- const channel = await Channel.findOneOrFail({ where: { guild_id, id: body.channel_id } });
- if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
- throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
- }
-
- await Promise.all([
- voice_state.save(),
- emitEvent({
- event: "VOICE_STATE_UPDATE",
- data: voice_state,
- guild_id
- } as VoiceStateUpdateEvent)
- ]);
- return res.sendStatus(204);
-});
+
+ await Promise.all([
+ voice_state.save(),
+ emitEvent({
+ event: "VOICE_STATE_UPDATE",
+ data: voice_state,
+ guild_id,
+ } as VoiceStateUpdateEvent),
+ ]);
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts
index 57da062d..80ab138b 100644
--- a/src/api/routes/guilds/#guild_id/welcome-screen.ts
+++ b/src/api/routes/guilds/#guild_id/welcome-screen.ts
@@ -14,20 +14,30 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.json(guild.welcome_screen);
});
-router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
- const body = req.body as GuildUpdateWelcomeScreenSchema;
-
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
-
- if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400);
- if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid
- if (body.description) guild.welcome_screen.description = body.description;
- if (body.enabled != null) guild.welcome_screen.enabled = body.enabled;
-
- await guild.save();
-
- res.sendStatus(204);
-});
+router.patch(
+ "/",
+ route({
+ body: "GuildUpdateWelcomeScreenSchema",
+ permission: "MANAGE_GUILD",
+ }),
+ async (req: Request, res: Response) => {
+ const guild_id = req.params.guild_id;
+ const body = req.body as GuildUpdateWelcomeScreenSchema;
+
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+
+ if (!guild.welcome_screen.enabled)
+ throw new HTTPError("Welcome screen disabled", 400);
+ if (body.welcome_channels)
+ guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid
+ if (body.description)
+ guild.welcome_screen.description = body.description;
+ if (body.enabled != null) guild.welcome_screen.enabled = body.enabled;
+
+ await guild.save();
+
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index be5bf23f..2c3124a2 100644
--- a/src/api/routes/guilds/#guild_id/widget.json.ts
+++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -1,5 +1,12 @@
import { Request, Response, Router } from "express";
-import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util";
+import {
+ Config,
+ Permissions,
+ Guild,
+ Invite,
+ Channel,
+ Member,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { random, route } from "@fosscord/api";
@@ -21,7 +28,9 @@ router.get("/", route({}), async (req: Request, res: Response) => {
if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
// Fetch existing widget invite for widget channel
- var invite = await Invite.findOne({ where: { channel_id: guild.widget_channel_id } });
+ var invite = await Invite.findOne({
+ where: { channel_id: guild.widget_channel_id },
+ });
if (guild.widget_channel_id && !invite) {
// Create invite for channel if none exists
@@ -45,16 +54,24 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// Fetch voice channels, and the @everyone permissions object
const channels = [] as any[];
- (await Channel.find({ where: { guild_id: guild_id, type: 2 }, order: { position: "ASC" } })).filter((doc) => {
+ (
+ await Channel.find({
+ where: { guild_id: guild_id, type: 2 },
+ order: { position: "ASC" },
+ })
+ ).filter((doc) => {
// Only return channels where @everyone has the CONNECT permission
if (
doc.permission_overwrites === undefined ||
- Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT
+ Permissions.channelPermission(
+ doc.permission_overwrites,
+ Permissions.FLAGS.CONNECT,
+ ) === Permissions.FLAGS.CONNECT
) {
channels.push({
id: doc.id,
name: doc.name,
- position: doc.position
+ position: doc.position,
});
}
});
@@ -70,7 +87,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
instant_invite: invite?.code,
channels: channels,
members: members,
- presence_count: guild.presence_count
+ presence_count: guild.presence_count,
};
res.set("Cache-Control", "public, max-age=300");
diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index c17d511e..eaec8f07 100644
--- a/src/api/routes/guilds/#guild_id/widget.png.ts
+++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -24,8 +24,13 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// Fetch parameter
const style = req.query.style?.toString() || "shield";
- if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) {
- throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
+ if (
+ !["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)
+ ) {
+ throw new HTTPError(
+ "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
+ 400,
+ );
}
// Setup canvas
@@ -34,7 +39,17 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const sizeOf = require("image-size");
// TODO: Widget style templates need Fosscord branding
- const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`);
+ const source = path.join(
+ __dirname,
+ "..",
+ "..",
+ "..",
+ "..",
+ "..",
+ "assets",
+ "widget",
+ `${style}.png`,
+ );
if (!fs.existsSync(source)) {
throw new HTTPError("Widget template does not exist.", 400);
}
@@ -50,30 +65,68 @@ router.get("/", route({}), async (req: Request, res: Response) => {
switch (style) {
case "shield":
ctx.textAlign = "center";
- await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence);
+ await drawText(
+ ctx,
+ 73,
+ 13,
+ "#FFFFFF",
+ "thin 10px Verdana",
+ presence,
+ );
break;
case "banner1":
if (icon) await drawIcon(ctx, 20, 27, 50, icon);
await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
- await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence);
+ await drawText(
+ ctx,
+ 83,
+ 66,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
break;
case "banner2":
if (icon) await drawIcon(ctx, 13, 19, 36, icon);
await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
- await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence);
+ await drawText(
+ ctx,
+ 62,
+ 49,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
break;
case "banner3":
if (icon) await drawIcon(ctx, 20, 20, 50, icon);
await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
- await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence);
+ await drawText(
+ ctx,
+ 83,
+ 58,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
break;
case "banner4":
if (icon) await drawIcon(ctx, 21, 136, 50, icon);
await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
- await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence);
+ await drawText(
+ ctx,
+ 84,
+ 171,
+ "#C9D2F0FF",
+ "thin 12px Verdana",
+ presence,
+ );
break;
default:
- throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
+ throw new HTTPError(
+ "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
+ 400,
+ );
}
// Return final image
@@ -83,7 +136,13 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.send(buffer);
});
-async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) {
+async function drawIcon(
+ canvas: any,
+ x: number,
+ y: number,
+ scale: number,
+ icon: string,
+) {
// @ts-ignore
const img = new require("canvas").Image();
img.src = icon;
@@ -101,10 +160,19 @@ async function drawIcon(canvas: any, x: number, y: number, scale: number, icon:
canvas.restore();
}
-async function drawText(canvas: any, x: number, y: number, color: string, font: string, text: string, maxcharacters?: number) {
+async function drawText(
+ canvas: any,
+ x: number,
+ y: number,
+ color: string,
+ font: string,
+ text: string,
+ maxcharacters?: number,
+) {
canvas.fillStyle = color;
canvas.font = font;
- if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
+ if (text.length > (maxcharacters || 0) && maxcharacters)
+ text = text.slice(0, maxcharacters) + "...";
canvas.fillText(text, x, y);
}
diff --git a/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index dbb4cc0c..108339e1 100644
--- a/src/api/routes/guilds/#guild_id/widget.ts
+++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -10,18 +10,31 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null });
+ return res.json({
+ enabled: guild.widget_enabled || false,
+ channel_id: guild.widget_channel_id || null,
+ });
});
// https://discord.com/developers/docs/resources/guild#modify-guild-widget
-router.patch("/", route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const body = req.body as WidgetModifySchema;
- const { guild_id } = req.params;
-
- await Guild.update({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id });
- // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
-
- return res.json(body);
-});
+router.patch(
+ "/",
+ route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as WidgetModifySchema;
+ const { guild_id } = req.params;
+
+ await Guild.update(
+ { id: guild_id },
+ {
+ widget_enabled: body.enabled,
+ widget_channel_id: body.channel_id,
+ },
+ );
+ // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
+
+ return res.json(body);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index 0807cb96..69575aea 100644
--- a/src/api/routes/guilds/index.ts
+++ b/src/api/routes/guilds/index.ts
@@ -1,32 +1,47 @@
import { Router, Request, Response } from "express";
-import { Role, Guild, Config, getRights, Member, DiscordApiErrors, GuildCreateSchema } from "@fosscord/util";
+import {
+ Role,
+ Guild,
+ Config,
+ getRights,
+ Member,
+ DiscordApiErrors,
+ GuildCreateSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
//TODO: create default channel
-router.post("/", route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }), async (req: Request, res: Response) => {
- const body = req.body as GuildCreateSchema;
-
- const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ where: { id: req.user_id } });
- const rights = await getRights(req.user_id);
- if ((guild_count >= maxGuilds) && !rights.has("MANAGE_GUILDS")) {
- throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
- }
-
- const guild = await Guild.createGuild({ ...body, owner_id: req.user_id });
-
- const { autoJoin } = Config.get().guild;
- if (autoJoin.enabled && !autoJoin.guilds?.length) {
- // @ts-ignore
- await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
- }
-
- await Member.addToGuild(req.user_id, guild.id);
-
- res.status(201).json({ id: guild.id });
-});
+router.post(
+ "/",
+ route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as GuildCreateSchema;
+
+ const { maxGuilds } = Config.get().limits.user;
+ const guild_count = await Member.count({ where: { id: req.user_id } });
+ const rights = await getRights(req.user_id);
+ if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) {
+ throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
+ }
+
+ const guild = await Guild.createGuild({
+ ...body,
+ owner_id: req.user_id,
+ });
+
+ const { autoJoin } = Config.get().guild;
+ if (autoJoin.enabled && !autoJoin.guilds?.length) {
+ // @ts-ignore
+ await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
+ }
+
+ await Member.addToGuild(req.user_id, guild.id);
+
+ res.status(201).json({ id: guild.id });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index 4e7abcc5..240bf074 100644
--- a/src/api/routes/guilds/templates/index.ts
+++ b/src/api/routes/guilds/templates/index.ts
@@ -1,29 +1,58 @@
import { Request, Response, Router } from "express";
-import { Template, Guild, Role, Snowflake, Config, Member, GuildTemplateCreateSchema } from "@fosscord/util";
+import {
+ Template,
+ Guild,
+ Role,
+ Snowflake,
+ Config,
+ Member,
+ GuildTemplateCreateSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import { DiscordApiErrors } from "@fosscord/util";
import fetch from "node-fetch";
const router: Router = Router();
router.get("/:code", route({}), async (req: Request, res: Response) => {
- const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
- if (!enabled) res.json({ code: 403, message: "Template creation & usage is disabled on this instance." }).sendStatus(403);
+ const { allowDiscordTemplates, allowRaws, enabled } =
+ Config.get().templates;
+ if (!enabled)
+ res.json({
+ code: 403,
+ message: "Template creation & usage is disabled on this instance.",
+ }).sendStatus(403);
const { code } = req.params;
if (code.startsWith("discord:")) {
- if (!allowDiscordTemplates) return res.json({ code: 403, message: "Discord templates cannot be used on this instance." }).sendStatus(403);
+ if (!allowDiscordTemplates)
+ return res
+ .json({
+ code: 403,
+ message:
+ "Discord templates cannot be used on this instance.",
+ })
+ .sendStatus(403);
const discordTemplateID = code.split("discord:", 2)[1];
- const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, {
- method: "get",
- headers: { "Content-Type": "application/json" }
- });
+ const discordTemplateData = await fetch(
+ `https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
+ {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
return res.json(await discordTemplateData.json());
}
if (code.startsWith("external:")) {
- if (!allowRaws) return res.json({ code: 403, message: "Importing raws is disabled on this instance." }).sendStatus(403);
+ if (!allowRaws)
+ return res
+ .json({
+ code: 403,
+ message: "Importing raws is disabled on this instance.",
+ })
+ .sendStatus(403);
return res.json(code.split("external:", 2)[1]);
}
@@ -32,48 +61,72 @@ router.get("/:code", route({}), async (req: Request, res: Response) => {
res.json(template);
});
-router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => {
- const { enabled, allowTemplateCreation, allowDiscordTemplates, allowRaws } = Config.get().templates;
- if (!enabled) return res.json({ code: 403, message: "Template creation & usage is disabled on this instance." }).sendStatus(403);
- if (!allowTemplateCreation) return res.json({ code: 403, message: "Template creation is disabled on this instance." }).sendStatus(403);
-
- const { code } = req.params;
- const body = req.body as GuildTemplateCreateSchema;
-
- const { maxGuilds } = Config.get().limits.user;
-
- const guild_count = await Member.count({ where: { id: req.user_id } });
- if (guild_count >= maxGuilds) {
- throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
- }
-
- const template = await Template.findOneOrFail({ where: { code: code } });
+router.post(
+ "/:code",
+ route({ body: "GuildTemplateCreateSchema" }),
+ async (req: Request, res: Response) => {
+ const {
+ enabled,
+ allowTemplateCreation,
+ allowDiscordTemplates,
+ allowRaws,
+ } = Config.get().templates;
+ if (!enabled)
+ return res
+ .json({
+ code: 403,
+ message:
+ "Template creation & usage is disabled on this instance.",
+ })
+ .sendStatus(403);
+ if (!allowTemplateCreation)
+ return res
+ .json({
+ code: 403,
+ message: "Template creation is disabled on this instance.",
+ })
+ .sendStatus(403);
+
+ const { code } = req.params;
+ const body = req.body as GuildTemplateCreateSchema;
+
+ const { maxGuilds } = Config.get().limits.user;
+
+ const guild_count = await Member.count({ where: { id: req.user_id } });
+ if (guild_count >= maxGuilds) {
+ throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
+ }
+
+ const template = await Template.findOneOrFail({
+ where: { code: code },
+ });
- const guild_id = Snowflake.generate();
-
- const [guild, role] = await Promise.all([
- Guild.create({
- ...body,
- ...template.serialized_source_guild,
- id: guild_id,
- owner_id: req.user_id
- }).save(),
- Role.create({
- id: guild_id,
- guild_id: guild_id,
- color: 0,
- hoist: false,
- managed: true,
- mentionable: true,
- name: "@everyone",
- permissions: BigInt("2251804225").toString(), // TODO: where did this come from?
- position: 0,
- }).save()
- ]);
-
- await Member.addToGuild(req.user_id, guild_id);
-
- res.status(201).json({ id: guild.id });
-});
+ const guild_id = Snowflake.generate();
+
+ const [guild, role] = await Promise.all([
+ Guild.create({
+ ...body,
+ ...template.serialized_source_guild,
+ id: guild_id,
+ owner_id: req.user_id,
+ }).save(),
+ Role.create({
+ id: guild_id,
+ guild_id: guild_id,
+ color: 0,
+ hoist: false,
+ managed: true,
+ mentionable: true,
+ name: "@everyone",
+ permissions: BigInt("2251804225").toString(), // TODO: where did this come from?
+ position: 0,
+ }).save(),
+ ]);
+
+ await Member.addToGuild(req.user_id, guild_id);
+
+ res.status(201).json({ id: guild.id });
+ },
+);
export default router;
diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts
index c268085f..ce0ba982 100644
--- a/src/api/routes/invites/index.ts
+++ b/src/api/routes/invites/index.ts
@@ -1,5 +1,13 @@
import { Router, Request, Response } from "express";
-import { emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, User, PublicInviteRelation } from "@fosscord/util";
+import {
+ emitEvent,
+ getPermission,
+ Guild,
+ Invite,
+ InviteDeleteEvent,
+ User,
+ PublicInviteRelation,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import { HTTPError } from "lambert-server";
@@ -8,24 +16,45 @@ const router: Router = Router();
router.get("/:code", route({}), async (req: Request, res: Response) => {
const { code } = req.params;
- const invite = await Invite.findOneOrFail({ where: { code }, relations: PublicInviteRelation });
+ const invite = await Invite.findOneOrFail({
+ where: { code },
+ relations: PublicInviteRelation,
+ });
res.status(200).send(invite);
});
-router.post("/:code", route({ right: "USE_MASS_INVITES" }), async (req: Request, res: Response) => {
- const { code } = req.params;
- const { guild_id } = await Invite.findOneOrFail({ where: { code: code } });
- const { features } = await Guild.findOneOrFail({ where: { id: guild_id } });
- const { public_flags } = await User.findOneOrFail({ where: { id: req.user_id } });
+router.post(
+ "/:code",
+ route({ right: "USE_MASS_INVITES" }),
+ async (req: Request, res: Response) => {
+ const { code } = req.params;
+ const { guild_id } = await Invite.findOneOrFail({
+ where: { code: code },
+ });
+ const { features } = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ });
+ const { public_flags } = await User.findOneOrFail({
+ where: { id: req.user_id },
+ });
- if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) throw new HTTPError("Only intended for the staff of this server.", 401);
- if (features.includes("INVITES_CLOSED")) throw new HTTPError("Sorry, this guild has joins closed.", 403);
+ if (
+ features.includes("INTERNAL_EMPLOYEE_ONLY") &&
+ (public_flags & 1) !== 1
+ )
+ throw new HTTPError(
+ "Only intended for the staff of this server.",
+ 401,
+ );
+ if (features.includes("INVITES_CLOSED"))
+ throw new HTTPError("Sorry, this guild has joins closed.", 403);
- const invite = await Invite.joinGuild(req.user_id, code);
+ const invite = await Invite.joinGuild(req.user_id, code);
- res.json(invite);
-});
+ res.json(invite);
+ },
+);
// * cant use permission of route() function because path doesn't have guild_id/channel_id
router.delete("/:code", route({}), async (req: Request, res: Response) => {
@@ -36,7 +65,10 @@ router.delete("/:code", route({}), async (req: Request, res: Response) => {
const permission = await getPermission(req.user_id, guild_id, channel_id);
if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS"))
- throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401);
+ throw new HTTPError(
+ "You missing the MANAGE_GUILD or MANAGE_CHANNELS permission",
+ 401,
+ );
await Promise.all([
Invite.delete({ code }),
@@ -46,9 +78,9 @@ router.delete("/:code", route({}), async (req: Request, res: Response) => {
data: {
channel_id: channel_id,
guild_id: guild_id,
- code: code
- }
- } as InviteDeleteEvent)
+ code: code,
+ },
+ } as InviteDeleteEvent),
]);
res.json({ invite: invite });
diff --git a/src/api/routes/partners/#guild_id/requirements.ts b/src/api/routes/partners/#guild_id/requirements.ts
index 545c5c78..7e63c06b 100644
--- a/src/api/routes/partners/#guild_id/requirements.ts
+++ b/src/api/routes/partners/#guild_id/requirements.ts
@@ -1,4 +1,3 @@
-
import { Guild, Config } from "@fosscord/util";
import { Router, Request, Response } from "express";
@@ -7,33 +6,33 @@ import { route } from "@fosscord/api";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- // TODO:
- // Load from database
- // Admin control, but for now it allows anyone to be discoverable
+ const { guild_id } = req.params;
+ // TODO:
+ // Load from database
+ // Admin control, but for now it allows anyone to be discoverable
res.send({
guild_id: guild_id,
safe_environment: true,
- healthy: true,
- health_score_pending: false,
- size: true,
- nsfw_properties: {},
- protected: true,
- sufficient: true,
- sufficient_without_grace_period: true,
- valid_rules_channel: true,
- retention_healthy: true,
- engagement_healthy: true,
- age: true,
- minimum_age: 0,
- health_score: {
- avg_nonnew_participators: 0,
- avg_nonnew_communicators: 0,
- num_intentful_joiners: 0,
- perc_ret_w1_intentful: 0
- },
- minimum_size: 0
+ healthy: true,
+ health_score_pending: false,
+ size: true,
+ nsfw_properties: {},
+ protected: true,
+ sufficient: true,
+ sufficient_without_grace_period: true,
+ valid_rules_channel: true,
+ retention_healthy: true,
+ engagement_healthy: true,
+ age: true,
+ minimum_age: 0,
+ health_score: {
+ avg_nonnew_participators: 0,
+ avg_nonnew_communicators: 0,
+ num_intentful_joiners: 0,
+ perc_ret_w1_intentful: 0,
+ },
+ minimum_size: 0,
});
});
diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts
index 20cd07ba..f22eac17 100644
--- a/src/api/routes/policies/instance/domains.ts
+++ b/src/api/routes/policies/instance/domains.ts
@@ -1,16 +1,19 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
-import { config } from "dotenv"
+import { config } from "dotenv";
const router = Router();
-router.get("/",route({}), async (req: Request, res: Response) => {
- const { cdn, gateway } = Config.get();
-
- const IdentityForm = {
- cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001",
- gateway: gateway.endpointPublic || process.env.GATEWAY || "ws://localhost:3002"
- };
+router.get("/", route({}), async (req: Request, res: Response) => {
+ const { cdn, gateway } = Config.get();
+
+ const IdentityForm = {
+ cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001",
+ gateway:
+ gateway.endpointPublic ||
+ process.env.GATEWAY ||
+ "ws://localhost:3002",
+ };
res.json(IdentityForm);
});
diff --git a/src/api/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts
index e3da014f..1c1afa09 100644
--- a/src/api/routes/policies/instance/index.ts
+++ b/src/api/routes/policies/instance/index.ts
@@ -3,8 +3,7 @@ import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
const router = Router();
-
-router.get("/",route({}), async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
const { general } = Config.get();
res.json(general);
});
diff --git a/src/api/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts
index 7de1476b..06f14f83 100644
--- a/src/api/routes/policies/instance/limits.ts
+++ b/src/api/routes/policies/instance/limits.ts
@@ -3,7 +3,7 @@ import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
const router = Router();
-router.get("/",route({}), async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
const { limits } = Config.get();
res.json(limits);
});
diff --git a/src/api/routes/scheduled-maintenances/upcoming_json.ts b/src/api/routes/scheduled-maintenances/upcoming_json.ts
index 83092e44..e42723a1 100644
--- a/src/api/routes/scheduled-maintenances/upcoming_json.ts
+++ b/src/api/routes/scheduled-maintenances/upcoming_json.ts
@@ -2,11 +2,15 @@ import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
const router = Router();
-router.get("/scheduled-maintenances/upcoming.json",route({}), async (req: Request, res: Response) => {
- res.json({
- "page": {},
- "scheduled_maintenances": {}
- });
-});
+router.get(
+ "/scheduled-maintenances/upcoming.json",
+ route({}),
+ async (req: Request, res: Response) => {
+ res.json({
+ page: {},
+ scheduled_maintenances: {},
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/stop.ts b/src/api/routes/stop.ts
index 7f8b78ba..78abb9d7 100644
--- a/src/api/routes/stop.ts
+++ b/src/api/routes/stop.ts
@@ -6,17 +6,19 @@ const router: Router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
//EXPERIMENTAL: have an "OPERATOR" platform permission implemented for this API route
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["rights"] });
- if((Number(user.rights) << Number(0))%Number(2)==Number(1)) {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["rights"],
+ });
+ if ((Number(user.rights) << Number(0)) % Number(2) == Number(1)) {
console.log("user that POSTed to the API was ALLOWED");
console.log(user.rights);
- res.sendStatus(200)
- process.kill(process.pid, 'SIGTERM')
- }
- else {
+ res.sendStatus(200);
+ process.kill(process.pid, "SIGTERM");
+ } else {
console.log("operation failed");
console.log(user.rights);
- res.sendStatus(403)
+ res.sendStatus(403);
}
});
diff --git a/src/api/routes/store/published-listings/applications.ts b/src/api/routes/store/published-listings/applications.ts
index 060a4c3d..6156f43e 100644
--- a/src/api/routes/store/published-listings/applications.ts
+++ b/src/api/routes/store/published-listings/applications.ts
@@ -41,29 +41,29 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
publishers: [
{
id: "",
- name: ""
- }
+ name: "",
+ },
],
developers: [
{
id: "",
- name: ""
- }
+ name: "",
+ },
],
system_requirements: {},
show_age_gate: false,
price: {
amount: 0,
- currency: "EUR"
+ currency: "EUR",
},
- locales: []
+ locales: [],
},
tagline: "",
description: "",
carousel_items: [
{
- asset_id: ""
- }
+ asset_id: "",
+ },
],
header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
header_logo_light_theme: {},
@@ -71,8 +71,8 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
thumbnail: {},
header_background: {},
hero_background: {},
- assets: []
- }
+ assets: [],
+ },
}).status(200);
});
diff --git a/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts b/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
index 54151ae5..845cdfe7 100644
--- a/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
@@ -17,8 +17,8 @@ router.get("/", route({}), async (req: Request, res: Response) => {
fallback_currency: "eur",
currency: "eur",
price: 4199,
- price_tier: null
- }
+ price_tier: null,
+ },
]).status(200);
});
diff --git a/src/api/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts
index 060a4c3d..6156f43e 100644
--- a/src/api/routes/store/published-listings/skus.ts
+++ b/src/api/routes/store/published-listings/skus.ts
@@ -41,29 +41,29 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
publishers: [
{
id: "",
- name: ""
- }
+ name: "",
+ },
],
developers: [
{
id: "",
- name: ""
- }
+ name: "",
+ },
],
system_requirements: {},
show_age_gate: false,
price: {
amount: 0,
- currency: "EUR"
+ currency: "EUR",
},
- locales: []
+ locales: [],
},
tagline: "",
description: "",
carousel_items: [
{
- asset_id: ""
- }
+ asset_id: "",
+ },
],
header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
header_logo_light_theme: {},
@@ -71,8 +71,8 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
thumbnail: {},
header_background: {},
hero_background: {},
- assets: []
- }
+ assets: [],
+ },
}).status(200);
});
diff --git a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
index 03162ec8..33151056 100644
--- a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
@@ -17,8 +17,8 @@ const skus = new Map([
currency: "usd",
price: 0,
price_tier: null,
- }
- ]
+ },
+ ],
],
[
"521842865731534868",
@@ -32,7 +32,7 @@ const skus = new Map([
sku_id: "521842865731534868",
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "511651860671627264",
@@ -43,9 +43,9 @@ const skus = new Map([
sku_id: "521842865731534868",
currency: "usd",
price: 0,
- price_tier: null
- }
- ]
+ price_tier: null,
+ },
+ ],
],
[
"521846918637420545",
@@ -59,7 +59,7 @@ const skus = new Map([
sku_id: "521846918637420545",
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "511651876987469824",
@@ -70,9 +70,9 @@ const skus = new Map([
sku_id: "521846918637420545",
currency: "usd",
price: 0,
- price_tier: null
- }
- ]
+ price_tier: null,
+ },
+ ],
],
[
"521847234246082599",
@@ -86,7 +86,7 @@ const skus = new Map([
sku_id: "521847234246082599",
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "511651880837840896",
@@ -97,7 +97,7 @@ const skus = new Map([
sku_id: "521847234246082599",
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "511651885459963904",
@@ -108,9 +108,9 @@ const skus = new Map([
sku_id: "521847234246082599",
currency: "usd",
price: 0,
- price_tier: null
- }
- ]
+ price_tier: null,
+ },
+ ],
],
[
"590663762298667008",
@@ -125,7 +125,7 @@ const skus = new Map([
discount_price: 0,
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "590665538238152709",
@@ -137,10 +137,10 @@ const skus = new Map([
discount_price: 0,
currency: "usd",
price: 0,
- price_tier: null
- }
- ]
- ]
+ price_tier: null,
+ },
+ ],
+ ],
]);
router.get("/", route({}), async (req: Request, res: Response) => {
diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts
index 42f77323..8fe6fc2a 100644
--- a/src/api/routes/updates.ts
+++ b/src/api/routes/updates.ts
@@ -7,13 +7,15 @@ const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { client } = Config.get();
- const release = await Release.findOneOrFail({ where: { name: client.releases.upstreamVersion } });
+ const release = await Release.findOneOrFail({
+ where: { name: client.releases.upstreamVersion },
+ });
res.json({
name: release.name,
pub_date: release.pub_date,
url: release.url,
- notes: release.notes
+ notes: release.notes,
});
});
diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts
index de96422a..ebea805b 100644
--- a/src/api/routes/users/#id/profile.ts
+++ b/src/api/routes/users/#id/profile.ts
@@ -1,5 +1,12 @@
import { Router, Request, Response } from "express";
-import { PublicConnectedAccount, PublicUser, User, UserPublic, Member, Guild } from "@fosscord/util";
+import {
+ PublicConnectedAccount,
+ PublicUser,
+ User,
+ UserPublic,
+ Member,
+ Guild,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
@@ -11,81 +18,102 @@ export interface UserProfileResponse {
premium_since?: Date;
}
-router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => {
- if (req.params.id === "@me") req.params.id = req.user_id;
+router.get(
+ "/",
+ route({ test: { response: { body: "UserProfileResponse" } } }),
+ async (req: Request, res: Response) => {
+ if (req.params.id === "@me") req.params.id = req.user_id;
- const { guild_id, with_mutual_guilds } = req.query;
+ const { guild_id, with_mutual_guilds } = req.query;
- const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] });
+ const user = await User.getPublicUser(req.params.id, {
+ relations: ["connected_accounts"],
+ });
- var mutual_guilds: object[] = [];
- var premium_guild_since;
+ var mutual_guilds: object[] = [];
+ var premium_guild_since;
- if (with_mutual_guilds == "true") {
- const requested_member = await Member.find({ where: { id: req.params.id } });
- const self_member = await Member.find({ where: { id: req.user_id } });
+ if (with_mutual_guilds == "true") {
+ const requested_member = await Member.find({
+ where: { id: req.params.id },
+ });
+ const self_member = await Member.find({
+ where: { id: req.user_id },
+ });
- for (const rmem of requested_member) {
- if (rmem.premium_since) {
- if (premium_guild_since) {
- if (premium_guild_since > rmem.premium_since) {
+ for (const rmem of requested_member) {
+ if (rmem.premium_since) {
+ if (premium_guild_since) {
+ if (premium_guild_since > rmem.premium_since) {
+ premium_guild_since = rmem.premium_since;
+ }
+ } else {
premium_guild_since = rmem.premium_since;
}
- } else {
- premium_guild_since = rmem.premium_since;
}
- }
- for (const smem of self_member) {
- if (smem.guild_id === rmem.guild_id) {
- mutual_guilds.push({ id: rmem.guild_id, nick: rmem.nick });
+ for (const smem of self_member) {
+ if (smem.guild_id === rmem.guild_id) {
+ mutual_guilds.push({
+ id: rmem.guild_id,
+ nick: rmem.nick,
+ });
+ }
}
}
}
- }
- const guild_member = guild_id && typeof guild_id == "string"
- ? await Member.findOneOrFail({ where: { id: req.params.id, guild_id: guild_id }, relations: ["roles"] })
- : undefined;
+ const guild_member =
+ guild_id && typeof guild_id == "string"
+ ? await Member.findOneOrFail({
+ where: { id: req.params.id, guild_id: guild_id },
+ relations: ["roles"],
+ })
+ : undefined;
- // TODO: make proper DTO's in util?
+ // TODO: make proper DTO's in util?
- const userDto = {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- public_flags: user.public_flags,
- avatar: user.avatar,
- accent_color: user.accent_color,
- banner: user.banner,
- bio: req.user_bot ? null : user.bio,
- bot: user.bot
- };
+ const userDto = {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ public_flags: user.public_flags,
+ avatar: user.avatar,
+ accent_color: user.accent_color,
+ banner: user.banner,
+ bio: req.user_bot ? null : user.bio,
+ bot: user.bot,
+ };
- const guildMemberDto = guild_member ? {
- avatar: user.avatar, // TODO
- banner: user.banner, // TODO
- bio: req.user_bot ? null : user.bio, // TODO
- communication_disabled_until: null, // TODO
- deaf: guild_member.deaf,
- flags: user.flags,
- is_pending: guild_member.pending,
- pending: guild_member.pending, // why is this here twice, discord?
- joined_at: guild_member.joined_at,
- mute: guild_member.mute,
- nick: guild_member.nick,
- premium_since: guild_member.premium_since,
- roles: guild_member.roles.map(x => x.id).filter(id => id != guild_id),
- user: userDto
- } : undefined;
+ const guildMemberDto = guild_member
+ ? {
+ avatar: user.avatar, // TODO
+ banner: user.banner, // TODO
+ bio: req.user_bot ? null : user.bio, // TODO
+ communication_disabled_until: null, // TODO
+ deaf: guild_member.deaf,
+ flags: user.flags,
+ is_pending: guild_member.pending,
+ pending: guild_member.pending, // why is this here twice, discord?
+ joined_at: guild_member.joined_at,
+ mute: guild_member.mute,
+ nick: guild_member.nick,
+ premium_since: guild_member.premium_since,
+ roles: guild_member.roles
+ .map((x) => x.id)
+ .filter((id) => id != guild_id),
+ user: userDto,
+ }
+ : undefined;
- res.json({
- connected_accounts: user.connected_accounts,
- premium_guild_since: premium_guild_since, // TODO
- premium_since: user.premium_since, // TODO
- mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
- user: userDto,
- guild_member: guildMemberDto,
- });
-});
+ res.json({
+ connected_accounts: user.connected_accounts,
+ premium_guild_since: premium_guild_since, // TODO
+ premium_since: user.premium_since, // TODO
+ mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
+ user: userDto,
+ guild_member: guildMemberDto,
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/users/#id/relationships.ts b/src/api/routes/users/#id/relationships.ts
index de7cb9d3..c6480567 100644
--- a/src/api/routes/users/#id/relationships.ts
+++ b/src/api/routes/users/#id/relationships.ts
@@ -6,36 +6,49 @@ const router: Router = Router();
export interface UserRelationsResponse {
object: {
- id?: string,
- username?: string,
- avatar?: string,
- discriminator?: string,
- public_flags?: number
- }
+ id?: string;
+ username?: string;
+ avatar?: string;
+ discriminator?: string;
+ public_flags?: number;
+ };
}
+router.get(
+ "/",
+ route({ test: { response: { body: "UserRelationsResponse" } } }),
+ async (req: Request, res: Response) => {
+ var mutual_relations: object[] = [];
+ const requested_relations = await User.findOneOrFail({
+ where: { id: req.params.id },
+ relations: ["relationships"],
+ });
+ const self_relations = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships"],
+ });
-router.get("/", route({ test: { response: { body: "UserRelationsResponse" } } }), async (req: Request, res: Response) => {
- var mutual_relations: object[] = [];
- const requested_relations = await User.findOneOrFail({
- where: { id: req.params.id },
- relations: ["relationships"]
- });
- const self_relations = await User.findOneOrFail({
- where: { id: req.user_id },
- relations: ["relationships"]
- });
-
- for(const rmem of requested_relations.relationships) {
- for(const smem of self_relations.relationships)
- if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) {
- var relation_user = await User.getPublicUser(rmem.to_id)
+ for (const rmem of requested_relations.relationships) {
+ for (const smem of self_relations.relationships)
+ if (
+ rmem.to_id === smem.to_id &&
+ rmem.type === 1 &&
+ rmem.to_id !== req.user_id
+ ) {
+ var relation_user = await User.getPublicUser(rmem.to_id);
- mutual_relations.push({id: relation_user.id, username: relation_user.username, avatar: relation_user.avatar, discriminator: relation_user.discriminator, public_flags: relation_user.public_flags})
+ mutual_relations.push({
+ id: relation_user.id,
+ username: relation_user.username,
+ avatar: relation_user.avatar,
+ discriminator: relation_user.discriminator,
+ public_flags: relation_user.public_flags,
+ });
+ }
}
- }
- res.json(mutual_relations)
-});
+ res.json(mutual_relations);
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts
index ad483529..237be102 100644
--- a/src/api/routes/users/@me/channels.ts
+++ b/src/api/routes/users/@me/channels.ts
@@ -1,5 +1,10 @@
import { Request, Response, Router } from "express";
-import { Recipient, DmChannelDTO, Channel, DmChannelCreateSchema } from "@fosscord/util";
+import {
+ Recipient,
+ DmChannelDTO,
+ Channel,
+ DmChannelCreateSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
@@ -7,14 +12,28 @@ const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const recipients = await Recipient.find({
where: { user_id: req.user_id, closed: false },
- relations: ["channel", "channel.recipients"]
+ relations: ["channel", "channel.recipients"],
});
- res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id]))));
+ res.json(
+ await Promise.all(
+ recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])),
+ ),
+ );
});
-router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => {
- const body = req.body as DmChannelCreateSchema;
- res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
-});
+router.post(
+ "/",
+ route({ body: "DmChannelCreateSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as DmChannelCreateSchema;
+ res.json(
+ await Channel.createDMChannel(
+ body.recipients,
+ req.user_id,
+ body.name,
+ ),
+ );
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts
index c24c3f1e..a9f8167c 100644
--- a/src/api/routes/users/@me/delete.ts
+++ b/src/api/routes/users/@me/delete.ts
@@ -7,7 +7,10 @@ import { HTTPError } from "lambert-server";
const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); //User object
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ }); //User object
let correctpass = true;
if (user.data.hash) {
@@ -21,7 +24,10 @@ router.post("/", route({}), async (req: Request, res: Response) => {
// TODO: decrement guild member count
if (correctpass) {
- await Promise.all([User.delete({ id: req.user_id }), Member.delete({ id: req.user_id })]);
+ await Promise.all([
+ User.delete({ id: req.user_id }),
+ Member.delete({ id: req.user_id }),
+ ]);
res.sendStatus(204);
} else {
diff --git a/src/api/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts
index 4aff3774..313a888f 100644
--- a/src/api/routes/users/@me/disable.ts
+++ b/src/api/routes/users/@me/disable.ts
@@ -6,7 +6,10 @@ import bcrypt from "bcrypt";
const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); //User object
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ }); //User object
let correctpass = true;
if (user.data.hash) {
@@ -19,7 +22,10 @@ router.post("/", route({}), async (req: Request, res: Response) => {
res.sendStatus(204);
} else {
- res.status(400).json({ message: "Password does not match", code: 50018 });
+ res.status(400).json({
+ message: "Password does not match",
+ code: 50018,
+ });
}
});
diff --git a/src/api/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts
index 3114984e..a2834b89 100644
--- a/src/api/routes/users/@me/email-settings.ts
+++ b/src/api/routes/users/@me/email-settings.ts
@@ -11,9 +11,9 @@ router.get("/", route({}), (req: Request, res: Response) => {
communication: true,
tips: false,
updates_and_announcements: false,
- recommendations_and_events: false
+ recommendations_and_events: false,
},
- initialized: false
+ initialized: false,
}).status(200);
});
diff --git a/src/api/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts
index 754a240e..e12bf258 100644
--- a/src/api/routes/users/@me/guilds.ts
+++ b/src/api/routes/users/@me/guilds.ts
@@ -1,12 +1,23 @@
import { Router, Request, Response } from "express";
-import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent, Config } from "@fosscord/util";
+import {
+ Guild,
+ Member,
+ User,
+ GuildDeleteEvent,
+ GuildMemberRemoveEvent,
+ emitEvent,
+ Config,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } });
+ const members = await Member.find({
+ relations: ["guild"],
+ where: { id: req.user_id },
+ });
let guild = members.map((x) => x.guild);
@@ -21,11 +32,19 @@ router.get("/", route({}), async (req: Request, res: Response) => {
router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
const { autoJoin } = Config.get().guild;
const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] });
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: ["owner_id"],
+ });
if (!guild) throw new HTTPError("Guild doesn't exist", 404);
- if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400);
- if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) {
+ if (guild.owner_id === req.user_id)
+ throw new HTTPError("You can't leave your own guild", 400);
+ if (
+ autoJoin.enabled &&
+ autoJoin.guilds.includes(guild_id) &&
+ !autoJoin.canLeave
+ ) {
throw new HTTPError("You can't leave instance auto join guilds", 400);
}
@@ -34,10 +53,10 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
emitEvent({
event: "GUILD_DELETE",
data: {
- id: guild_id
+ id: guild_id,
},
- user_id: req.user_id
- } as GuildDeleteEvent)
+ user_id: req.user_id,
+ } as GuildDeleteEvent),
]);
const user = await User.getPublicUser(req.user_id);
@@ -46,9 +65,9 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
event: "GUILD_MEMBER_REMOVE",
data: {
guild_id: guild_id,
- user: user
+ user: user,
},
- guild_id: guild_id
+ guild_id: guild_id,
} as GuildMemberRemoveEvent);
return res.sendStatus(204);
diff --git a/src/api/routes/users/@me/guilds/#guild_id/settings.ts b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
index f09be25b..4b806cfb 100644
--- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts
+++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
@@ -1,39 +1,51 @@
import { Router, Response, Request } from "express";
-import { Channel, ChannelOverride, Member, UserGuildSettings } from "@fosscord/util";
+import {
+ Channel,
+ ChannelOverride,
+ Member,
+ UserGuildSettings,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router = Router();
// This sucks. I would use a DeepPartial, my own or typeorms, but they both generate inncorect schema
-export interface UserGuildSettingsSchema extends Partial<Omit<UserGuildSettings, 'channel_overrides'>> {
+export interface UserGuildSettingsSchema
+ extends Partial<Omit<UserGuildSettings, "channel_overrides">> {
channel_overrides: {
[channel_id: string]: Partial<ChannelOverride>;
- },
+ };
}
// GET doesn't exist on discord.com
router.get("/", route({}), async (req: Request, res: Response) => {
const user = await Member.findOneOrFail({
where: { id: req.user_id, guild_id: req.params.guild_id },
- select: ["settings"]
+ select: ["settings"],
});
return res.json(user.settings);
});
-router.patch("/", route({ body: "UserGuildSettingsSchema" }), async (req: Request, res: Response) => {
- const body = req.body as UserGuildSettings;
+router.patch(
+ "/",
+ route({ body: "UserGuildSettingsSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserGuildSettings;
- if (body.channel_overrides) {
- for (var channel in body.channel_overrides) {
- Channel.findOneOrFail({ where: { id: channel } });
+ if (body.channel_overrides) {
+ for (var channel in body.channel_overrides) {
+ Channel.findOneOrFail({ where: { id: channel } });
+ }
}
- }
- const user = await Member.findOneOrFail({ where: { id: req.user_id, guild_id: req.params.guild_id } });
- user.settings = { ...user.settings, ...body };
- await user.save();
+ const user = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id: req.params.guild_id },
+ });
+ user.settings = { ...user.settings, ...body };
+ await user.save();
- res.json(user.settings);
-});
+ res.json(user.settings);
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index e849b72a..5eba4665 100644
--- a/src/api/routes/users/@me/index.ts
+++ b/src/api/routes/users/@me/index.ts
@@ -1,5 +1,15 @@
import { Router, Request, Response } from "express";
-import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile, FieldErrors, adjustEmail, Config, UserModifySchema } from "@fosscord/util";
+import {
+ User,
+ PrivateUserProjection,
+ emitEvent,
+ UserUpdateEvent,
+ handleFile,
+ FieldErrors,
+ adjustEmail,
+ Config,
+ UserModifySchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import bcrypt from "bcrypt";
import { HTTPError } from "lambert-server";
@@ -7,79 +17,134 @@ import { HTTPError } from "lambert-server";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } }));
+ res.json(
+ await User.findOne({
+ select: PrivateUserProjection,
+ where: { id: req.user_id },
+ }),
+ );
});
-router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: Response) => {
- const body = req.body as UserModifySchema;
-
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
-
- if (user.email == "demo@maddy.k.vu") throw new HTTPError("Demo user, sorry", 400);
+router.patch(
+ "/",
+ route({ body: "UserModifySchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserModifySchema;
+
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: [...PrivateUserProjection, "data"],
+ });
+
+ if (user.email == "demo@maddy.k.vu")
+ throw new HTTPError("Demo user, sorry", 400);
+
+ if (body.avatar)
+ body.avatar = await handleFile(
+ `/avatars/${req.user_id}`,
+ body.avatar as string,
+ );
+ if (body.banner)
+ body.banner = await handleFile(
+ `/banners/${req.user_id}`,
+ body.banner as string,
+ );
+
+ if (body.password) {
+ if (user.data?.hash) {
+ const same_password = await bcrypt.compare(
+ body.password,
+ user.data.hash || "",
+ );
+ if (!same_password) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
+ } else {
+ user.data.hash = await bcrypt.hash(body.password, 12);
+ }
+ }
- if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
- if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
+ if (body.email) {
+ body.email = adjustEmail(body.email);
+ if (!body.email && Config.get().register.email.required)
+ throw FieldErrors({
+ email: {
+ message: req.t("auth:register.EMAIL_INVALID"),
+ code: "EMAIL_INVALID",
+ },
+ });
+ if (!body.password)
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:register.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- if (body.password) {
- if (user.data?.hash) {
- const same_password = await bcrypt.compare(body.password, user.data.hash || "");
- if (!same_password) {
- throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
+ if (body.new_password) {
+ if (!body.password && !user.email) {
+ throw FieldErrors({
+ password: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
}
- } else {
- user.data.hash = await bcrypt.hash(body.password, 12);
+ user.data.hash = await bcrypt.hash(body.new_password, 12);
}
- }
-
- if (body.email) {
- body.email = adjustEmail(body.email);
- if (!body.email && Config.get().register.email.required)
- throw FieldErrors({ email: { message: req.t("auth:register.EMAIL_INVALID"), code: "EMAIL_INVALID" } });
- if (!body.password)
- throw FieldErrors({ password: { message: req.t("auth:register.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
- }
-
- if (body.new_password) {
- if (!body.password && !user.email) {
- throw FieldErrors({
- password: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
- });
- }
- user.data.hash = await bcrypt.hash(body.new_password, 12);
- }
-
- if (body.username) {
- var check_username = body?.username?.replace(/\s/g, '');
- if (!check_username) {
- throw FieldErrors({
- username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
- });
+
+ if (body.username) {
+ var check_username = body?.username?.replace(/\s/g, "");
+ if (!check_username) {
+ throw FieldErrors({
+ username: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
}
- }
- if (body.discriminator) {
- if (await User.findOne({ where: { discriminator: body.discriminator, username: body.username || user.username } })) {
- throw FieldErrors({
- discriminator: { code: "INVALID_DISCRIMINATOR", message: "This discriminator is already in use." }
- });
+ if (body.discriminator) {
+ if (
+ await User.findOne({
+ where: {
+ discriminator: body.discriminator,
+ username: body.username || user.username,
+ },
+ })
+ ) {
+ throw FieldErrors({
+ discriminator: {
+ code: "INVALID_DISCRIMINATOR",
+ message: "This discriminator is already in use.",
+ },
+ });
+ }
}
- }
- user.assign(body);
- await user.save();
+ user.assign(body);
+ await user.save();
- // @ts-ignore
- delete user.data;
+ // @ts-ignore
+ delete user.data;
- // TODO: send update member list event in gateway
- await emitEvent({
- event: "USER_UPDATE",
- user_id: req.user_id,
- data: user
- } as UserUpdateEvent);
+ // TODO: send update member list event in gateway
+ await emitEvent({
+ event: "USER_UPDATE",
+ user_id: req.user_id,
+ data: user,
+ } as UserUpdateEvent);
- res.json(user);
-});
+ res.json(user);
+ },
+);
export default router;
// {"message": "Invalid two-factor code", "code": 60008}
diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts
index 071c71fa..3411605b 100644
--- a/src/api/routes/users/@me/mfa/codes-verification.ts
+++ b/src/api/routes/users/@me/mfa/codes-verification.ts
@@ -1,41 +1,49 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
-import { BackupCode, generateMfaBackupCodes, User, CodesVerificationSchema } from "@fosscord/util";
+import {
+ BackupCode,
+ generateMfaBackupCodes,
+ User,
+ CodesVerificationSchema,
+} from "@fosscord/util";
const router = Router();
-router.post("/", route({ body: "CodesVerificationSchema" }), async (req: Request, res: Response) => {
- const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
-
- // TODO: We don't have email/etc etc, so can't send a verification code.
- // Once that's done, this route can verify `key`
-
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
-
- var codes: BackupCode[];
- if (regenerate) {
- await BackupCode.update(
- { user: { id: req.user_id } },
- { expired: true }
- );
-
- codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(codes.map(x => x.save()));
- }
- else {
- codes = await BackupCode.find({
- where: {
- user: {
- id: req.user_id,
+router.post(
+ "/",
+ route({ body: "CodesVerificationSchema" }),
+ async (req: Request, res: Response) => {
+ const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
+
+ // TODO: We don't have email/etc etc, so can't send a verification code.
+ // Once that's done, this route can verify `key`
+
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+
+ var codes: BackupCode[];
+ if (regenerate) {
+ await BackupCode.update(
+ { user: { id: req.user_id } },
+ { expired: true },
+ );
+
+ codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(codes.map((x) => x.save()));
+ } else {
+ codes = await BackupCode.find({
+ where: {
+ user: {
+ id: req.user_id,
+ },
+ expired: false,
},
- expired: false,
- }
- });
- }
+ });
+ }
- return res.json({
- backup_codes: codes.map(x => ({ ...x, expired: undefined })),
- });
-});
+ return res.json({
+ backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts
index 58466b9c..33053028 100644
--- a/src/api/routes/users/@me/mfa/codes.ts
+++ b/src/api/routes/users/@me/mfa/codes.ts
@@ -1,45 +1,62 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
-import { BackupCode, FieldErrors, generateMfaBackupCodes, User, MfaCodesSchema } from "@fosscord/util";
+import {
+ BackupCode,
+ FieldErrors,
+ generateMfaBackupCodes,
+ User,
+ MfaCodesSchema,
+} from "@fosscord/util";
import bcrypt from "bcrypt";
const router = Router();
// TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients
-router.post("/", route({ body: "MfaCodesSchema" }), async (req: Request, res: Response) => {
- const { password, regenerate } = req.body as MfaCodesSchema;
-
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] });
-
- if (!await bcrypt.compare(password, user.data.hash || "")) {
- throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
- }
-
- var codes: BackupCode[];
- if (regenerate) {
- await BackupCode.update(
- { user: { id: req.user_id } },
- { expired: true }
- );
-
- codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(codes.map(x => x.save()));
- }
- else {
- codes = await BackupCode.find({
- where: {
- user: {
- id: req.user_id,
- },
- expired: false,
- }
+router.post(
+ "/",
+ route({ body: "MfaCodesSchema" }),
+ async (req: Request, res: Response) => {
+ const { password, regenerate } = req.body as MfaCodesSchema;
+
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
});
- }
- return res.json({
- backup_codes: codes.map(x => ({ ...x, expired: undefined })),
- });
-});
+ if (!(await bcrypt.compare(password, user.data.hash || ""))) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
+
+ var codes: BackupCode[];
+ if (regenerate) {
+ await BackupCode.update(
+ { user: { id: req.user_id } },
+ { expired: true },
+ );
+
+ codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(codes.map((x) => x.save()));
+ } else {
+ codes = await BackupCode.find({
+ where: {
+ user: {
+ id: req.user_id,
+ },
+ expired: false,
+ },
+ });
+ }
+
+ return res.json({
+ backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts
index 2fe9355c..7916e598 100644
--- a/src/api/routes/users/@me/mfa/totp/disable.ts
+++ b/src/api/routes/users/@me/mfa/totp/disable.ts
@@ -1,41 +1,56 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
-import { verifyToken } from 'node-2fa';
+import { verifyToken } from "node-2fa";
import { HTTPError } from "lambert-server";
-import { User, generateToken, BackupCode, TotpDisableSchema } from "@fosscord/util";
+import {
+ User,
+ generateToken,
+ BackupCode,
+ TotpDisableSchema,
+} from "@fosscord/util";
const router = Router();
-router.post("/", route({ body: "TotpDisableSchema" }), async (req: Request, res: Response) => {
- const body = req.body as TotpDisableSchema;
-
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["totp_secret"] });
-
- const backup = await BackupCode.findOne({ where: { code: body.code } });
- if (!backup) {
- const ret = verifyToken(user.totp_secret!, body.code);
- if (!ret || ret.delta != 0)
- throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- }
-
- await User.update(
- { id: req.user_id },
- {
- mfa_enabled: false,
- totp_secret: "",
- },
- );
-
- await BackupCode.update(
- { user: { id: req.user_id } },
- {
- expired: true,
+router.post(
+ "/",
+ route({ body: "TotpDisableSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as TotpDisableSchema;
+
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["totp_secret"],
+ });
+
+ const backup = await BackupCode.findOne({ where: { code: body.code } });
+ if (!backup) {
+ const ret = verifyToken(user.totp_secret!, body.code);
+ if (!ret || ret.delta != 0)
+ throw new HTTPError(
+ req.t("auth:login.INVALID_TOTP_CODE"),
+ 60008,
+ );
}
- );
- return res.json({
- token: await generateToken(user.id),
- });
-});
-
-export default router;
\ No newline at end of file
+ await User.update(
+ { id: req.user_id },
+ {
+ mfa_enabled: false,
+ totp_secret: "",
+ },
+ );
+
+ await BackupCode.update(
+ { user: { id: req.user_id } },
+ {
+ expired: true,
+ },
+ );
+
+ return res.json({
+ token: await generateToken(user.id),
+ });
+ },
+);
+
+export default router;
diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts
index adafe180..75c64425 100644
--- a/src/api/routes/users/@me/mfa/totp/enable.ts
+++ b/src/api/routes/users/@me/mfa/totp/enable.ts
@@ -1,46 +1,62 @@
import { Router, Request, Response } from "express";
-import { User, generateToken, generateMfaBackupCodes, TotpEnableSchema } from "@fosscord/util";
+import {
+ User,
+ generateToken,
+ generateMfaBackupCodes,
+ TotpEnableSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import bcrypt from "bcrypt";
import { HTTPError } from "lambert-server";
-import { verifyToken } from 'node-2fa';
+import { verifyToken } from "node-2fa";
const router = Router();
-router.post("/", route({ body: "TotpEnableSchema" }), async (req: Request, res: Response) => {
- const body = req.body as TotpEnableSchema;
-
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data", "email"] });
-
- if (user.email == "demo@maddy.k.vu") throw new HTTPError("Demo user, sorry", 400);
-
- // TODO: Are guests allowed to enable 2fa?
- if (user.data.hash) {
- if (!await bcrypt.compare(body.password, user.data.hash)) {
- throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+router.post(
+ "/",
+ route({ body: "TotpEnableSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as TotpEnableSchema;
+
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data", "email"],
+ });
+
+ if (user.email == "demo@maddy.k.vu")
+ throw new HTTPError("Demo user, sorry", 400);
+
+ // TODO: Are guests allowed to enable 2fa?
+ if (user.data.hash) {
+ if (!(await bcrypt.compare(body.password, user.data.hash))) {
+ throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+ }
}
- }
-
- if (!body.secret)
- throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
-
- if (!body.code)
- throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
-
- if (verifyToken(body.secret, body.code)?.delta != 0)
- throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
-
- let backup_codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(backup_codes.map(x => x.save()));
- await User.update(
- { id: req.user_id },
- { mfa_enabled: true, totp_secret: body.secret }
- );
-
- res.send({
- token: await generateToken(user.id),
- backup_codes: backup_codes.map(x => ({ ...x, expired: undefined })),
- });
-});
-export default router;
\ No newline at end of file
+ if (!body.secret)
+ throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
+
+ if (!body.code)
+ throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+
+ if (verifyToken(body.secret, body.code)?.delta != 0)
+ throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+
+ let backup_codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(backup_codes.map((x) => x.save()));
+ await User.update(
+ { id: req.user_id },
+ { mfa_enabled: true, totp_secret: body.secret },
+ );
+
+ res.send({
+ token: await generateToken(user.id),
+ backup_codes: backup_codes.map((x) => ({
+ ...x,
+ expired: undefined,
+ })),
+ });
+ },
+);
+
+export default router;
diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts
index f938f088..e54eb897 100644
--- a/src/api/routes/users/@me/notes.ts
+++ b/src/api/routes/users/@me/notes.ts
@@ -11,7 +11,7 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
where: {
owner: { id: req.user_id },
target: { id: id },
- }
+ },
});
return res.json({
@@ -24,32 +24,40 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
router.put("/:id", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
const owner = await User.findOneOrFail({ where: { id: req.user_id } });
- const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw
+ const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw
const { note } = req.body;
if (note && note.length) {
// upsert a note
- if (await Note.findOne({ where: { owner: { id: owner.id }, target: { id: target.id } } })) {
+ if (
+ await Note.findOne({
+ where: { owner: { id: owner.id }, target: { id: target.id } },
+ })
+ ) {
Note.update(
{ owner: { id: owner.id }, target: { id: target.id } },
- { owner, target, content: note }
+ { owner, target, content: note },
);
+ } else {
+ Note.insert({
+ id: Snowflake.generate(),
+ owner,
+ target,
+ content: note,
+ });
}
- else {
- Note.insert(
- { id: Snowflake.generate(), owner, target, content: note }
- );
- }
- }
- else {
- await Note.delete({ owner: { id: owner.id }, target: { id: target.id } });
+ } else {
+ await Note.delete({
+ owner: { id: owner.id },
+ target: { id: target.id },
+ });
}
await emitEvent({
event: "USER_NOTE_UPDATE",
data: {
note: note,
- id: target.id
+ id: target.id,
},
user_id: owner.id,
});
diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts
index cd33704d..3eec704b 100644
--- a/src/api/routes/users/@me/relationships.ts
+++ b/src/api/routes/users/@me/relationships.ts
@@ -6,7 +6,7 @@ import {
RelationshipRemoveEvent,
emitEvent,
Relationship,
- Config
+ Config,
} from "@fosscord/util";
import { Router, Response, Request } from "express";
import { HTTPError } from "lambert-server";
@@ -15,13 +15,16 @@ import { route } from "@fosscord/api";
const router = Router();
-const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection];
+const userProjection: (keyof User)[] = [
+ "relationships",
+ ...PublicUserProjection,
+];
router.get("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
relations: ["relationships", "relationships.to"],
- select: ["id", "relationships"]
+ select: ["id", "relationships"],
});
//TODO DTO
@@ -30,49 +33,76 @@ router.get("/", route({}), async (req: Request, res: Response) => {
id: r.to.id,
type: r.type,
nickname: null,
- user: r.to.toPublicUser()
+ user: r.to.toPublicUser(),
};
});
return res.json(related_users);
});
-router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => {
- return await updateRelationship(
- req,
- res,
- await User.findOneOrFail({ where: { id: req.params.id }, relations: ["relationships", "relationships.to"], select: userProjection }),
- req.body.type ?? RelationshipType.friends
- );
-});
+router.put(
+ "/:id",
+ route({ body: "RelationshipPutSchema" }),
+ async (req: Request, res: Response) => {
+ return await updateRelationship(
+ req,
+ res,
+ await User.findOneOrFail({
+ where: { id: req.params.id },
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
+ }),
+ req.body.type ?? RelationshipType.friends,
+ );
+ },
+);
-router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, res: Response) => {
- return await updateRelationship(
- req,
- res,
- await User.findOneOrFail({
- relations: ["relationships", "relationships.to"],
- select: userProjection,
- where: {
- discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes
- username: req.body.username
- }
- }),
- req.body.type
- );
-});
+router.post(
+ "/",
+ route({ body: "RelationshipPostSchema" }),
+ async (req: Request, res: Response) => {
+ return await updateRelationship(
+ req,
+ res,
+ await User.findOneOrFail({
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
+ where: {
+ discriminator: String(req.body.discriminator).padStart(
+ 4,
+ "0",
+ ), //Discord send the discriminator as integer, we need to add leading zeroes
+ username: req.body.username,
+ },
+ }),
+ req.body.type,
+ );
+ },
+);
router.delete("/:id", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
- if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
+ if (id === req.user_id)
+ throw new HTTPError("You can't remove yourself as a friend");
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: userProjection, relations: ["relationships"] });
- const friend = await User.findOneOrFail({ where: { id: id }, select: userProjection, relations: ["relationships"] });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
+ const friend = await User.findOneOrFail({
+ where: { id: id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
const relationship = user.relationships.find((x) => x.to_id === id);
- const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
+ const friendRequest = friend.relationships.find(
+ (x) => x.to_id === req.user_id,
+ );
- if (!relationship) throw new HTTPError("You are not friends with the user", 404);
+ if (!relationship)
+ throw new HTTPError("You are not friends with the user", 404);
if (relationship?.type === RelationshipType.blocked) {
// unblock user
@@ -81,8 +111,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
emitEvent({
event: "RELATIONSHIP_REMOVE",
user_id: req.user_id,
- data: relationship.toPublicRelationship()
- } as RelationshipRemoveEvent)
+ data: relationship.toPublicRelationship(),
+ } as RelationshipRemoveEvent),
]);
return res.sendStatus(204);
}
@@ -92,8 +122,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
await emitEvent({
event: "RELATIONSHIP_REMOVE",
data: friendRequest.toPublicRelationship(),
- user_id: id
- } as RelationshipRemoveEvent)
+ user_id: id,
+ } as RelationshipRemoveEvent),
]);
}
@@ -102,8 +132,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
emitEvent({
event: "RELATIONSHIP_REMOVE",
data: relationship.toPublicRelationship(),
- user_id: req.user_id
- } as RelationshipRemoveEvent)
+ user_id: req.user_id,
+ } as RelationshipRemoveEvent),
]);
return res.sendStatus(204);
@@ -111,26 +141,40 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
export default router;
-async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) {
+async function updateRelationship(
+ req: Request,
+ res: Response,
+ friend: User,
+ type: RelationshipType,
+) {
const id = friend.id;
- if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend");
+ if (id === req.user_id)
+ throw new HTTPError("You can't add yourself as a friend");
const user = await User.findOneOrFail({
where: { id: req.user_id },
- relations: ["relationships", "relationships.to"], select: userProjection
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
});
var relationship = user.relationships.find((x) => x.to_id === id);
- const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
+ const friendRequest = friend.relationships.find(
+ (x) => x.to_id === req.user_id,
+ );
// TODO: you can add infinitely many blocked users (should this be prevented?)
if (type === RelationshipType.blocked) {
if (relationship) {
- if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user");
+ if (relationship.type === RelationshipType.blocked)
+ throw new HTTPError("You already blocked the user");
relationship.type = RelationshipType.blocked;
await relationship.save();
} else {
- relationship = await Relationship.create({ to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save();
+ relationship = await Relationship.create({
+ to_id: id,
+ type: RelationshipType.blocked,
+ from_id: req.user_id,
+ }).save();
}
if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
@@ -139,43 +183,56 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
emitEvent({
event: "RELATIONSHIP_REMOVE",
data: friendRequest.toPublicRelationship(),
- user_id: id
- } as RelationshipRemoveEvent)
+ user_id: id,
+ } as RelationshipRemoveEvent),
]);
}
await emitEvent({
event: "RELATIONSHIP_ADD",
data: relationship.toPublicRelationship(),
- user_id: req.user_id
+ user_id: req.user_id,
} as RelationshipAddEvent);
return res.sendStatus(204);
}
const { maxFriends } = Config.get().limits.user;
- if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
+ if (user.relationships.length >= maxFriends)
+ throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
- var incoming_relationship = Relationship.create({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
+ var incoming_relationship = Relationship.create({
+ nickname: undefined,
+ type: RelationshipType.incoming,
+ to: user,
+ from: friend,
+ });
var outgoing_relationship = Relationship.create({
nickname: undefined,
type: RelationshipType.outgoing,
to: friend,
- from: user
+ from: user,
});
if (friendRequest) {
- if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
- if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
+ if (friendRequest.type === RelationshipType.blocked)
+ throw new HTTPError("The user blocked you");
+ if (friendRequest.type === RelationshipType.friends)
+ throw new HTTPError("You are already friends with the user");
// accept friend request
incoming_relationship = friendRequest;
incoming_relationship.type = RelationshipType.friends;
}
if (relationship) {
- if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request");
- if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request");
- if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
+ if (relationship.type === RelationshipType.outgoing)
+ throw new HTTPError("You already sent a friend request");
+ if (relationship.type === RelationshipType.blocked)
+ throw new HTTPError(
+ "Unblock the user before sending a friend request",
+ );
+ if (relationship.type === RelationshipType.friends)
+ throw new HTTPError("You are already friends with the user");
outgoing_relationship = relationship;
outgoing_relationship.type = RelationshipType.friends;
}
@@ -186,16 +243,16 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
emitEvent({
event: "RELATIONSHIP_ADD",
data: outgoing_relationship.toPublicRelationship(),
- user_id: req.user_id
+ user_id: req.user_id,
} as RelationshipAddEvent),
emitEvent({
event: "RELATIONSHIP_ADD",
data: {
...incoming_relationship.toPublicRelationship(),
- should_notify: true
+ should_notify: true,
},
- user_id: id
- } as RelationshipAddEvent)
+ user_id: id,
+ } as RelationshipAddEvent),
]);
return res.sendStatus(204);
diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts
index 9060baf7..30e5969c 100644
--- a/src/api/routes/users/@me/settings.ts
+++ b/src/api/routes/users/@me/settings.ts
@@ -4,25 +4,31 @@ import { route } from "@fosscord/api";
const router = Router();
-export interface UserSettingsSchema extends Partial<UserSettings> { }
+export interface UserSettingsSchema extends Partial<UserSettings> {}
router.get("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
- select: ["settings"]
+ select: ["settings"],
});
return res.json(user.settings);
});
-router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => {
- const body = req.body as UserSettings;
- if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale
+router.patch(
+ "/",
+ route({ body: "UserSettingsSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserSettings;
+ if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale
- const user = await User.findOneOrFail({ where: { id: req.user_id, bot: false } });
- user.settings = { ...user.settings, ...body };
- await user.save();
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id, bot: false },
+ });
+ user.settings = { ...user.settings, ...body };
+ await user.save();
- res.json(user.settings);
-});
+ res.json(user.settings);
+ },
+);
export default router;
diff --git a/src/api/start.ts b/src/api/start.ts
index ccb4d108..fa120e59 100644
--- a/src/api/start.ts
+++ b/src/api/start.ts
@@ -11,7 +11,7 @@ var cores = 1;
try {
cores = Number(process.env.THREADS) || os.cpus().length;
} catch {
- console.log("[API] Failed to get thread count! Using 1...")
+ console.log("[API] Failed to get thread count! Using 1...");
}
if (cluster.isMaster && process.env.NODE_ENV == "production") {
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 0d29c2e6..d44b368f 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -32,24 +32,32 @@ const allow_empty = false;
// TODO: check webhook, application, system author, stickers
// TODO: embed gifs/videos/images
-const LINK_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
+const LINK_REGEX =
+ /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
const DEFAULT_FETCH_OPTIONS: any = {
redirect: "follow",
follow: 1,
headers: {
- "user-agent": "Mozilla/5.0 (compatible; Fosscord/1.0; +https://github.com/fosscord/fosscord)"
+ "user-agent":
+ "Mozilla/5.0 (compatible; Fosscord/1.0; +https://github.com/fosscord/fosscord)",
},
// size: 1024 * 1024 * 5, // grabbed from config later
compress: true,
- method: "GET"
+ method: "GET",
};
export async function handleMessage(opts: MessageOptions): Promise<Message> {
- const channel = await Channel.findOneOrFail({ where: { id: opts.channel_id }, relations: ["recipients"] });
- if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
+ const channel = await Channel.findOneOrFail({
+ where: { id: opts.channel_id },
+ relations: ["recipients"],
+ });
+ if (!channel || !opts.channel_id)
+ throw new HTTPError("Channel not found", 404);
- const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined;
+ const stickers = opts.sticker_ids
+ ? await Sticker.find({ where: { id: In(opts.sticker_ids) } })
+ : undefined;
const message = Message.create({
...opts,
id: Snowflake.generate(),
@@ -58,11 +66,14 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
channel_id: opts.channel_id,
attachments: opts.attachments || [],
embeds: opts.embeds || [],
- reactions: /*opts.reactions ||*/[],
+ reactions: /*opts.reactions ||*/ [],
type: opts.type ?? 0,
});
- if (message.content && message.content.length > Config.get().limits.message.maxCharacters) {
+ if (
+ message.content &&
+ message.content.length > Config.get().limits.message.maxCharacters
+ ) {
throw new HTTPError("Content length over max character limit");
}
@@ -72,13 +83,21 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
rights.hasThrow("SEND_MESSAGES");
}
if (opts.application_id) {
- message.application = await Application.findOneOrFail({ where: { id: opts.application_id } });
+ message.application = await Application.findOneOrFail({
+ where: { id: opts.application_id },
+ });
}
if (opts.webhook_id) {
- message.webhook = await Webhook.findOneOrFail({ where: { id: opts.webhook_id } });
+ message.webhook = await Webhook.findOneOrFail({
+ where: { id: opts.webhook_id },
+ });
}
- const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id);
+ const permission = await getPermission(
+ opts.author_id,
+ channel.guild_id,
+ opts.channel_id,
+ );
permission.hasThrow("SEND_MESSAGES");
if (permission.cache.member) {
message.member = permission.cache.member;
@@ -89,10 +108,18 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
permission.hasThrow("READ_MESSAGE_HISTORY");
// code below has to be redone when we add custom message routing
if (message.guild_id !== null) {
- const guild = await Guild.findOneOrFail({ where: { id: channel.guild_id } });
+ const guild = await Guild.findOneOrFail({
+ where: { id: channel.guild_id },
+ });
if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
- if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
- if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
+ if (opts.message_reference.guild_id !== channel.guild_id)
+ throw new HTTPError(
+ "You can only reference messages from this guild",
+ );
+ if (opts.message_reference.channel_id !== opts.channel_id)
+ throw new HTTPError(
+ "You can only reference messages from this channel",
+ );
}
}
/** Q: should be checked if the referenced message exists? ANSWER: NO
@@ -102,7 +129,13 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
}
// TODO: stickers/activity
- if (!allow_empty && (!opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length)) {
+ if (
+ !allow_empty &&
+ !opts.content &&
+ !opts.embeds?.length &&
+ !opts.attachments?.length &&
+ !opts.sticker_ids?.length
+ ) {
throw new HTTPError("Empty messages are not allowed", 50006);
}
@@ -112,31 +145,42 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
var mention_user_ids = [] as string[];
var mention_everyone = false;
- if (content) { // TODO: explicit-only mentions
+ if (content) {
+ // TODO: explicit-only mentions
message.content = content.trim();
for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) {
- if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention);
+ if (!mention_channel_ids.includes(mention))
+ mention_channel_ids.push(mention);
}
for (const [_, mention] of content.matchAll(USER_MENTION)) {
- if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention);
+ if (!mention_user_ids.includes(mention))
+ mention_user_ids.push(mention);
}
await Promise.all(
- Array.from(content.matchAll(ROLE_MENTION)).map(async ([_, mention]) => {
- const role = await Role.findOneOrFail({ where: { id: mention, guild_id: channel.guild_id } });
- if (role.mentionable || permission.has("MANAGE_ROLES")) {
- mention_role_ids.push(mention);
- }
- })
+ Array.from(content.matchAll(ROLE_MENTION)).map(
+ async ([_, mention]) => {
+ const role = await Role.findOneOrFail({
+ where: { id: mention, guild_id: channel.guild_id },
+ });
+ if (role.mentionable || permission.has("MANAGE_ROLES")) {
+ mention_role_ids.push(mention);
+ }
+ },
+ ),
);
if (permission.has("MENTION_EVERYONE")) {
- mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION);
+ mention_everyone =
+ !!content.match(EVERYONE_MENTION) ||
+ !!content.match(HERE_MENTION);
}
}
- message.mention_channels = mention_channel_ids.map((x) => Channel.create({ id: x }));
+ message.mention_channels = mention_channel_ids.map((x) =>
+ Channel.create({ id: x }),
+ );
message.mention_roles = mention_role_ids.map((x) => Role.create({ id: x }));
message.mentions = mention_user_ids.map((x) => User.create({ id: x }));
message.mention_everyone = mention_everyone;
@@ -156,7 +200,8 @@ export async function postHandleMessage(message: Message) {
links = links.slice(0, 20) as RegExpMatchArray; // embed max 20 links — TODO: make this configurable with instance policies
- const { endpointPublic, resizeWidthMax, resizeHeightMax } = Config.get().cdn;
+ const { endpointPublic, resizeWidthMax, resizeHeightMax } =
+ Config.get().cdn;
for (const link of links) {
try {
@@ -176,45 +221,64 @@ export async function postHandleMessage(message: Message) {
},
image: {
// can't be bothered rn
- proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(link)}?width=500&height=400`,
+ proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(
+ link,
+ )}?width=500&height=400`,
url: link,
width: 500,
- height: 400
- }
+ height: 400,
+ },
};
data.embeds.push(embed);
- }
- else {
+ } else {
const text = await request.text();
const $ = cheerio.load(text);
const title = $('meta[property="og:title"]').attr("content");
const provider_name = $('meta[property="og:site_name"]').text();
- const author_name = $('meta[property="article:author"]').attr("content");
- const description = $('meta[property="og:description"]').attr("content") || $('meta[property="description"]').attr("content");
+ const author_name = $('meta[property="article:author"]').attr(
+ "content",
+ );
+ const description =
+ $('meta[property="og:description"]').attr("content") ||
+ $('meta[property="description"]').attr("content");
const image = $('meta[property="og:image"]').attr("content");
- const width = parseInt($('meta[property="og:image:width"]').attr("content") || "") || undefined;
- const height = parseInt($('meta[property="og:image:height"]').attr("content") || "") || undefined;
+ const width =
+ parseInt(
+ $('meta[property="og:image:width"]').attr("content") ||
+ "",
+ ) || undefined;
+ const height =
+ parseInt(
+ $('meta[property="og:image:height"]').attr("content") ||
+ "",
+ ) || undefined;
const url = $('meta[property="og:url"]').attr("content");
// TODO: color
embed = {
provider: {
url: link,
- name: provider_name
- }
+ name: provider_name,
+ },
};
const resizeWidth = Math.min(resizeWidthMax ?? 1, width ?? 100);
- const resizeHeight = Math.min(resizeHeightMax ?? 1, height ?? 100);
+ const resizeHeight = Math.min(
+ resizeHeightMax ?? 1,
+ height ?? 100,
+ );
if (author_name) embed.author = { name: author_name };
- if (image) embed.thumbnail = {
- proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(image)}?width=${resizeWidth}&height=${resizeHeight}`,
- url: image,
- width: width,
- height: height
- };
+ if (image)
+ embed.thumbnail = {
+ proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(
+ image,
+ )}?width=${resizeWidth}&height=${resizeHeight}`,
+ url: image,
+ width: width,
+ height: height,
+ };
if (title) embed.title = title;
if (url) embed.url = url;
if (description) embed.description = description;
@@ -227,18 +291,25 @@ export async function postHandleMessage(message: Message) {
// very bad code below
// don't care lol
- if (embed?.thumbnail?.url && approvedProviders.indexOf(new URL(embed.thumbnail.url).hostname) !== -1) {
+ if (
+ embed?.thumbnail?.url &&
+ approvedProviders.indexOf(
+ new URL(embed.thumbnail.url).hostname,
+ ) !== -1
+ ) {
embed = {
provider: {
url: link,
name: new URL(link).hostname,
},
image: {
- proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(image!)}?width=${resizeWidth}&height=${resizeHeight}`,
+ proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(
+ image!,
+ )}?width=${resizeWidth}&height=${resizeHeight}`,
url: image,
width: width,
- height: height
- }
+ height: height,
+ },
};
}
@@ -246,16 +317,19 @@ export async function postHandleMessage(message: Message) {
data.embeds.push(embed);
}
}
- } catch (error) { }
+ } catch (error) {}
}
await Promise.all([
emitEvent({
event: "MESSAGE_UPDATE",
channel_id: message.channel_id,
- data
+ data,
} as MessageUpdateEvent),
- Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })
+ Message.update(
+ { id: message.id, channel_id: message.channel_id },
+ { embeds: data.embeds },
+ ),
]);
}
@@ -264,10 +338,14 @@ export async function sendMessage(opts: MessageOptions) {
await Promise.all([
Message.insert(message),
- emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message.toJSON() } as MessageCreateEvent)
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: opts.channel_id,
+ data: message.toJSON(),
+ } as MessageCreateEvent),
]);
- postHandleMessage(message).catch((e) => { }); // no await as it should catch error non-blockingly
+ postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly
return message;
}
diff --git a/src/api/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts
index 4d60eb91..88e266a1 100644
--- a/src/api/util/handlers/Voice.ts
+++ b/src/api/util/handlers/Voice.ts
@@ -3,7 +3,9 @@ import { distanceBetweenLocations, IPAnalysis } from "../utility/ipAddress";
export async function getVoiceRegions(ipAddress: string, vip: boolean) {
const regions = Config.get().regions;
- const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip));
+ const availableRegions = regions.available.filter((ar) =>
+ vip ? true : !ar.vip,
+ );
let optimalId = regions.default;
if (!regions.useDefaultAsOptimal) {
@@ -13,7 +15,10 @@ export async function getVoiceRegions(ipAddress: string, vip: boolean) {
for (let ar of availableRegions) {
//TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call
- const dist = distanceBetweenLocations(clientIpAnalysis, ar.location || (await IPAnalysis(ar.endpoint)));
+ const dist = distanceBetweenLocations(
+ clientIpAnalysis,
+ ar.location || (await IPAnalysis(ar.endpoint)),
+ );
if (dist < min) {
min = dist;
@@ -27,6 +32,6 @@ export async function getVoiceRegions(ipAddress: string, vip: boolean) {
name: ar.name,
custom: ar.custom,
deprecated: ar.deprecated,
- optimal: ar.id === optimalId
+ optimal: ar.id === optimalId,
}));
}
diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts
index c245b411..5dcae953 100644
--- a/src/api/util/handlers/route.ts
+++ b/src/api/util/handlers/route.ts
@@ -10,7 +10,7 @@ import {
PermissionResolvable,
Permissions,
RightResolvable,
- Rights
+ Rights,
} from "@fosscord/util";
import { NextFunction, Request, Response } from "express";
import { AnyValidateFunction } from "ajv/dist/core";
@@ -23,7 +23,11 @@ declare global {
}
}
-export type RouteResponse = { status?: number; body?: `${string}Response`; headers?: Record<string, string> };
+export type RouteResponse = {
+ status?: number;
+ body?: `${string}Response`;
+ headers?: Record<string, string>;
+};
export interface RouteOptions {
permission?: PermissionResolvable;
@@ -48,11 +52,17 @@ export function route(opts: RouteOptions) {
return async (req: Request, res: Response, next: NextFunction) => {
if (opts.permission) {
const required = new Permissions(opts.permission);
- req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id);
+ req.permission = await getPermission(
+ req.user_id,
+ req.params.guild_id,
+ req.params.channel_id,
+ );
// bitfield comparison: check if user lacks certain permission
if (!req.permission.has(required)) {
- throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string);
+ throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(
+ opts.permission as string,
+ );
}
}
@@ -61,15 +71,26 @@ export function route(opts: RouteOptions) {
req.rights = await getRights(req.user_id);
if (!req.rights || !req.rights.has(required)) {
- throw FosscordApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
+ throw FosscordApiErrors.MISSING_RIGHTS.withParams(
+ opts.right as string,
+ );
}
}
if (validate) {
const valid = validate(normalizeBody(req.body));
if (!valid) {
- const fields: Record<string, { code?: string; message: string }> = {};
- validate.errors?.forEach((x) => (fields[x.instancePath.slice(1)] = { code: x.keyword, message: x.message || "" }));
+ const fields: Record<
+ string,
+ { code?: string; message: string }
+ > = {};
+ validate.errors?.forEach(
+ (x) =>
+ (fields[x.instancePath.slice(1)] = {
+ code: x.keyword,
+ message: x.message || "",
+ }),
+ );
throw FieldErrors(fields);
}
}
diff --git a/src/api/util/index.ts b/src/api/util/index.ts
index de6b6064..9f375f72 100644
--- a/src/api/util/index.ts
+++ b/src/api/util/index.ts
@@ -6,4 +6,4 @@ export * from "./utility/RandomInviteID";
export * from "./handlers/route";
export * from "./utility/String";
export * from "./handlers/Voice";
-export * from "./utility/captcha";
\ No newline at end of file
+export * from "./utility/captcha";
diff --git a/src/api/util/utility/Base64.ts b/src/api/util/utility/Base64.ts
index 46cff77a..c10176f2 100644
--- a/src/api/util/utility/Base64.ts
+++ b/src/api/util/utility/Base64.ts
@@ -1,4 +1,5 @@
-const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+";
+const alphabet =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+";
// binary to string lookup table
const b2s = alphabet.split("");
diff --git a/src/api/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts
index 7ea344e0..bfed65bb 100644
--- a/src/api/util/utility/RandomInviteID.ts
+++ b/src/api/util/utility/RandomInviteID.ts
@@ -2,7 +2,8 @@ import { Snowflake } from "@fosscord/util";
export function random(length = 6) {
// Declare all characters
- let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// Pick characers randomly
let str = "";
@@ -15,18 +16,18 @@ export function random(length = 6) {
export function snowflakeBasedInvite() {
// Declare all characters
- let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let base = BigInt(chars.length);
let snowflake = Snowflake.generateWorkerProcess();
// snowflakes hold ~10.75 characters worth of entropy;
// safe to generate a 8-char invite out of them
let str = "";
- for (let i=0; i < 10; i++) {
-
+ for (let i = 0; i < 10; i++) {
str.concat(chars.charAt(Number(snowflake % base)));
snowflake = snowflake / base;
}
-
- return str.substr(3,8).split("").reverse().join("");
+
+ return str.substr(3, 8).split("").reverse().join("");
}
diff --git a/src/api/util/utility/String.ts b/src/api/util/utility/String.ts
index 982b7e11..0913013e 100644
--- a/src/api/util/utility/String.ts
+++ b/src/api/util/utility/String.ts
@@ -2,13 +2,21 @@ import { Request } from "express";
import { ntob } from "./Base64";
import { FieldErrors } from "@fosscord/util";
-export function checkLength(str: string, min: number, max: number, key: string, req: Request) {
+export function checkLength(
+ str: string,
+ min: number,
+ max: number,
+ key: string,
+ req: Request,
+) {
if (str.length < min || str.length > max) {
throw FieldErrors({
[key]: {
code: "BASE_TYPE_BAD_LENGTH",
- message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { length: `${min} - ${max}` })
- }
+ message: req.t("common:field.BASE_TYPE_BAD_LENGTH", {
+ length: `${min} - ${max}`,
+ }),
+ },
});
}
}
diff --git a/src/api/util/utility/captcha.ts b/src/api/util/utility/captcha.ts
index 739647d2..50e2c91a 100644
--- a/src/api/util/utility/captcha.ts
+++ b/src/api/util/utility/captcha.ts
@@ -7,8 +7,8 @@ export interface hcaptchaResponse {
hostname: string;
credit: boolean;
"error-codes": string[];
- score: number; // enterprise only
- score_reason: string[]; // enterprise only
+ score: number; // enterprise only
+ score_reason: string[]; // enterprise only
}
export interface recaptchaResponse {
@@ -23,7 +23,7 @@ export interface recaptchaResponse {
const verifyEndpoints = {
hcaptcha: "https://hcaptcha.com/siteverify",
recaptcha: "https://www.google.com/recaptcha/api/siteverify",
-}
+};
export async function verifyCaptcha(response: string, ip?: string) {
const { security } = Config.get();
@@ -36,11 +36,12 @@ export async function verifyCaptcha(response: string, ip?: string) {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
- body: `response=${encodeURIComponent(response)}`
- + `&secret=${encodeURIComponent(secret!)}`
- + `&sitekey=${encodeURIComponent(sitekey!)}`
- + (ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""),
+ body:
+ `response=${encodeURIComponent(response)}` +
+ `&secret=${encodeURIComponent(secret!)}` +
+ `&sitekey=${encodeURIComponent(sitekey!)}` +
+ (ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""),
});
- return await res.json() as hcaptchaResponse | recaptchaResponse;
-}
\ No newline at end of file
+ return (await res.json()) as hcaptchaResponse | recaptchaResponse;
+}
diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts
index f17b145e..d166ebc5 100644
--- a/src/api/util/utility/ipAddress.ts
+++ b/src/api/util/utility/ipAddress.ts
@@ -25,27 +25,27 @@ const exampleData = {
name: "",
domain: "",
route: "",
- type: "isp"
+ type: "isp",
},
languages: [
{
name: "",
- native: ""
- }
+ native: "",
+ },
],
currency: {
name: "",
code: "",
symbol: "",
native: "",
- plural: ""
+ plural: "",
},
time_zone: {
name: "",
abbr: "",
offset: "",
is_dst: true,
- current_time: ""
+ current_time: "",
},
threat: {
is_tor: false,
@@ -54,10 +54,10 @@ const exampleData = {
is_known_attacker: false,
is_known_abuser: false,
is_threat: false,
- is_bogon: false
+ is_bogon: false,
},
count: 0,
- status: 200
+ status: 200,
};
//TODO add function that support both ip and domain names
@@ -65,7 +65,9 @@ export async function IPAnalysis(ip: string): Promise<typeof exampleData> {
const { ipdataApiKey } = Config.get().security;
if (!ipdataApiKey) return { ...exampleData, ip };
- return (await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json() as any; // TODO: types
+ return (
+ await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)
+ ).json() as any; // TODO: types
}
export function isProxy(data: typeof exampleData) {
@@ -77,19 +79,35 @@ export function isProxy(data: typeof exampleData) {
}
export function getIpAdress(req: Request): string {
- // @ts-ignore
- return req.headers[Config.get().security.forwadedFor] || req.socket.remoteAddress;
+ return (
+ // @ts-ignore
+ req.headers[Config.get().security.forwadedFor] ||
+ req.socket.remoteAddress
+ );
}
export function distanceBetweenLocations(loc1: any, loc2: any): number {
- return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude);
+ return distanceBetweenCoords(
+ loc1.latitude,
+ loc1.longitude,
+ loc2.latitude,
+ loc2.longitude,
+ );
}
//Haversine function
-function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) {
+function distanceBetweenCoords(
+ lat1: number,
+ lon1: number,
+ lat2: number,
+ lon2: number,
+) {
const p = 0.017453292519943295; // Math.PI / 180
const c = Math.cos;
- const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2;
+ const a =
+ 0.5 -
+ c((lat2 - lat1) * p) / 2 +
+ (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2;
return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
}
diff --git a/src/api/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts
index 439700d0..35c55999 100644
--- a/src/api/util/utility/passwordStrength.ts
+++ b/src/api/util/utility/passwordStrength.ts
@@ -18,7 +18,8 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored
* Returns: 0 > pw > 1
*/
export function checkPassword(password: string): number {
- const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password;
+ const { minLength, minNumbers, minUpperCase, minSymbols } =
+ Config.get().register.password;
var strength = 0;
// checks for total password len
@@ -42,19 +43,24 @@ export function checkPassword(password: string): number {
}
// checks if password only consists of numbers or only consists of chars
- if (password.length == password.count(reNUMBER) || password.length === password.count(reUPPERCASELETTER)) {
+ if (
+ password.length == password.count(reNUMBER) ||
+ password.length === password.count(reUPPERCASELETTER)
+ ) {
strength = 0;
}
-
+
let entropyMap: { [key: string]: number } = {};
for (let i = 0; i < password.length; i++) {
if (entropyMap[password[i]]) entropyMap[password[i]]++;
else entropyMap[password[i]] = 1;
}
-
+
let entropies = Object.values(entropyMap);
-
- entropies.map(x => (x / entropyMap.length));
- strength += entropies.reduceRight((a: number, x: number) => a - (x * Math.log2(x))) / Math.log2(password.length);
+
+ entropies.map((x) => x / entropyMap.length);
+ strength +=
+ entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) /
+ Math.log2(password.length);
return strength;
}
diff --git a/src/bundle/Server.ts b/src/bundle/Server.ts
index c85daf40..dd75e777 100644
--- a/src/bundle/Server.ts
+++ b/src/bundle/Server.ts
@@ -22,7 +22,7 @@ const cdn = new CDNServer({ server, port, production, app });
const gateway = new Gateway.Server({ server, port, production });
//this is what has been added for the /stop API route
-process.on('SIGTERM', () => {
+process.on("SIGTERM", () => {
server.close(() => {
console.log("Stop API has been successfully POSTed, SIGTERM sent");
});
@@ -66,7 +66,9 @@ async function main() {
//Sentry
if (Config.get().sentry.enabled) {
console.log(
- `[Bundle] ${yellow("You are using Sentry! This may slightly impact performance on large loads!")}`
+ `[Bundle] ${yellow(
+ "You are using Sentry! This may slightly impact performance on large loads!",
+ )}`,
);
Sentry.init({
dsn: Config.get().sentry.endpoint,
@@ -81,7 +83,10 @@ async function main() {
Sentry.addGlobalEventProcessor((event, hint) => {
if (event.transaction) {
- event.transaction = event.transaction.split("/").map(x => !parseInt(x) ? x : ":id").join("/");
+ event.transaction = event.transaction
+ .split("/")
+ .map((x) => (!parseInt(x) ? x : ":id"))
+ .join("/");
}
delete event.request?.cookies;
@@ -93,14 +98,20 @@ async function main() {
}
if (event.breadcrumbs) {
- event.breadcrumbs = event.breadcrumbs.filter(x => {
+ event.breadcrumbs = event.breadcrumbs.filter((x) => {
if (x.message?.includes("identified as")) return false;
if (x.message?.includes("[WebSocket] closed")) return false;
- if (x.message?.includes("Got Resume -> cancel not implemented")) return false;
- if (x.message?.includes("[Gateway] New connection from")) return false;
+ if (
+ x.message?.includes(
+ "Got Resume -> cancel not implemented",
+ )
+ )
+ return false;
+ if (x.message?.includes("[Gateway] New connection from"))
+ return false;
return true;
- })
+ });
}
return event;
diff --git a/src/bundle/index.ts b/src/bundle/index.ts
index 960d4dc0..45f084c5 100644
--- a/src/bundle/index.ts
+++ b/src/bundle/index.ts
@@ -1,4 +1,4 @@
export * from "@fosscord/api";
export * from "@fosscord/util";
export * from "@fosscord/gateway";
-export * from "@fosscord/cdn";
\ No newline at end of file
+export * from "@fosscord/cdn";
diff --git a/src/bundle/start.ts b/src/bundle/start.ts
index 2a1e6520..0c85b58d 100644
--- a/src/bundle/start.ts
+++ b/src/bundle/start.ts
@@ -1,5 +1,5 @@
// process.env.MONGOMS_DEBUG = "true";
-require('module-alias/register');
+require("module-alias/register");
import "reflect-metadata";
import cluster, { Worker } from "cluster";
import os from "os";
@@ -37,18 +37,20 @@ if (cluster.isMaster) {
╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝
fosscord-server | ${yellow(
- `Pre-release (${commit !== null
- ? commit.slice(0, 7)
- : "Unknown (Git cannot be found)"
- })`
+ `Pre-release (${
+ commit !== null
+ ? commit.slice(0, 7)
+ : "Unknown (Git cannot be found)"
+ })`,
)}
-Commit Hash: ${commit !== null
+Commit Hash: ${
+ commit !== null
? `${cyan(commit)} (${yellow(commit.slice(0, 7))})`
: "Unknown (Git cannot be found)"
- }
+ }
Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).)
-`)
+`),
);
if (commit == null) {
@@ -85,8 +87,8 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).)
cluster.on("exit", (worker: any, code: any, signal: any) => {
console.log(
`[Worker] ${red(
- `died with PID: ${worker.process.pid} , restarting ...`
- )}`
+ `died with PID: ${worker.process.pid} , restarting ...`,
+ )}`,
);
cluster.fork();
});
diff --git a/src/bundle/stats.ts b/src/bundle/stats.ts
index 0234e0b4..3a9b2d85 100644
--- a/src/bundle/stats.ts
+++ b/src/bundle/stats.ts
@@ -6,18 +6,17 @@ export function initStats() {
console.log(`[Path] running in ${__dirname}`);
try {
console.log(`[CPU] ${osu.cpu.model()} Cores x${osu.cpu.count()}`);
+ } catch {
+ console.log("[CPU] Failed to get cpu model!");
}
- catch {
- console.log('[CPU] Failed to get cpu model!')
- }
-
+
console.log(`[System] ${os.platform()} ${os.arch()}`);
console.log(`[Process] running with PID: ${process.pid}`);
if (process.getuid && process.getuid() === 0) {
console.warn(
red(
- `[Process] Warning fosscord is running as root, this highly discouraged and might expose your system vulnerable to attackers. Please run fosscord as a user without root privileges.`
- )
+ `[Process] Warning fosscord is running as root, this highly discouraged and might expose your system vulnerable to attackers. Please run fosscord as a user without root privileges.`,
+ ),
);
}
diff --git a/src/cdn/Server.ts b/src/cdn/Server.ts
index 5b395589..f7e6dbdc 100644
--- a/src/cdn/Server.ts
+++ b/src/cdn/Server.ts
@@ -5,7 +5,7 @@ import avatarsRoute from "./routes/avatars";
import iconsRoute from "./routes/role-icons";
import bodyParser from "body-parser";
-export interface CDNServerOptions extends ServerOptions { }
+export interface CDNServerOptions extends ServerOptions {}
export class CDNServer extends Server {
public declare options: CDNServerOptions;
@@ -22,15 +22,15 @@ export class CDNServer extends Server {
// TODO: use better CSP policy
res.set(
"Content-security-policy",
- "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';"
+ "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';",
);
res.set(
"Access-Control-Allow-Headers",
- req.header("Access-Control-Request-Headers") || "*"
+ req.header("Access-Control-Request-Headers") || "*",
);
res.set(
"Access-Control-Allow-Methods",
- req.header("Access-Control-Request-Methods") || "*"
+ req.header("Access-Control-Request-Methods") || "*",
);
next();
});
diff --git a/src/cdn/routes/attachments.ts b/src/cdn/routes/attachments.ts
index ae50bc48..2a1b6f09 100644
--- a/src/cdn/routes/attachments.ts
+++ b/src/cdn/routes/attachments.ts
@@ -56,7 +56,7 @@ router.post(
};
return res.json(file);
- }
+ },
);
router.get(
@@ -65,7 +65,7 @@ router.get(
const { channel_id, id, filename } = req.params;
const file = await storage.get(
- `attachments/${channel_id}/${id}/${filename}`
+ `attachments/${channel_id}/${id}/${filename}`,
);
if (!file) throw new HTTPError("File not found");
const type = await FileType.fromBuffer(file);
@@ -79,7 +79,7 @@ router.get(
res.set("Cache-Control", "public, max-age=31536000");
return res.send(file);
- }
+ },
);
router.delete(
@@ -94,7 +94,7 @@ router.delete(
await storage.delete(path);
return res.send({ success: true });
- }
+ },
);
export default router;
diff --git a/src/cdn/routes/avatars.ts b/src/cdn/routes/avatars.ts
index e5e25a4c..50a76d4b 100644
--- a/src/cdn/routes/avatars.ts
+++ b/src/cdn/routes/avatars.ts
@@ -55,7 +55,7 @@ router.post(
size,
url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`,
});
- }
+ },
);
router.get("/:user_id", async (req: Request, res: Response) => {
@@ -86,7 +86,7 @@ export const getAvatar = async (req: Request, res: Response) => {
res.set("Cache-Control", "public, max-age=31536000");
return res.send(file);
-}
+};
router.get("/:user_id/:hash", getAvatar);
diff --git a/src/cdn/routes/external.ts b/src/cdn/routes/external.ts
index cb17ff9b..405e665e 100644
--- a/src/cdn/routes/external.ts
+++ b/src/cdn/routes/external.ts
@@ -66,14 +66,14 @@ router.get("/resize/:url", async (req: Request, res: Response) => {
const { resizeHeightMax, resizeWidthMax } = Config.get().cdn;
const w = Math.min(parseInt(width as string), resizeWidthMax ?? 100);
const h = Math.min(parseInt(height as string), resizeHeightMax ?? 100);
- if (w < 1 || h < 1) throw new HTTPError("Width and height must be greater than 0");
+ if (w < 1 || h < 1)
+ throw new HTTPError("Width and height must be greater than 0");
let buffer, response;
try {
response = await fetch(url, DEFAULT_FETCH_OPTIONS);
buffer = await response.buffer();
- }
- catch (e) {
+ } catch (e) {
throw new HTTPError("Couldn't fetch website");
}
@@ -84,7 +84,10 @@ router.get("/resize/:url", async (req: Request, res: Response) => {
.toBuffer();
res.setHeader("Content-Disposition", "attachment");
- res.setHeader("Content-Type", response.headers.get("content-type") ?? "image/png");
+ res.setHeader(
+ "Content-Type",
+ response.headers.get("content-type") ?? "image/png",
+ );
return res.end(resizedBuffer);
});
diff --git a/src/cdn/routes/guilds.ts b/src/cdn/routes/guilds.ts
index 3c4b646c..6f0719b6 100644
--- a/src/cdn/routes/guilds.ts
+++ b/src/cdn/routes/guilds.ts
@@ -7,4 +7,4 @@ const router = Router();
router.get("/:guild_id/users/:user_id/avatars/:hash", getAvatar);
router.get("/:guild_id/users/:user_id/banners/:hash", getAvatar);
-export default router;
\ No newline at end of file
+export default router;
diff --git a/src/cdn/routes/role-icons.ts b/src/cdn/routes/role-icons.ts
index 12aae8a4..bdfa0355 100644
--- a/src/cdn/routes/role-icons.ts
+++ b/src/cdn/routes/role-icons.ts
@@ -54,7 +54,7 @@ router.post(
size,
url: `${endpoint}${req.baseUrl}/${role_id}/${hash}`,
});
- }
+ },
);
router.get("/:role_id", async (req: Request, res: Response) => {
diff --git a/src/cdn/start.ts b/src/cdn/start.ts
index 1fdea22e..c22984fa 100644
--- a/src/cdn/start.ts
+++ b/src/cdn/start.ts
@@ -1,4 +1,4 @@
-require('module-alias/register')
+require("module-alias/register");
import dotenv from "dotenv";
dotenv.config();
diff --git a/src/cdn/util/S3Storage.ts b/src/cdn/util/S3Storage.ts
index c4066817..33c11265 100644
--- a/src/cdn/util/S3Storage.ts
+++ b/src/cdn/util/S3Storage.ts
@@ -14,7 +14,7 @@ export class S3Storage implements Storage {
public constructor(
private client: S3,
private bucket: string,
- private basePath?: string
+ private basePath?: string,
) {}
/**
diff --git a/src/cdn/util/Storage.ts b/src/cdn/util/Storage.ts
index d040f50b..d66cb2dc 100644
--- a/src/cdn/util/Storage.ts
+++ b/src/cdn/util/Storage.ts
@@ -33,14 +33,14 @@ if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) {
if (!region) {
console.error(
- `[CDN] You must provide a region when using the S3 storage provider.`
+ `[CDN] You must provide a region when using the S3 storage provider.`,
);
process.exit(1);
}
if (!bucket) {
console.error(
- `[CDN] You must provide a bucket when using the S3 storage provider.`
+ `[CDN] You must provide a bucket when using the S3 storage provider.`,
);
process.exit(1);
}
@@ -50,7 +50,7 @@ if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) {
if (!location) {
console.warn(
- `[CDN] STORAGE_LOCATION unconfigured for S3 provider, defaulting to the bucket root...`
+ `[CDN] STORAGE_LOCATION unconfigured for S3 provider, defaulting to the bucket root...`,
);
location = undefined;
}
diff --git a/src/gateway/events/Connection.ts b/src/gateway/events/Connection.ts
index bed3cf44..8747e3ad 100644
--- a/src/gateway/events/Connection.ts
+++ b/src/gateway/events/Connection.ts
@@ -21,10 +21,12 @@ try {
export async function Connection(
this: WS.Server,
socket: WebSocket,
- request: IncomingMessage
+ request: IncomingMessage,
) {
const forwardedFor = Config.get().security.forwadedFor;
- const ipAddress = forwardedFor ? request.headers[forwardedFor] as string : request.socket.remoteAddress;
+ const ipAddress = forwardedFor
+ ? (request.headers[forwardedFor] as string)
+ : request.socket.remoteAddress;
socket.ipAddress = ipAddress;
@@ -33,7 +35,9 @@ export async function Connection(
socket.on("close", Close);
// @ts-ignore
socket.on("message", Message);
- console.log(`[Gateway] New connection from ${socket.ipAddress}, total ${this.clients.size}`);
+ console.log(
+ `[Gateway] New connection from ${socket.ipAddress}, total ${this.clients.size}`,
+ );
const { searchParams } = new URL(`http://localhost${request.url}`);
// @ts-ignore
@@ -41,7 +45,7 @@ export async function Connection(
if (!["json", "etf"].includes(socket.encoding)) {
if (socket.encoding === "etf" && erlpack) {
throw new Error(
- "Erlpack is not installed: 'npm i @yukikaze-bot/erlpack'"
+ "Erlpack is not installed: 'npm i @yukikaze-bot/erlpack'",
);
}
return socket.close(CLOSECODES.Decode_error);
diff --git a/src/gateway/events/Message.ts b/src/gateway/events/Message.ts
index 4699f1af..603f68fa 100644
--- a/src/gateway/events/Message.ts
+++ b/src/gateway/events/Message.ts
@@ -3,7 +3,7 @@ import { WebSocket, Payload } from "@fosscord/gateway";
var erlpack: any;
try {
erlpack = require("@yukikaze-bot/erlpack");
-} catch (error) { }
+} catch (error) {}
import OPCodeHandlers from "../opcodes";
import { Tuple } from "lambert-server";
import { check } from "../opcodes/instanceOf";
@@ -34,11 +34,9 @@ export async function Message(this: WebSocket, buffer: WS.Data) {
}
}
data = bigIntJson.parse(buffer as string);
- }
- else if (typeof buffer == "string") {
+ } else if (typeof buffer == "string") {
data = bigIntJson.parse(buffer as string);
- }
- else return;
+ } else return;
check.call(this, PayloadSchema, data);
diff --git a/src/gateway/listener/listener.ts b/src/gateway/listener/listener.ts
index 72dd9d5b..1d9caebb 100644
--- a/src/gateway/listener/listener.ts
+++ b/src/gateway/listener/listener.ts
@@ -26,7 +26,7 @@ import { Recipient } from "@fosscord/util";
export function handlePresenceUpdate(
this: WebSocket,
- { event, acknowledge, data }: EventOpts
+ { event, acknowledge, data }: EventOpts,
) {
acknowledge?.();
if (event === EVENTEnum.PresenceUpdate) {
@@ -54,14 +54,14 @@ export async function setupListener(this: WebSocket) {
where: {
from_id: this.user_id,
type: RelationshipType.friends,
- }
+ },
}),
]);
const guilds = members.map((x) => x.guild);
const dm_channels = recipients.map((x) => x.channel);
- const opts: { acknowledge: boolean; channel?: AMQChannel; } = {
+ const opts: { acknowledge: boolean; channel?: AMQChannel } = {
acknowledge: true,
};
this.listen_options = opts;
@@ -79,7 +79,7 @@ export async function setupListener(this: WebSocket) {
this.events[relationship.to_id] = await listenEvent(
relationship.to_id,
handlePresenceUpdate.bind(this),
- opts
+ opts,
);
});
@@ -101,7 +101,7 @@ export async function setupListener(this: WebSocket) {
this.events[channel.id] = await listenEvent(
channel.id,
consumer,
- opts
+ opts,
);
}
});
@@ -137,7 +137,7 @@ async function consume(this: WebSocket, opts: EventOpts) {
this.member_events[data.user.id] = await listenEvent(
data.user.id,
handlePresenceUpdate.bind(this),
- this.listen_options
+ this.listen_options,
);
break;
case "GUILD_MEMBER_REMOVE":
@@ -164,7 +164,7 @@ async function consume(this: WebSocket, opts: EventOpts) {
this.events[data.user.id] = await listenEvent(
data.user.id,
handlePresenceUpdate.bind(this),
- this.listen_options
+ this.listen_options,
);
break;
case "GUILD_CREATE":
diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts
index b4b36075..c5c78f1a 100644
--- a/src/gateway/opcodes/Identify.ts
+++ b/src/gateway/opcodes/Identify.ts
@@ -158,11 +158,13 @@ export async function onIdentify(this: WebSocket, data: Payload) {
...x.settings,
guild_id: x.guild.id,
// disgusting
- channel_overrides: Object.entries(x.settings.channel_overrides ?? {}).map(y => ({
+ channel_overrides: Object.entries(
+ x.settings.channel_overrides ?? {},
+ ).map((y) => ({
...y[1],
channel_id: y[0],
- }))
- })) as any as UserGuildSettings[]; // VERY disgusting. don't care.
+ })),
+ })) as any as UserGuildSettings[]; // VERY disgusting. don't care.
const channels = recipients.map((x) => {
// @ts-ignore
@@ -171,7 +173,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
users = users.concat(x.channel.recipients as unknown as User[]);
if (x.channel.isDm()) {
x.channel.recipients = x.channel.recipients!.filter(
- (x) => x.id !== this.user_id
+ (x) => x.id !== this.user_id,
);
}
return x.channel;
@@ -243,7 +245,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
accent_color: user.accent_color,
banner: user.banner,
bio: user.bio,
- premium_since: user.premium_since
+ premium_since: user.premium_since,
};
const d: ReadyEventData = {
diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts
index 0f21d087..f5bbad14 100644
--- a/src/gateway/opcodes/LazyRequest.ts
+++ b/src/gateway/opcodes/LazyRequest.ts
@@ -1,5 +1,19 @@
-import { getDatabase, getPermission, listenEvent, Member, Role, Session, LazyRequestSchema } from "@fosscord/util";
-import { WebSocket, Payload, handlePresenceUpdate, OPCODES, Send } from "@fosscord/gateway";
+import {
+ getDatabase,
+ getPermission,
+ listenEvent,
+ Member,
+ Role,
+ Session,
+ LazyRequestSchema,
+} from "@fosscord/util";
+import {
+ WebSocket,
+ Payload,
+ handlePresenceUpdate,
+ OPCODES,
+ Send,
+} from "@fosscord/gateway";
import { check } from "./instanceOf";
// TODO: only show roles/members that have access to this channel
@@ -14,7 +28,8 @@ async function getMembers(guild_id: string, range: [number, number]) {
let members: Member[] = [];
try {
- members = await getDatabase()!.getRepository(Member)
+ members = await getDatabase()!
+ .getRepository(Member)
.createQueryBuilder("member")
.where("member.guild_id = :guild_id", { guild_id })
.leftJoinAndSelect("member.roles", "role")
@@ -23,7 +38,7 @@ async function getMembers(guild_id: string, range: [number, number]) {
.addSelect("user.settings")
.addSelect(
"CASE WHEN session.status = 'offline' THEN 0 ELSE 1 END",
- "_status"
+ "_status",
)
.orderBy("role.position", "DESC")
.addOrderBy("_status", "DESC")
@@ -31,8 +46,7 @@ async function getMembers(guild_id: string, range: [number, number]) {
.offset(Number(range[0]) || 0)
.limit(Number(range[1]) || 100)
.getMany();
- }
- catch (e) {
+ } catch (e) {
console.error(`LazyRequest`, e);
}
@@ -51,14 +65,20 @@ async function getMembers(guild_id: string, range: [number, number]) {
.map((m) => m.roles)
.flat()
.unique((r: Role) => r.id);
- member_roles.push(member_roles.splice(member_roles.findIndex(x => x.id === x.guild_id), 1)[0]);
+ member_roles.push(
+ member_roles.splice(
+ member_roles.findIndex((x) => x.id === x.guild_id),
+ 1,
+ )[0],
+ );
const offlineItems = [];
for (const role of member_roles) {
// @ts-ignore
- const [role_members, other_members]: Member[][] = partition(members, (m: Member) =>
- m.roles.find((r) => r.id === role.id)
+ const [role_members, other_members]: Member[][] = partition(
+ members,
+ (m: Member) => m.roles.find((r) => r.id === role.id),
);
const group = {
count: role_members.length,
@@ -74,15 +94,19 @@ async function getMembers(guild_id: string, range: [number, number]) {
.map((x: Role) => x.id);
const statusMap = {
- "online": 0,
- "idle": 1,
- "dnd": 2,
- "invisible": 3,
- "offline": 4,
+ online: 0,
+ idle: 1,
+ dnd: 2,
+ invisible: 3,
+ offline: 4,
};
// sort sessions by relevance
const sessions = member.user.sessions.sort((a, b) => {
- return (statusMap[a.status] - statusMap[b.status]) + ((a.activities.length - b.activities.length) * 2);
+ return (
+ statusMap[a.status] -
+ statusMap[b.status] +
+ (a.activities.length - b.activities.length) * 2
+ );
});
var session: Session | undefined = sessions.first();
@@ -103,7 +127,11 @@ async function getMembers(guild_id: string, range: [number, number]) {
},
};
- if (!session || session.status == "invisible" || session.status == "offline") {
+ if (
+ !session ||
+ session.status == "invisible" ||
+ session.status == "offline"
+ ) {
item.member.presence.status = "offline";
offlineItems.push(item);
group.count--;
@@ -130,7 +158,9 @@ async function getMembers(guild_id: string, range: [number, number]) {
items,
groups,
range,
- members: items.map((x) => 'member' in x ? x.member : undefined).filter(x => !!x),
+ members: items
+ .map((x) => ("member" in x ? x.member : undefined))
+ .filter((x) => !!x),
};
}
@@ -161,7 +191,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
this.member_events[member.user.id] = await listenEvent(
member.user.id,
handlePresenceUpdate.bind(this),
- this.listen_options
+ this.listen_options,
);
});
});
@@ -181,7 +211,9 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
op: "SYNC",
range: x.range,
})),
- online_count: member_count - (groups.find(x => x.id == "offline")?.count ?? 0),
+ online_count:
+ member_count -
+ (groups.find((x) => x.id == "offline")?.count ?? 0),
member_count,
id: "everyone",
guild_id,
@@ -199,6 +231,6 @@ function partition<T>(array: T[], isValid: Function) {
? [[...pass, elem], fail]
: [pass, [...fail, elem]];
},
- [[], []]
+ [[], []],
);
}
diff --git a/src/gateway/opcodes/PresenceUpdate.ts b/src/gateway/opcodes/PresenceUpdate.ts
index d17b7dd7..37299213 100644
--- a/src/gateway/opcodes/PresenceUpdate.ts
+++ b/src/gateway/opcodes/PresenceUpdate.ts
@@ -1,5 +1,11 @@
import { WebSocket, Payload } from "@fosscord/gateway";
-import { emitEvent, PresenceUpdateEvent, Session, User, ActivitySchema } from "@fosscord/util";
+import {
+ emitEvent,
+ PresenceUpdateEvent,
+ Session,
+ User,
+ ActivitySchema,
+} from "@fosscord/util";
import { check } from "./instanceOf";
export async function onPresenceUpdate(this: WebSocket, { d }: Payload) {
@@ -8,7 +14,7 @@ export async function onPresenceUpdate(this: WebSocket, { d }: Payload) {
await Session.update(
{ session_id: this.session_id },
- { status: presence.status, activities: presence.activities }
+ { status: presence.status, activities: presence.activities },
);
await emitEvent({
diff --git a/src/gateway/opcodes/VoiceStateUpdate.ts b/src/gateway/opcodes/VoiceStateUpdate.ts
index 8e1585ec..17ed7e4f 100644
--- a/src/gateway/opcodes/VoiceStateUpdate.ts
+++ b/src/gateway/opcodes/VoiceStateUpdate.ts
@@ -87,16 +87,18 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
//If it's null it means that we are leaving the channel and this event is not needed
if (voiceState.channel_id !== null) {
- const guild = await Guild.findOne({ where: { id: voiceState.guild_id } });
+ const guild = await Guild.findOne({
+ where: { id: voiceState.guild_id },
+ });
const regions = Config.get().regions;
let guildRegion: Region;
if (guild && guild.region) {
guildRegion = regions.available.filter(
- (r) => r.id === guild.region
+ (r) => r.id === guild.region,
)[0];
} else {
guildRegion = regions.available.filter(
- (r) => r.id === regions.default
+ (r) => r.id === regions.default,
)[0];
}
diff --git a/src/gateway/start.ts b/src/gateway/start.ts
index 90d7f34e..84de674f 100644
--- a/src/gateway/start.ts
+++ b/src/gateway/start.ts
@@ -1,4 +1,4 @@
-require('module-alias/register');
+require("module-alias/register");
process.on("uncaughtException", console.error);
process.on("unhandledRejection", console.error);
diff --git a/src/gateway/util/Send.ts b/src/gateway/util/Send.ts
index e1460846..1c0f33c3 100644
--- a/src/gateway/util/Send.ts
+++ b/src/gateway/util/Send.ts
@@ -2,7 +2,9 @@ var erlpack: any;
try {
erlpack = require("@yukikaze-bot/erlpack");
} catch (error) {
- console.log("Missing @yukikaze-bot/erlpack, electron-based desktop clients designed for discord.com will not be able to connect!");
+ console.log(
+ "Missing @yukikaze-bot/erlpack, electron-based desktop clients designed for discord.com will not be able to connect!",
+ );
}
import { Payload, WebSocket } from "@fosscord/gateway";
diff --git a/src/util/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts
index 226b2f9d..fcc91204 100644
--- a/src/util/dtos/DmChannelDTO.ts
+++ b/src/util/dtos/DmChannelDTO.ts
@@ -11,7 +11,11 @@ export class DmChannelDTO {
recipients: MinimalPublicUserDTO[];
type: number;
- static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) {
+ static async from(
+ channel: Channel,
+ excluded_recipients: string[] = [],
+ origin_channel_id?: string,
+ ) {
const obj = new DmChannelDTO();
obj.icon = channel.icon || null;
obj.id = channel.id;
@@ -23,10 +27,15 @@ export class DmChannelDTO {
obj.recipients = (
await Promise.all(
channel
- .recipients!.filter((r) => !excluded_recipients.includes(r.user_id))
+ .recipients!.filter(
+ (r) => !excluded_recipients.includes(r.user_id),
+ )
.map(async (r) => {
- return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection });
- })
+ return await User.findOneOrFail({
+ where: { id: r.user_id },
+ select: PublicUserProjection,
+ });
+ }),
)
).map((u) => new MinimalPublicUserDTO(u));
return obj;
@@ -35,7 +44,9 @@ export class DmChannelDTO {
excludedRecipients(excluded_recipients: string[]): DmChannelDTO {
return {
...this,
- recipients: this.recipients.filter((r) => !excluded_recipients.includes(r.id)),
+ recipients: this.recipients.filter(
+ (r) => !excluded_recipients.includes(r.id),
+ ),
};
}
}
diff --git a/src/util/entities/Attachment.ts b/src/util/entities/Attachment.ts
index 7b4b17eb..055b6f4b 100644
--- a/src/util/entities/Attachment.ts
+++ b/src/util/entities/Attachment.ts
@@ -1,4 +1,11 @@
-import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import {
+ BeforeRemove,
+ Column,
+ Entity,
+ JoinColumn,
+ ManyToOne,
+ RelationId,
+} from "typeorm";
import { URL } from "url";
import { deleteFile } from "../util/cdn";
import { BaseClass } from "./BaseClass";
@@ -31,9 +38,13 @@ export class Attachment extends BaseClass {
message_id: string;
@JoinColumn({ name: "message_id" })
- @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, {
- onDelete: "CASCADE",
- })
+ @ManyToOne(
+ () => require("./Message").Message,
+ (message: import("./Message").Message) => message.attachments,
+ {
+ onDelete: "CASCADE",
+ },
+ )
message: import("./Message").Message;
@BeforeRemove()
diff --git a/src/util/entities/AuditLog.ts b/src/util/entities/AuditLog.ts
index b003e7ba..9cc97742 100644
--- a/src/util/entities/AuditLog.ts
+++ b/src/util/entities/AuditLog.ts
@@ -5,24 +5,24 @@ import { User } from "./User";
export enum AuditLogEvents {
// guild level
- GUILD_UPDATE = 1,
+ GUILD_UPDATE = 1,
GUILD_IMPORT = 2,
GUILD_EXPORTED = 3,
GUILD_ARCHIVE = 4,
GUILD_UNARCHIVE = 5,
// join-leave
- USER_JOIN = 6,
+ USER_JOIN = 6,
USER_LEAVE = 7,
// channels
- CHANNEL_CREATE = 10,
+ CHANNEL_CREATE = 10,
CHANNEL_UPDATE = 11,
CHANNEL_DELETE = 12,
// permission overrides
- CHANNEL_OVERWRITE_CREATE = 13,
+ CHANNEL_OVERWRITE_CREATE = 13,
CHANNEL_OVERWRITE_UPDATE = 14,
CHANNEL_OVERWRITE_DELETE = 15,
// kick and ban
- MEMBER_KICK = 20,
+ MEMBER_KICK = 20,
MEMBER_PRUNE = 21,
MEMBER_BAN_ADD = 22,
MEMBER_BAN_REMOVE = 23,
@@ -79,17 +79,17 @@ export enum AuditLogEvents {
// application commands
APPLICATION_COMMAND_PERMISSION_UPDATE = 121,
// automod
- POLICY_CREATE = 140,
+ POLICY_CREATE = 140,
POLICY_UPDATE = 141,
POLICY_DELETE = 142,
- MESSAGE_BLOCKED_BY_POLICIES = 143, // in fosscord, blocked messages are stealth-dropped
+ MESSAGE_BLOCKED_BY_POLICIES = 143, // in fosscord, blocked messages are stealth-dropped
// instance policies affecting the guild
GUILD_AFFECTED_BY_POLICIES = 216,
// message moves
IN_GUILD_MESSAGE_MOVE = 223,
CROSS_GUILD_MESSAGE_MOVE = 224,
// message routing
- ROUTE_CREATE = 225,
+ ROUTE_CREATE = 225,
ROUTE_UPDATE = 226,
}
diff --git a/src/util/entities/BackupCodes.ts b/src/util/entities/BackupCodes.ts
index d532a39a..81cdbb6d 100644
--- a/src/util/entities/BackupCodes.ts
+++ b/src/util/entities/BackupCodes.ts
@@ -24,7 +24,7 @@ export function generateMfaBackupCodes(user_id: string) {
for (let i = 0; i < 10; i++) {
const code = BackupCode.create({
user: { id: user_id },
- code: crypto.randomBytes(4).toString("hex"), // 8 characters
+ code: crypto.randomBytes(4).toString("hex"), // 8 characters
consumed: false,
expired: false,
});
@@ -32,4 +32,4 @@ export function generateMfaBackupCodes(user_id: string) {
}
return backup_codes;
-}
\ No newline at end of file
+}
diff --git a/src/util/entities/BaseClass.ts b/src/util/entities/BaseClass.ts
index d5a7c2bf..9942b60e 100644
--- a/src/util/entities/BaseClass.ts
+++ b/src/util/entities/BaseClass.ts
@@ -1,5 +1,12 @@
import "reflect-metadata";
-import { BaseEntity, BeforeInsert, BeforeUpdate, FindOptionsWhere, ObjectIdColumn, PrimaryColumn } from "typeorm";
+import {
+ BaseEntity,
+ BeforeInsert,
+ BeforeUpdate,
+ FindOptionsWhere,
+ ObjectIdColumn,
+ PrimaryColumn,
+} from "typeorm";
import { Snowflake } from "../util/Snowflake";
import "missing-native-js-functions";
import { getDatabase } from "..";
@@ -22,23 +29,40 @@ export class BaseClassWithoutId extends BaseEntity {
toJSON(): any {
return Object.fromEntries(
this.metadata!.columns // @ts-ignore
- .map((x) => [x.propertyName, this[x.propertyName]]) // @ts-ignore
- .concat(this.metadata.relations.map((x) => [x.propertyName, this[x.propertyName]]))
+ .map((x) => [x.propertyName, this[x.propertyName]])
+ .concat(
+ // @ts-ignore
+ this.metadata.relations.map((x) => [
+ x.propertyName,
+ // @ts-ignore
+ this[x.propertyName],
+ ]),
+ ),
);
}
- static increment<T extends BaseClass>(conditions: FindOptionsWhere<T>, propertyPath: string, value: number | string) {
+ static increment<T extends BaseClass>(
+ conditions: FindOptionsWhere<T>,
+ propertyPath: string,
+ value: number | string,
+ ) {
const repository = this.getRepository();
return repository.increment(conditions, propertyPath, value);
}
- static decrement<T extends BaseClass>(conditions: FindOptionsWhere<T>, propertyPath: string, value: number | string) {
+ static decrement<T extends BaseClass>(
+ conditions: FindOptionsWhere<T>,
+ propertyPath: string,
+ value: number | string,
+ ) {
const repository = this.getRepository();
return repository.decrement(conditions, propertyPath, value);
}
}
-export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn;
+export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb")
+ ? ObjectIdColumn
+ : PrimaryColumn;
export class BaseClass extends BaseClassWithoutId {
@PrimaryIdColumn()
diff --git a/src/util/entities/Categories.ts b/src/util/entities/Categories.ts
index 81fbc303..f12b237d 100644
--- a/src/util/entities/Categories.ts
+++ b/src/util/entities/Categories.ts
@@ -1,4 +1,4 @@
-import { PrimaryColumn, Column, Entity} from "typeorm";
+import { PrimaryColumn, Column, Entity } from "typeorm";
import { BaseClassWithoutId } from "./BaseClass";
// TODO: categories:
@@ -16,18 +16,18 @@ import { BaseClassWithoutId } from "./BaseClass";
// Also populate discord default categories
@Entity("categories")
-export class Categories extends BaseClassWithoutId { // Not using snowflake
-
- @PrimaryColumn()
- id: number;
+export class Categories extends BaseClassWithoutId {
+ // Not using snowflake
- @Column({ nullable: true })
- name: string;
+ @PrimaryColumn()
+ id: number;
- @Column({ type: "simple-json" })
- localizations: string;
+ @Column({ nullable: true })
+ name: string;
- @Column({ nullable: true })
- is_primary: boolean;
+ @Column({ type: "simple-json" })
+ localizations: string;
-}
\ No newline at end of file
+ @Column({ nullable: true })
+ is_primary: boolean;
+}
diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts
index 2200bfa3..14f36857 100644
--- a/src/util/entities/Channel.ts
+++ b/src/util/entities/Channel.ts
@@ -1,389 +1,457 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
-import { BaseClass } from "./BaseClass";
-import { Guild } from "./Guild";
-import { PublicUserProjection, User } from "./User";
-import { HTTPError } from "lambert-server";
-import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters, ChannelTypes } from "../util";
-import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
-import { Recipient } from "./Recipient";
-import { Message } from "./Message";
-import { ReadState } from "./ReadState";
-import { Invite } from "./Invite";
-import { VoiceState } from "./VoiceState";
-import { Webhook } from "./Webhook";
-import { DmChannelDTO } from "../dtos";
-
-export enum ChannelType {
- GUILD_TEXT = 0, // a text channel within a guild
- DM = 1, // a direct message between users
- GUILD_VOICE = 2, // a voice channel within a guild
- GROUP_DM = 3, // a direct message between multiple users
- GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels
- GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route
- GUILD_STORE = 6, // a channel in which game developers can sell their things
- ENCRYPTED = 7, // end-to-end encrypted channel
- ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel
- TRANSACTIONAL = 9, // event chain style transactional channel
- GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel
- GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel
- GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
- GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience
- DIRECTORY = 14, // guild directory listing channel
- GUILD_FORUM = 15, // forum composed of IM threads
- TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12
- KANBAN = 34, // confluence like kanban board
- VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage)
- CUSTOM_START = 64, // start custom channel types from here
- UNHANDLED = 255 // unhandled unowned pass-through channel type
-}
-
-@Entity("channels")
-export class Channel extends BaseClass {
- @Column()
- created_at: Date;
-
- @Column({ nullable: true })
- name?: string;
-
- @Column({ type: "text", nullable: true })
- icon?: string | null;
-
- @Column({ type: "int" })
- type: ChannelType;
-
- @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, {
- cascade: true,
- orphanedRowAction: "delete",
- })
- recipients?: Recipient[];
-
- @Column({ nullable: true })
- last_message_id?: string;
-
- @Column({ nullable: true })
- @RelationId((channel: Channel) => channel.guild)
- guild_id?: string;
-
- @JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, {
- onDelete: "CASCADE",
- })
- guild: Guild;
-
- @Column({ nullable: true })
- @RelationId((channel: Channel) => channel.parent)
- parent_id: string;
-
- @JoinColumn({ name: "parent_id" })
- @ManyToOne(() => Channel)
- parent?: Channel;
-
- // for group DMs and owned custom channel types
- @Column({ nullable: true })
- @RelationId((channel: Channel) => channel.owner)
- owner_id?: string;
-
- @JoinColumn({ name: "owner_id" })
- @ManyToOne(() => User)
- owner: User;
-
- @Column({ nullable: true })
- last_pin_timestamp?: number;
-
- @Column({ nullable: true })
- default_auto_archive_duration?: number;
-
- @Column({ nullable: true })
- position?: number;
-
- @Column({ type: "simple-json", nullable: true })
- permission_overwrites?: ChannelPermissionOverwrite[];
-
- @Column({ nullable: true })
- video_quality_mode?: number;
-
- @Column({ nullable: true })
- bitrate?: number;
-
- @Column({ nullable: true })
- user_limit?: number;
-
- @Column()
- nsfw: boolean = false;
-
- @Column({ nullable: true })
- rate_limit_per_user?: number;
-
- @Column({ nullable: true })
- topic?: string;
-
- @OneToMany(() => Invite, (invite: Invite) => invite.channel, {
- cascade: true,
- orphanedRowAction: "delete",
- })
- invites?: Invite[];
-
- @Column({ nullable: true })
- retention_policy_id?: string;
-
- @OneToMany(() => Message, (message: Message) => message.channel, {
- cascade: true,
- orphanedRowAction: "delete",
- })
- messages?: Message[];
-
- @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, {
- cascade: true,
- orphanedRowAction: "delete",
- })
- voice_states?: VoiceState[];
-
- @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, {
- cascade: true,
- orphanedRowAction: "delete",
- })
- read_states?: ReadState[];
-
- @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, {
- cascade: true,
- orphanedRowAction: "delete",
- })
- webhooks?: Webhook[];
-
- // TODO: DM channel
- static async createChannel(
- channel: Partial<Channel>,
- user_id: string = "0",
- opts?: {
- keepId?: boolean;
- skipExistsCheck?: boolean;
- skipPermissionCheck?: boolean;
- skipEventEmit?: boolean;
- skipNameChecks?: boolean;
- }
- ) {
- if (!opts?.skipPermissionCheck) {
- // Always check if user has permission first
- const permissions = await getPermission(user_id, channel.guild_id);
- permissions.hasThrow("MANAGE_CHANNELS");
- }
-
- if (!opts?.skipNameChecks) {
- const guild = await Guild.findOneOrFail({ where: { id: channel.guild_id } });
- if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) {
- for (var character of InvisibleCharacters)
- if (channel.name.includes(character))
- throw new HTTPError("Channel name cannot include invalid characters", 403);
-
- // Categories skip these checks on discord.com
- if (channel.type !== ChannelType.GUILD_CATEGORY) {
- if (channel.name.includes(" "))
- throw new HTTPError("Channel name cannot include invalid characters", 403);
-
- if (channel.name.match(/\-\-+/g))
- throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403);
-
- if (channel.name.charAt(0) === "-" ||
- channel.name.charAt(channel.name.length - 1) === "-")
- throw new HTTPError("Channel name cannot start/end with dash.", 403);
- }
- else
- channel.name = channel.name.trim(); //category names are trimmed client side on discord.com
- }
-
- if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) {
- if (!channel.name)
- throw new HTTPError("Channel name cannot be empty.", 403);
- }
- }
-
- switch (channel.type) {
- case ChannelType.GUILD_TEXT:
- case ChannelType.GUILD_NEWS:
- case ChannelType.GUILD_VOICE:
- if (channel.parent_id && !opts?.skipExistsCheck) {
- const exists = await Channel.findOneOrFail({ where: { id: channel.parent_id } });
- if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400);
- if (exists.guild_id !== channel.guild_id)
- throw new HTTPError("The category channel needs to be in the guild");
- }
- break;
- case ChannelType.GUILD_CATEGORY:
- case ChannelType.UNHANDLED:
- break;
- case ChannelType.DM:
- case ChannelType.GROUP_DM:
- throw new HTTPError("You can't create a dm channel in a guild");
- case ChannelType.GUILD_STORE:
- default:
- throw new HTTPError("Not yet supported");
- }
-
- if (!channel.permission_overwrites) channel.permission_overwrites = [];
- // TODO: eagerly auto generate position of all guild channels
-
- channel = {
- ...channel,
- ...(!opts?.keepId && { id: Snowflake.generate() }),
- created_at: new Date(),
- position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0,
- };
-
- await Promise.all([
- Channel.create(channel).save(),
- !opts?.skipEventEmit
- ? emitEvent({
- event: "CHANNEL_CREATE",
- data: channel,
- guild_id: channel.guild_id,
- } as ChannelCreateEvent)
- : Promise.resolve(),
- ]);
-
- return channel;
- }
-
- static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
- recipients = recipients.unique().filter((x) => x !== creator_user_id);
- // TODO: check config for max number of recipients
- /** if you want to disallow note to self channels, uncomment the conditional below
-
- const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
- if (otherRecipientsUsers.length !== recipients.length) {
- throw new HTTPError("Recipient/s not found");
- }
- **/
-
- const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM;
-
- let channel = null;
-
- const channelRecipients = [...recipients, creator_user_id];
-
- const userRecipients = await Recipient.find({
- where: { user_id: creator_user_id },
- relations: ["channel", "channel.recipients"],
- });
-
- for (let ur of userRecipients) {
- let re = ur.channel.recipients!.map((r) => r.user_id);
- if (re.length === channelRecipients.length) {
- if (containsAll(re, channelRecipients)) {
- if (channel == null) {
- channel = ur.channel;
- await ur.assign({ closed: false }).save();
- }
- }
- }
- }
-
- if (channel == null) {
- name = trimSpecial(name);
-
- channel = await Channel.create({
- name,
- type,
- owner_id: undefined,
- created_at: new Date(),
- last_message_id: undefined,
- recipients: channelRecipients.map(
- (x) =>
- Recipient.create({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })
- ),
- nsfw: false,
- }).save();
- }
-
- const channel_dto = await DmChannelDTO.from(channel);
-
- if (type === ChannelType.GROUP_DM) {
- for (let recipient of channel.recipients!) {
- await emitEvent({
- event: "CHANNEL_CREATE",
- data: channel_dto.excludedRecipients([recipient.user_id]),
- user_id: recipient.user_id,
- });
- }
- } else {
- await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });
- }
-
- if (recipients.length === 1) return channel_dto;
- else return channel_dto.excludedRecipients([creator_user_id]);
- }
-
- static async removeRecipientFromChannel(channel: Channel, user_id: string) {
- await Recipient.delete({ channel_id: channel.id, user_id: user_id });
- channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id);
-
- if (channel.recipients?.length === 0) {
- await Channel.deleteChannel(channel);
- await emitEvent({
- event: "CHANNEL_DELETE",
- data: await DmChannelDTO.from(channel, [user_id]),
- user_id: user_id,
- });
- return;
- }
-
- await emitEvent({
- event: "CHANNEL_DELETE",
- data: await DmChannelDTO.from(channel, [user_id]),
- user_id: user_id,
- });
-
- //If the owner leave the server user is the new owner
- if (channel.owner_id === user_id) {
- channel.owner_id = "1"; // The channel is now owned by the server user
- await emitEvent({
- event: "CHANNEL_UPDATE",
- data: await DmChannelDTO.from(channel, [user_id]),
- channel_id: channel.id,
- });
- }
-
- await channel.save();
-
- await emitEvent({
- event: "CHANNEL_RECIPIENT_REMOVE",
- data: {
- channel_id: channel.id,
- user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }),
- },
- channel_id: channel.id,
- } as ChannelRecipientRemoveEvent);
- }
-
- static async deleteChannel(channel: Channel) {
- await Message.delete({ channel_id: channel.id }); //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util
- //TODO before deleting the channel we should check and delete other relations
- await Channel.delete({ id: channel.id });
- }
-
- isDm() {
- return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM;
- }
-
- // Does the channel support sending messages ( eg categories do not )
- isWritable() {
- const disallowedChannelTypes = [
- ChannelType.GUILD_CATEGORY,
- ChannelType.GUILD_STAGE_VOICE,
- ChannelType.VOICELESS_WHITEBOARD,
- ];
- return disallowedChannelTypes.indexOf(this.type) == -1;
- }
-}
-
-export interface ChannelPermissionOverwrite {
- allow: string;
- deny: string;
- id: string;
- type: ChannelPermissionOverwriteType;
-}
-
-export enum ChannelPermissionOverwriteType {
- role = 0,
- member = 1,
- group = 2,
-}
+import {
+ Column,
+ Entity,
+ JoinColumn,
+ ManyToOne,
+ OneToMany,
+ RelationId,
+} from "typeorm";
+import { BaseClass } from "./BaseClass";
+import { Guild } from "./Guild";
+import { PublicUserProjection, User } from "./User";
+import { HTTPError } from "lambert-server";
+import {
+ containsAll,
+ emitEvent,
+ getPermission,
+ Snowflake,
+ trimSpecial,
+ InvisibleCharacters,
+ ChannelTypes,
+} from "../util";
+import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
+import { Recipient } from "./Recipient";
+import { Message } from "./Message";
+import { ReadState } from "./ReadState";
+import { Invite } from "./Invite";
+import { VoiceState } from "./VoiceState";
+import { Webhook } from "./Webhook";
+import { DmChannelDTO } from "../dtos";
+
+export enum ChannelType {
+ GUILD_TEXT = 0, // a text channel within a guild
+ DM = 1, // a direct message between users
+ GUILD_VOICE = 2, // a voice channel within a guild
+ GROUP_DM = 3, // a direct message between multiple users
+ GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels
+ GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route
+ GUILD_STORE = 6, // a channel in which game developers can sell their things
+ ENCRYPTED = 7, // end-to-end encrypted channel
+ ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel
+ TRANSACTIONAL = 9, // event chain style transactional channel
+ GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel
+ GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel
+ GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
+ GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience
+ DIRECTORY = 14, // guild directory listing channel
+ GUILD_FORUM = 15, // forum composed of IM threads
+ TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12
+ KANBAN = 34, // confluence like kanban board
+ VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage)
+ CUSTOM_START = 64, // start custom channel types from here
+ UNHANDLED = 255, // unhandled unowned pass-through channel type
+}
+
+@Entity("channels")
+export class Channel extends BaseClass {
+ @Column()
+ created_at: Date;
+
+ @Column({ nullable: true })
+ name?: string;
+
+ @Column({ type: "text", nullable: true })
+ icon?: string | null;
+
+ @Column({ type: "int" })
+ type: ChannelType;
+
+ @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ })
+ recipients?: Recipient[];
+
+ @Column({ nullable: true })
+ last_message_id?: string;
+
+ @Column({ nullable: true })
+ @RelationId((channel: Channel) => channel.guild)
+ guild_id?: string;
+
+ @JoinColumn({ name: "guild_id" })
+ @ManyToOne(() => Guild, {
+ onDelete: "CASCADE",
+ })
+ guild: Guild;
+
+ @Column({ nullable: true })
+ @RelationId((channel: Channel) => channel.parent)
+ parent_id: string;
+
+ @JoinColumn({ name: "parent_id" })
+ @ManyToOne(() => Channel)
+ parent?: Channel;
+
+ // for group DMs and owned custom channel types
+ @Column({ nullable: true })
+ @RelationId((channel: Channel) => channel.owner)
+ owner_id?: string;
+
+ @JoinColumn({ name: "owner_id" })
+ @ManyToOne(() => User)
+ owner: User;
+
+ @Column({ nullable: true })
+ last_pin_timestamp?: number;
+
+ @Column({ nullable: true })
+ default_auto_archive_duration?: number;
+
+ @Column({ nullable: true })
+ position?: number;
+
+ @Column({ type: "simple-json", nullable: true })
+ permission_overwrites?: ChannelPermissionOverwrite[];
+
+ @Column({ nullable: true })
+ video_quality_mode?: number;
+
+ @Column({ nullable: true })
+ bitrate?: number;
+
+ @Column({ nullable: true })
+ user_limit?: number;
+
+ @Column()
+ nsfw: boolean = false;
+
+ @Column({ nullable: true })
+ rate_limit_per_user?: number;
+
+ @Column({ nullable: true })
+ topic?: string;
+
+ @OneToMany(() => Invite, (invite: Invite) => invite.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ })
+ invites?: Invite[];
+
+ @Column({ nullable: true })
+ retention_policy_id?: string;
+
+ @OneToMany(() => Message, (message: Message) => message.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ })
+ messages?: Message[];
+
+ @OneToMany(
+ () => VoiceState,
+ (voice_state: VoiceState) => voice_state.channel,
+ {
+ cascade: true,
+ orphanedRowAction: "delete",
+ },
+ )
+ voice_states?: VoiceState[];
+
+ @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ })
+ read_states?: ReadState[];
+
+ @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ })
+ webhooks?: Webhook[];
+
+ // TODO: DM channel
+ static async createChannel(
+ channel: Partial<Channel>,
+ user_id: string = "0",
+ opts?: {
+ keepId?: boolean;
+ skipExistsCheck?: boolean;
+ skipPermissionCheck?: boolean;
+ skipEventEmit?: boolean;
+ skipNameChecks?: boolean;
+ },
+ ) {
+ if (!opts?.skipPermissionCheck) {
+ // Always check if user has permission first
+ const permissions = await getPermission(user_id, channel.guild_id);
+ permissions.hasThrow("MANAGE_CHANNELS");
+ }
+
+ if (!opts?.skipNameChecks) {
+ const guild = await Guild.findOneOrFail({
+ where: { id: channel.guild_id },
+ });
+ if (
+ !guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") &&
+ channel.name
+ ) {
+ for (var character of InvisibleCharacters)
+ if (channel.name.includes(character))
+ throw new HTTPError(
+ "Channel name cannot include invalid characters",
+ 403,
+ );
+
+ // Categories skip these checks on discord.com
+ if (channel.type !== ChannelType.GUILD_CATEGORY) {
+ if (channel.name.includes(" "))
+ throw new HTTPError(
+ "Channel name cannot include invalid characters",
+ 403,
+ );
+
+ if (channel.name.match(/\-\-+/g))
+ throw new HTTPError(
+ "Channel name cannot include multiple adjacent dashes.",
+ 403,
+ );
+
+ if (
+ channel.name.charAt(0) === "-" ||
+ channel.name.charAt(channel.name.length - 1) === "-"
+ )
+ throw new HTTPError(
+ "Channel name cannot start/end with dash.",
+ 403,
+ );
+ } else channel.name = channel.name.trim(); //category names are trimmed client side on discord.com
+ }
+
+ if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) {
+ if (!channel.name)
+ throw new HTTPError("Channel name cannot be empty.", 403);
+ }
+ }
+
+ switch (channel.type) {
+ case ChannelType.GUILD_TEXT:
+ case ChannelType.GUILD_NEWS:
+ case ChannelType.GUILD_VOICE:
+ if (channel.parent_id && !opts?.skipExistsCheck) {
+ const exists = await Channel.findOneOrFail({
+ where: { id: channel.parent_id },
+ });
+ if (!exists)
+ throw new HTTPError(
+ "Parent id channel doesn't exist",
+ 400,
+ );
+ if (exists.guild_id !== channel.guild_id)
+ throw new HTTPError(
+ "The category channel needs to be in the guild",
+ );
+ }
+ break;
+ case ChannelType.GUILD_CATEGORY:
+ case ChannelType.UNHANDLED:
+ break;
+ case ChannelType.DM:
+ case ChannelType.GROUP_DM:
+ throw new HTTPError("You can't create a dm channel in a guild");
+ case ChannelType.GUILD_STORE:
+ default:
+ throw new HTTPError("Not yet supported");
+ }
+
+ if (!channel.permission_overwrites) channel.permission_overwrites = [];
+ // TODO: eagerly auto generate position of all guild channels
+
+ channel = {
+ ...channel,
+ ...(!opts?.keepId && { id: Snowflake.generate() }),
+ created_at: new Date(),
+ position:
+ (channel.type === ChannelType.UNHANDLED
+ ? 0
+ : channel.position) || 0,
+ };
+
+ await Promise.all([
+ Channel.create(channel).save(),
+ !opts?.skipEventEmit
+ ? emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel,
+ guild_id: channel.guild_id,
+ } as ChannelCreateEvent)
+ : Promise.resolve(),
+ ]);
+
+ return channel;
+ }
+
+ static async createDMChannel(
+ recipients: string[],
+ creator_user_id: string,
+ name?: string,
+ ) {
+ recipients = recipients.unique().filter((x) => x !== creator_user_id);
+ // TODO: check config for max number of recipients
+ /** if you want to disallow note to self channels, uncomment the conditional below
+
+ const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
+ if (otherRecipientsUsers.length !== recipients.length) {
+ throw new HTTPError("Recipient/s not found");
+ }
+ **/
+
+ const type =
+ recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM;
+
+ let channel = null;
+
+ const channelRecipients = [...recipients, creator_user_id];
+
+ const userRecipients = await Recipient.find({
+ where: { user_id: creator_user_id },
+ relations: ["channel", "channel.recipients"],
+ });
+
+ for (let ur of userRecipients) {
+ let re = ur.channel.recipients!.map((r) => r.user_id);
+ if (re.length === channelRecipients.length) {
+ if (containsAll(re, channelRecipients)) {
+ if (channel == null) {
+ channel = ur.channel;
+ await ur.assign({ closed: false }).save();
+ }
+ }
+ }
+ }
+
+ if (channel == null) {
+ name = trimSpecial(name);
+
+ channel = await Channel.create({
+ name,
+ type,
+ owner_id: undefined,
+ created_at: new Date(),
+ last_message_id: undefined,
+ recipients: channelRecipients.map((x) =>
+ Recipient.create({
+ user_id: x,
+ closed: !(
+ type === ChannelType.GROUP_DM ||
+ x === creator_user_id
+ ),
+ }),
+ ),
+ nsfw: false,
+ }).save();
+ }
+
+ const channel_dto = await DmChannelDTO.from(channel);
+
+ if (type === ChannelType.GROUP_DM) {
+ for (let recipient of channel.recipients!) {
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel_dto.excludedRecipients([recipient.user_id]),
+ user_id: recipient.user_id,
+ });
+ }
+ } else {
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel_dto,
+ user_id: creator_user_id,
+ });
+ }
+
+ if (recipients.length === 1) return channel_dto;
+ else return channel_dto.excludedRecipients([creator_user_id]);
+ }
+
+ static async removeRecipientFromChannel(channel: Channel, user_id: string) {
+ await Recipient.delete({ channel_id: channel.id, user_id: user_id });
+ channel.recipients = channel.recipients?.filter(
+ (r) => r.user_id !== user_id,
+ );
+
+ if (channel.recipients?.length === 0) {
+ await Channel.deleteChannel(channel);
+ await emitEvent({
+ event: "CHANNEL_DELETE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ user_id: user_id,
+ });
+ return;
+ }
+
+ await emitEvent({
+ event: "CHANNEL_DELETE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ user_id: user_id,
+ });
+
+ //If the owner leave the server user is the new owner
+ if (channel.owner_id === user_id) {
+ channel.owner_id = "1"; // The channel is now owned by the server user
+ await emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ channel_id: channel.id,
+ });
+ }
+
+ await channel.save();
+
+ await emitEvent({
+ event: "CHANNEL_RECIPIENT_REMOVE",
+ data: {
+ channel_id: channel.id,
+ user: await User.findOneOrFail({
+ where: { id: user_id },
+ select: PublicUserProjection,
+ }),
+ },
+ channel_id: channel.id,
+ } as ChannelRecipientRemoveEvent);
+ }
+
+ static async deleteChannel(channel: Channel) {
+ await Message.delete({ channel_id: channel.id }); //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util
+ //TODO before deleting the channel we should check and delete other relations
+ await Channel.delete({ id: channel.id });
+ }
+
+ isDm() {
+ return (
+ this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM
+ );
+ }
+
+ // Does the channel support sending messages ( eg categories do not )
+ isWritable() {
+ const disallowedChannelTypes = [
+ ChannelType.GUILD_CATEGORY,
+ ChannelType.GUILD_STAGE_VOICE,
+ ChannelType.VOICELESS_WHITEBOARD,
+ ];
+ return disallowedChannelTypes.indexOf(this.type) == -1;
+ }
+}
+
+export interface ChannelPermissionOverwrite {
+ allow: string;
+ deny: string;
+ id: string;
+ type: ChannelPermissionOverwriteType;
+}
+
+export enum ChannelPermissionOverwriteType {
+ role = 0,
+ member = 1,
+ group = 2,
+}
diff --git a/src/util/entities/ClientRelease.ts b/src/util/entities/ClientRelease.ts
index c5afd307..2723ab67 100644
--- a/src/util/entities/ClientRelease.ts
+++ b/src/util/entities/ClientRelease.ts
@@ -1,4 +1,4 @@
-import { Column, Entity} from "typeorm";
+import { Column, Entity } from "typeorm";
import { BaseClass } from "./BaseClass";
@Entity("client_release")
diff --git a/src/util/entities/Config.ts b/src/util/entities/Config.ts
index 9aabc1a8..cd7a6923 100644
--- a/src/util/entities/Config.ts
+++ b/src/util/entities/Config.ts
@@ -191,17 +191,17 @@ export interface ConfigValue {
allowTemplateCreation: Boolean;
allowDiscordTemplates: Boolean;
allowRaws: Boolean;
- },
+ };
client: {
useTestClient: Boolean;
releases: {
useLocalRelease: Boolean; //TODO
upstreamVersion: string;
};
- },
+ };
metrics: {
timeout: number;
- },
+ };
sentry: {
enabled: boolean;
endpoint: string;
@@ -230,7 +230,8 @@ export const DefaultConfigOptions: ConfigValue = {
},
general: {
instanceName: "Fosscord Instance",
- instanceDescription: "This is a Fosscord instance made in pre-release days",
+ instanceDescription:
+ "This is a Fosscord instance made in pre-release days",
frontPage: null,
tosPage: null,
correspondenceEmail: "noreply@localhost.local",
@@ -318,8 +319,9 @@ export const DefaultConfigOptions: ConfigValue = {
sitekey: null,
secret: null,
},
- ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9",
- defaultRights: "30644591655936", // See util/scripts/rights.js
+ ipdataApiKey:
+ "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9",
+ defaultRights: "30644591655936", // See util/scripts/rights.js
},
login: {
requireCaptcha: false,
@@ -395,22 +397,23 @@ export const DefaultConfigOptions: ConfigValue = {
enabled: true,
allowTemplateCreation: true,
allowDiscordTemplates: true,
- allowRaws: false
+ allowRaws: false,
},
client: {
useTestClient: true,
releases: {
useLocalRelease: true,
- upstreamVersion: "0.0.264"
- }
+ upstreamVersion: "0.0.264",
+ },
},
metrics: {
- timeout: 30000
+ timeout: 30000,
},
sentry: {
enabled: false,
- endpoint: "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6",
+ endpoint:
+ "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6",
traceSampleRate: 1.0,
- environment: hostname()
- }
-};
\ No newline at end of file
+ environment: hostname(),
+ },
+};
diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts
index 09ae30ab..a893ff34 100644
--- a/src/util/entities/ConnectedAccount.ts
+++ b/src/util/entities/ConnectedAccount.ts
@@ -2,7 +2,8 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
-export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verified"> {}
+export interface PublicConnectedAccount
+ extends Pick<ConnectedAccount, "name" | "type" | "verified"> {}
@Entity("connected_accounts")
export class ConnectedAccount extends BaseClass {
diff --git a/src/util/entities/Emoji.ts b/src/util/entities/Emoji.ts
index a3615b7d..0aa640b5 100644
--- a/src/util/entities/Emoji.ts
+++ b/src/util/entities/Emoji.ts
@@ -40,7 +40,7 @@ export class Emoji extends BaseClass {
@Column({ type: "simple-array" })
roles: string[]; // roles this emoji is whitelisted to (new discord feature?)
-
+
@Column({ type: "simple-array", nullable: true })
groups: string[]; // user groups this emoji is whitelisted to (Fosscord extension)
}
diff --git a/src/util/entities/Encryption.ts b/src/util/entities/Encryption.ts
index b597b90a..4c427b32 100644
--- a/src/util/entities/Encryption.ts
+++ b/src/util/entities/Encryption.ts
@@ -1,9 +1,23 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
+import {
+ Column,
+ Entity,
+ JoinColumn,
+ ManyToOne,
+ OneToMany,
+ RelationId,
+} from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { PublicUserProjection, User } from "./User";
import { HTTPError } from "lambert-server";
-import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util";
+import {
+ containsAll,
+ emitEvent,
+ getPermission,
+ Snowflake,
+ trimSpecial,
+ InvisibleCharacters,
+} from "../util";
import { BitField, BitFieldResolvable, BitFlag } from "../util/BitField";
import { Recipient } from "./Recipient";
import { Message } from "./Message";
@@ -13,7 +27,6 @@ import { DmChannelDTO } from "../dtos";
@Entity("security_settings")
export class SecuritySettings extends BaseClass {
-
@Column({ nullable: true })
guild_id: string;
@@ -31,5 +44,4 @@ export class SecuritySettings extends BaseClass {
@Column({ nullable: true })
used_since_message: string;
-
}
diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts
index 2ce7c213..8854fec0 100644
--- a/src/util/entities/Guild.ts
+++ b/src/util/entities/Guild.ts
@@ -1,4 +1,13 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm";
+import {
+ Column,
+ Entity,
+ JoinColumn,
+ ManyToMany,
+ ManyToOne,
+ OneToMany,
+ OneToOne,
+ RelationId,
+} from "typeorm";
import { Config, handleFile, Snowflake } from "..";
import { Ban } from "./Ban";
import { BaseClass } from "./BaseClass";
@@ -86,7 +95,7 @@ export class Guild extends BaseClass {
//TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features
@Column({ nullable: true })
- primary_category_id?: string; // TODO: this was number?
+ primary_category_id?: string; // TODO: this was number?
@Column({ nullable: true })
icon?: string;
@@ -269,7 +278,7 @@ export class Guild extends BaseClass {
@Column()
nsfw: boolean;
-
+
// TODO: nested guilds
@Column({ nullable: true })
parent?: string;
@@ -332,10 +341,13 @@ export class Guild extends BaseClass {
permissions: String("2251804225"),
position: 0,
icon: undefined,
- unicode_emoji: undefined
+ unicode_emoji: undefined,
}).save();
- if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general", nsfw: false }];
+ if (!body.channels || !body.channels.length)
+ body.channels = [
+ { id: "01", type: 0, name: "general", nsfw: false },
+ ];
const ids = new Map();
@@ -345,17 +357,23 @@ export class Guild extends BaseClass {
}
});
- for (const channel of body.channels?.sort((a, b) => (a.parent_id ? 1 : -1))) {
+ for (const channel of body.channels?.sort((a, b) =>
+ a.parent_id ? 1 : -1,
+ )) {
var id = ids.get(channel.id) || Snowflake.generate();
var parent_id = ids.get(channel.parent_id);
- await Channel.createChannel({ ...channel, guild_id, id, parent_id }, body.owner_id, {
- keepId: true,
- skipExistsCheck: true,
- skipPermissionCheck: true,
- skipEventEmit: true,
- });
+ await Channel.createChannel(
+ { ...channel, guild_id, id, parent_id },
+ body.owner_id,
+ {
+ keepId: true,
+ skipExistsCheck: true,
+ skipPermissionCheck: true,
+ skipEventEmit: true,
+ },
+ );
}
return guild;
diff --git a/src/util/entities/Invite.ts b/src/util/entities/Invite.ts
index 4f36f247..90dec92a 100644
--- a/src/util/entities/Invite.ts
+++ b/src/util/entities/Invite.ts
@@ -1,4 +1,11 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId, PrimaryColumn } from "typeorm";
+import {
+ Column,
+ Entity,
+ JoinColumn,
+ ManyToOne,
+ RelationId,
+ PrimaryColumn,
+} from "typeorm";
import { Member } from "./Member";
import { BaseClassWithoutId } from "./BaseClass";
import { Channel } from "./Channel";
@@ -76,7 +83,8 @@ export class Invite extends BaseClassWithoutId {
static async joinGuild(user_id: string, code: string) {
const invite = await Invite.findOneOrFail({ where: { code } });
- if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code });
+ if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0)
+ await Invite.delete({ code });
else await invite.save();
await Member.addToGuild(user_id, invite.guild_id);
diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts
index 7d1346ba..f2762adc 100644
--- a/src/util/entities/Member.ts
+++ b/src/util/entities/Member.ts
@@ -22,7 +22,6 @@ import {
GuildMemberRemoveEvent,
GuildMemberUpdateEvent,
MessageCreateEvent,
-
} from "../interfaces";
import { HTTPError } from "lambert-server";
import { Role } from "./Role";
@@ -126,19 +125,34 @@ export class Member extends BaseClassWithoutId {
if (this.nick) {
this.nick = this.nick.split("\n").join("");
this.nick = this.nick.split("\t").join("");
- if (BannedWords.find(this.nick)) throw FieldErrors({ nick: { message: "Bad nickname", code: "INVALID_NICKNAME" } });
+ if (BannedWords.find(this.nick))
+ throw FieldErrors({
+ nick: { message: "Bad nickname", code: "INVALID_NICKNAME" },
+ });
}
}
static async IsInGuildOrFail(user_id: string, guild_id: string) {
- if (await Member.count({ where: { id: user_id, guild: { id: guild_id } } })) return true;
+ if (
+ await Member.count({
+ where: { id: user_id, guild: { id: guild_id } },
+ })
+ )
+ return true;
throw new HTTPError("You are not member of this guild", 403);
}
static async removeFromGuild(user_id: string, guild_id: string) {
- const guild = await Guild.findOneOrFail({ select: ["owner_id"], where: { id: guild_id } });
- if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild");
- const member = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user"] });
+ const guild = await Guild.findOneOrFail({
+ select: ["owner_id"],
+ where: { id: guild_id },
+ });
+ if (guild.owner_id === user_id)
+ throw new Error("The owner cannot be removed of the guild");
+ const member = await Member.findOneOrFail({
+ where: { id: user_id, guild_id },
+ relations: ["user"],
+ });
// use promise all to execute all promises at the same time -> save time
return Promise.all([
@@ -169,9 +183,12 @@ export class Member extends BaseClassWithoutId {
where: { id: user_id, guild_id },
relations: ["user", "roles"], // we don't want to load the role objects just the ids
//@ts-ignore
- select: ["index", "roles.id"], // TODO fix type
+ select: ["index", "roles.id"], // TODO fix type
+ }),
+ Role.findOneOrFail({
+ where: { id: role_id, guild_id },
+ select: ["id"],
}),
- Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }),
]);
member.roles.push(Role.create({ id: role_id }));
@@ -189,7 +206,11 @@ export class Member extends BaseClassWithoutId {
]);
}
- static async removeRole(user_id: string, guild_id: string, role_id: string) {
+ static async removeRole(
+ user_id: string,
+ guild_id: string,
+ role_id: string,
+ ) {
const [member] = await Promise.all([
Member.findOneOrFail({
where: { id: user_id, guild_id },
@@ -215,7 +236,11 @@ export class Member extends BaseClassWithoutId {
]);
}
- static async changeNickname(user_id: string, guild_id: string, nickname: string) {
+ static async changeNickname(
+ user_id: string,
+ guild_id: string,
+ nickname: string,
+ ) {
const member = await Member.findOneOrFail({
where: {
id: user_id,
@@ -249,7 +274,10 @@ export class Member extends BaseClassWithoutId {
const { maxGuilds } = Config.get().limits.user;
const guild_count = await Member.count({ where: { id: user_id } });
if (guild_count >= maxGuilds) {
- throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403);
+ throw new HTTPError(
+ `You are at the ${maxGuilds} server limit.`,
+ 403,
+ );
}
const guild = await Guild.findOneOrFail({
@@ -259,7 +287,11 @@ export class Member extends BaseClassWithoutId {
relations: [...PublicGuildRelations, "system_channel"],
});
- if (await Member.count({ where: { id: user.id, guild: { id: guild_id } } }))
+ if (
+ await Member.count({
+ where: { id: user.id, guild: { id: guild_id } },
+ })
+ )
throw new HTTPError("You are already a member of this guild", 400);
const member = {
@@ -268,7 +300,7 @@ export class Member extends BaseClassWithoutId {
nick: undefined,
roles: [guild_id], // @everyone role
joined_at: new Date(),
- premium_since: (new Date()).getTime(),
+ premium_since: new Date().getTime(),
deaf: false,
mute: false,
pending: false,
@@ -339,7 +371,11 @@ export class Member extends BaseClassWithoutId {
});
await Promise.all([
message.save(),
- emitEvent({ event: "MESSAGE_CREATE", channel_id: message.channel_id, data: message } as MessageCreateEvent)
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: message.channel_id,
+ data: message,
+ } as MessageCreateEvent),
]);
}
}
@@ -362,7 +398,7 @@ export interface UserGuildSettings {
channel_overrides: {
[channel_id: string]: ChannelOverride;
- } | null,
+ } | null;
message_notifications: number;
mobile_push: boolean;
mute_config: MuteConfig | null;
@@ -389,7 +425,7 @@ export const DefaultUserGuildSettings: UserGuildSettings = {
notify_highlights: 0,
suppress_everyone: false,
suppress_roles: false,
- version: 453, // ?
+ version: 453, // ?
guild_id: null,
};
diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts
index be790502..a52b4785 100644
--- a/src/util/entities/Message.ts
+++ b/src/util/entities/Message.ts
@@ -51,7 +51,7 @@ export enum MessageType {
SELF_COMMAND_SCRIPT = 43, // self command scripts
ENCRYPTION = 50,
CUSTOM_START = 63,
- UNHANDLED = 255
+ UNHANDLED = 255,
}
@Entity("messages")
@@ -115,7 +115,10 @@ export class Message extends BaseClass {
@ManyToOne(() => Application)
application?: Application;
- @Column({ nullable: true, type: process.env.PRODUCTION ? "longtext" : undefined })
+ @Column({
+ nullable: true,
+ type: process.env.PRODUCTION ? "longtext" : undefined,
+ })
content?: string;
@Column()
@@ -147,10 +150,14 @@ export class Message extends BaseClass {
@ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" })
sticker_items?: Sticker[];
- @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, {
- cascade: true,
- orphanedRowAction: "delete",
- })
+ @OneToMany(
+ () => Attachment,
+ (attachment: Attachment) => attachment.message,
+ {
+ cascade: true,
+ orphanedRowAction: "delete",
+ },
+ )
attachments?: Attachment[];
@Column({ type: "simple-json" })
@@ -176,7 +183,7 @@ export class Message extends BaseClass {
@Column({ nullable: true })
flags?: string;
-
+
@Column({ type: "simple-json", nullable: true })
message_reference?: {
message_id: string;
@@ -204,7 +211,11 @@ export class Message extends BaseClass {
@BeforeInsert()
validate() {
if (this.content) {
- if (BannedWords.find(this.content)) throw new HTTPError("Message was blocked by automatic moderation", 200000);
+ if (BannedWords.find(this.content))
+ throw new HTTPError(
+ "Message was blocked by automatic moderation",
+ 200000,
+ );
}
}
}
diff --git a/src/util/entities/Migration.ts b/src/util/entities/Migration.ts
index 3f39ae72..f4e54eae 100644
--- a/src/util/entities/Migration.ts
+++ b/src/util/entities/Migration.ts
@@ -1,7 +1,14 @@
-import { Column, Entity, ObjectIdColumn, PrimaryGeneratedColumn } from "typeorm";
+import {
+ Column,
+ Entity,
+ ObjectIdColumn,
+ PrimaryGeneratedColumn,
+} from "typeorm";
import { BaseClassWithoutId } from ".";
-export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb")
+export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith(
+ "mongodb",
+)
? ObjectIdColumn
: PrimaryGeneratedColumn;
diff --git a/src/util/entities/Note.ts b/src/util/entities/Note.ts
index 36017c5e..b3ac45ee 100644
--- a/src/util/entities/Note.ts
+++ b/src/util/entities/Note.ts
@@ -15,4 +15,4 @@ export class Note extends BaseClass {
@Column()
content: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/entities/ReadState.ts b/src/util/entities/ReadState.ts
index b915573b..53ed5589 100644
--- a/src/util/entities/ReadState.ts
+++ b/src/util/entities/ReadState.ts
@@ -1,4 +1,11 @@
-import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import {
+ Column,
+ Entity,
+ Index,
+ JoinColumn,
+ ManyToOne,
+ RelationId,
+} from "typeorm";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
import { Message } from "./Message";
@@ -33,8 +40,8 @@ export class ReadState extends BaseClass {
// fully read marker
@Column({ nullable: true })
- last_message_id: string;
-
+ last_message_id: string;
+
// public read receipt
@Column({ nullable: true })
public_ack: string;
diff --git a/src/util/entities/Relationship.ts b/src/util/entities/Relationship.ts
index c3592c76..25b52757 100644
--- a/src/util/entities/Relationship.ts
+++ b/src/util/entities/Relationship.ts
@@ -1,4 +1,11 @@
-import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import {
+ Column,
+ Entity,
+ Index,
+ JoinColumn,
+ ManyToOne,
+ RelationId,
+} from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
diff --git a/src/util/entities/StickerPack.ts b/src/util/entities/StickerPack.ts
index ec8c69a2..04d74bac 100644
--- a/src/util/entities/StickerPack.ts
+++ b/src/util/entities/StickerPack.ts
@@ -1,4 +1,12 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm";
+import {
+ Column,
+ Entity,
+ JoinColumn,
+ ManyToOne,
+ OneToMany,
+ OneToOne,
+ RelationId,
+} from "typeorm";
import { Sticker } from ".";
import { BaseClass } from "./BaseClass";
diff --git a/src/util/entities/Team.ts b/src/util/entities/Team.ts
index 22140b7f..8f410bb4 100644
--- a/src/util/entities/Team.ts
+++ b/src/util/entities/Team.ts
@@ -1,4 +1,12 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
+import {
+ Column,
+ Entity,
+ JoinColumn,
+ ManyToMany,
+ ManyToOne,
+ OneToMany,
+ RelationId,
+} from "typeorm";
import { BaseClass } from "./BaseClass";
import { TeamMember } from "./TeamMember";
import { User } from "./User";
diff --git a/src/util/entities/TeamMember.ts b/src/util/entities/TeamMember.ts
index b726e1e8..3f4a0422 100644
--- a/src/util/entities/TeamMember.ts
+++ b/src/util/entities/TeamMember.ts
@@ -20,9 +20,13 @@ export class TeamMember extends BaseClass {
team_id: string;
@JoinColumn({ name: "team_id" })
- @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, {
- onDelete: "CASCADE",
- })
+ @ManyToOne(
+ () => require("./Team").Team,
+ (team: import("./Team").Team) => team.members,
+ {
+ onDelete: "CASCADE",
+ },
+ )
team: import("./Team").Team;
@Column({ nullable: true })
diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts
index 84a8a674..1389a424 100644
--- a/src/util/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -1,9 +1,24 @@
-import { BeforeInsert, BeforeUpdate, Column, Entity, FindOneOptions, JoinColumn, OneToMany } from "typeorm";
+import {
+ BeforeInsert,
+ BeforeUpdate,
+ Column,
+ Entity,
+ FindOneOptions,
+ JoinColumn,
+ OneToMany,
+} from "typeorm";
import { BaseClass } from "./BaseClass";
import { BitField } from "../util/BitField";
import { Relationship } from "./Relationship";
import { ConnectedAccount } from "./ConnectedAccount";
-import { Config, FieldErrors, Snowflake, trimSpecial, BannedWords, adjustEmail } from "..";
+import {
+ Config,
+ FieldErrors,
+ Snowflake,
+ trimSpecial,
+ BannedWords,
+ adjustEmail,
+} from "..";
import { Member, Session } from ".";
export enum PublicUserEnum {
@@ -38,7 +53,7 @@ export enum PrivateUserEnum {
export type PrivateUserKeys = keyof typeof PrivateUserEnum | PublicUserKeys;
export const PublicUserProjection = Object.values(PublicUserEnum).filter(
- (x) => typeof x === "string"
+ (x) => typeof x === "string",
) as PublicUserKeys[];
export const PrivateUserProjection = [
...PublicUserProjection,
@@ -48,7 +63,7 @@ export const PrivateUserProjection = [
// Private user data that should never get sent to the client
export type PublicUser = Pick<User, PublicUserKeys>;
-export interface UserPublic extends Pick<User, PublicUserKeys> { }
+export interface UserPublic extends Pick<User, PublicUserKeys> {}
export interface UserPrivate extends Pick<User, PrivateUserKeys> {
locale: string;
@@ -144,17 +159,25 @@ export class User extends BaseClass {
sessions: Session[];
@JoinColumn({ name: "relationship_ids" })
- @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, {
- cascade: true,
- orphanedRowAction: "delete",
- })
+ @OneToMany(
+ () => Relationship,
+ (relationship: Relationship) => relationship.from,
+ {
+ cascade: true,
+ orphanedRowAction: "delete",
+ },
+ )
relationships: Relationship[];
@JoinColumn({ name: "connected_account_ids" })
- @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, {
- cascade: true,
- orphanedRowAction: "delete",
- })
+ @OneToMany(
+ () => ConnectedAccount,
+ (account: ConnectedAccount) => account.user,
+ {
+ cascade: true,
+ orphanedRowAction: "delete",
+ },
+ )
connected_accounts: ConnectedAccount[];
@Column({ type: "simple-json", select: false })
@@ -177,16 +200,43 @@ export class User extends BaseClass {
@BeforeInsert()
validate() {
this.email = adjustEmail(this.email);
- if (!this.email) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } });
- if (!this.email.match(/([a-z\d.-]{3,})@([a-z\d.-]+).([a-z]{2,})/g)) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } });
+ if (!this.email)
+ throw FieldErrors({
+ email: { message: "Invalid email", code: "EMAIL_INVALID" },
+ });
+ if (!this.email.match(/([a-z\d.-]{3,})@([a-z\d.-]+).([a-z]{2,})/g))
+ throw FieldErrors({
+ email: { message: "Invalid email", code: "EMAIL_INVALID" },
+ });
const discrim = Number(this.discriminator);
- if (this.discriminator.length > 4) throw FieldErrors({ email: { message: "Discriminator cannot be more than 4 digits.", code: "DISCRIMINATOR_INVALID" } });
- if (isNaN(discrim)) throw FieldErrors({ email: { message: "Discriminator must be a number.", code: "DISCRIMINATOR_INVALID" } });
- if (discrim <= 0 || discrim >= 10000) throw FieldErrors({ email: { message: "Discriminator must be a number.", code: "DISCRIMINATOR_INVALID" } });
+ if (this.discriminator.length > 4)
+ throw FieldErrors({
+ email: {
+ message: "Discriminator cannot be more than 4 digits.",
+ code: "DISCRIMINATOR_INVALID",
+ },
+ });
+ if (isNaN(discrim))
+ throw FieldErrors({
+ email: {
+ message: "Discriminator must be a number.",
+ code: "DISCRIMINATOR_INVALID",
+ },
+ });
+ if (discrim <= 0 || discrim >= 10000)
+ throw FieldErrors({
+ email: {
+ message: "Discriminator must be a number.",
+ code: "DISCRIMINATOR_INVALID",
+ },
+ });
this.discriminator = discrim.toString().padStart(4, "0");
- if (BannedWords.find(this.username)) throw FieldErrors({ username: { message: "Bad username", code: "INVALID_USERNAME" } });
+ if (BannedWords.find(this.username))
+ throw FieldErrors({
+ username: { message: "Bad username", code: "INVALID_USERNAME" },
+ });
}
toPublicUser() {
@@ -202,17 +252,25 @@ export class User extends BaseClass {
where: { id: user_id },
...opts,
//@ts-ignore
- select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix
+ select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix
});
}
- private static async generateDiscriminator(username: string): Promise<string | undefined> {
+ private static async generateDiscriminator(
+ username: string,
+ ): Promise<string | undefined> {
if (Config.get().register.incrementingDiscriminators) {
// discriminator will be incrementally generated
// First we need to figure out the currently highest discrimnator for the given username and then increment it
- const users = await User.find({ where: { username }, select: ["discriminator"] });
- const highestDiscriminator = Math.max(0, ...users.map((u) => Number(u.discriminator)));
+ const users = await User.find({
+ where: { username },
+ select: ["discriminator"],
+ });
+ const highestDiscriminator = Math.max(
+ 0,
+ ...users.map((u) => Number(u.discriminator)),
+ );
const discriminator = highestDiscriminator + 1;
if (discriminator >= 10000) {
@@ -226,8 +284,13 @@ export class User extends BaseClass {
// randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists
// TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database?
for (let tries = 0; tries < 5; tries++) {
- const discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0");
- const exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"] });
+ const discriminator = Math.randomIntBetween(1, 9999)
+ .toString()
+ .padStart(4, "0");
+ const exists = await User.findOne({
+ where: { discriminator, username: username },
+ select: ["id"],
+ });
if (!exists) return discriminator;
}
@@ -265,7 +328,8 @@ export class User extends BaseClass {
// TODO: save date_of_birth
// appearently discord doesn't save the date of birth and just calculate if nsfw is allowed
// if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false
- const language = req.language === "en" ? "en-US" : req.language || "en-US";
+ const language =
+ req.language === "en" ? "en-US" : req.language || "en-US";
const user = User.create({
created_at: new Date(),
@@ -295,8 +359,8 @@ export class User extends BaseClass {
},
settings: { ...defaultSettings, locale: language },
purchased_flags: 5, // TODO: idk what the values for this are
- premium_usage_flags: 2, // TODO: idk what the values for this are
- extended_settings: "", // TODO: was {}
+ premium_usage_flags: 2, // TODO: idk what the values for this are
+ extended_settings: "", // TODO: was {}
fingerprints: [],
});
@@ -305,7 +369,7 @@ export class User extends BaseClass {
setImmediate(async () => {
if (Config.get().guild.autoJoin.enabled) {
for (const guild of Config.get().guild.autoJoin.guilds || []) {
- await Member.addToGuild(user.id, guild).catch((e) => { });
+ await Member.addToGuild(user.id, guild).catch((e) => {});
}
}
});
@@ -372,7 +436,7 @@ export interface UserSettings {
disable_games_tab: boolean;
enable_tts_command: boolean;
explicit_content_filter: number;
- friend_source_flags: { all: boolean; };
+ friend_source_flags: { all: boolean };
gateway_connected: boolean;
gif_auto_play: boolean;
// every top guild is displayed as a "folder"
diff --git a/src/util/entities/index.ts b/src/util/entities/index.ts
index 49793810..c439a4b7 100644
--- a/src/util/entities/index.ts
+++ b/src/util/entities/index.ts
@@ -29,4 +29,4 @@ export * from "./VoiceState";
export * from "./Webhook";
export * from "./ClientRelease";
export * from "./BackupCodes";
-export * from "./Note";
\ No newline at end of file
+export * from "./Note";
diff --git a/src/util/imports/OrmUtils.ts b/src/util/imports/OrmUtils.ts
index 68a1932c..26652db0 100644
--- a/src/util/imports/OrmUtils.ts
+++ b/src/util/imports/OrmUtils.ts
@@ -9,7 +9,12 @@ export class OrmUtils {
return !item.constructor || item.constructor === Object;
}
- private static mergeArrayKey(target: any, key: number, value: any, memo: Map<any, any>) {
+ private static mergeArrayKey(
+ target: any,
+ key: number,
+ value: any,
+ memo: Map<any, any>,
+ ) {
// Have we seen this before? Prevent infinite recursion.
if (memo.has(value)) {
target[key] = memo.get(value);
@@ -38,7 +43,12 @@ export class OrmUtils {
memo.delete(value);
}
- private static mergeObjectKey(target: any, key: string, value: any, memo: Map<any, any>) {
+ private static mergeObjectKey(
+ target: any,
+ key: string,
+ value: any,
+ memo: Map<any, any>,
+ ) {
// Have we seen this before? Prevent infinite recursion.
if (memo.has(value)) {
Object.assign(target, { [key]: memo.get(value) });
@@ -67,7 +77,11 @@ export class OrmUtils {
memo.delete(value);
}
- private static merge(target: any, source: any, memo: Map<any, any> = new Map()): any {
+ private static merge(
+ target: any,
+ source: any,
+ memo: Map<any, any> = new Map(),
+ ): any {
if (Array.isArray(target) && Array.isArray(source)) {
for (let key = 0; key < source.length; key++) {
this.mergeArrayKey(target, key, source[key], memo);
@@ -93,4 +107,4 @@ export class OrmUtils {
return target;
}
-}
\ No newline at end of file
+}
diff --git a/src/util/imports/index.ts b/src/util/imports/index.ts
index 5d9bfb5f..823151d9 100644
--- a/src/util/imports/index.ts
+++ b/src/util/imports/index.ts
@@ -1 +1 @@
-export * from "./OrmUtils";
\ No newline at end of file
+export * from "./OrmUtils";
diff --git a/src/util/index.ts b/src/util/index.ts
index 385070a3..3773c275 100644
--- a/src/util/index.ts
+++ b/src/util/index.ts
@@ -5,4 +5,4 @@ export * from "./interfaces/index";
export * from "./entities/index";
export * from "./dtos/index";
export * from "./schemas";
-export * from "./imports";
\ No newline at end of file
+export * from "./imports";
diff --git a/src/util/interfaces/Activity.ts b/src/util/interfaces/Activity.ts
index 9912e197..279ee40f 100644
--- a/src/util/interfaces/Activity.ts
+++ b/src/util/interfaces/Activity.ts
@@ -36,7 +36,8 @@ export interface Activity {
id?: string;
sync_id?: string;
- metadata?: { // spotify
+ metadata?: {
+ // spotify
context_uri?: string;
album_id: string;
artist_ids: string[];
diff --git a/src/util/interfaces/Event.ts b/src/util/interfaces/Event.ts
index 59f995db..472fd572 100644
--- a/src/util/interfaces/Event.ts
+++ b/src/util/interfaces/Event.ts
@@ -75,7 +75,7 @@ export interface ReadyEventData {
number,
[[number, { e: number; s: number }[]]],
[number, [[number, [number, number]]]],
- { b: number; k: bigint[] }[]
+ { b: number; k: bigint[] }[],
][];
guild_join_requests?: any[]; // ? what is this? this is new
shard?: [number, number];
diff --git a/src/util/migrations/1633864260873-EmojiRoles.ts b/src/util/migrations/1633864260873-EmojiRoles.ts
index f0d709f2..31ced96b 100644
--- a/src/util/migrations/1633864260873-EmojiRoles.ts
+++ b/src/util/migrations/1633864260873-EmojiRoles.ts
@@ -4,10 +4,14 @@ export class EmojiRoles1633864260873 implements MigrationInterface {
name = "EmojiRoles1633864260873";
public async up(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.query(`ALTER TABLE "emojis" ADD "roles" text NOT NULL DEFAULT ''`);
+ await queryRunner.query(
+ `ALTER TABLE "emojis" ADD "roles" text NOT NULL DEFAULT ''`,
+ );
}
public async down(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN column_name "roles"`);
+ await queryRunner.query(
+ `ALTER TABLE "emojis" DROP COLUMN column_name "roles"`,
+ );
}
}
diff --git a/src/util/migrations/1633864669243-EmojiUser.ts b/src/util/migrations/1633864669243-EmojiUser.ts
index 982405d7..9610216b 100644
--- a/src/util/migrations/1633864669243-EmojiUser.ts
+++ b/src/util/migrations/1633864669243-EmojiUser.ts
@@ -7,17 +7,21 @@ export class EmojiUser1633864669243 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "emojis" ADD "user_id" varchar`);
try {
await queryRunner.query(
- `ALTER TABLE "emojis" ADD CONSTRAINT FK_fa7ddd5f9a214e28ce596548421 FOREIGN KEY (user_id) REFERENCES users(id)`
+ `ALTER TABLE "emojis" ADD CONSTRAINT FK_fa7ddd5f9a214e28ce596548421 FOREIGN KEY (user_id) REFERENCES users(id)`,
);
} catch (error) {
console.error(
- "sqlite doesn't support altering foreign keys: https://stackoverflow.com/questions/1884818/how-do-i-add-a-foreign-key-to-an-existing-sqlite-table"
+ "sqlite doesn't support altering foreign keys: https://stackoverflow.com/questions/1884818/how-do-i-add-a-foreign-key-to-an-existing-sqlite-table",
);
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN column_name "user_id"`);
- await queryRunner.query(`ALTER TABLE "emojis" DROP CONSTRAINT FK_fa7ddd5f9a214e28ce596548421`);
+ await queryRunner.query(
+ `ALTER TABLE "emojis" DROP COLUMN column_name "user_id"`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "emojis" DROP CONSTRAINT FK_fa7ddd5f9a214e28ce596548421`,
+ );
}
}
diff --git a/src/util/migrations/1633881705509-VanityInvite.ts b/src/util/migrations/1633881705509-VanityInvite.ts
index 45485310..16072473 100644
--- a/src/util/migrations/1633881705509-VanityInvite.ts
+++ b/src/util/migrations/1633881705509-VanityInvite.ts
@@ -5,15 +5,21 @@ export class VanityInvite1633881705509 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
try {
- await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN vanity_url_code`);
- await queryRunner.query(`ALTER TABLE "emojis" DROP CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad`);
+ await queryRunner.query(
+ `ALTER TABLE "emojis" DROP COLUMN vanity_url_code`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "emojis" DROP CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad`,
+ );
} catch (error) {}
}
public async down(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.query(`ALTER TABLE "emojis" ADD vanity_url_code varchar`);
await queryRunner.query(
- `ALTER TABLE "emojis" ADD CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad FOREIGN KEY ("vanity_url_code") REFERENCES "invites"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`
+ `ALTER TABLE "emojis" ADD vanity_url_code varchar`,
+ );
+ await queryRunner.query(
+ `ALTER TABLE "emojis" ADD CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad FOREIGN KEY ("vanity_url_code") REFERENCES "invites"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}
}
diff --git a/src/util/migrations/1634308884591-Stickers.ts b/src/util/migrations/1634308884591-Stickers.ts
index fbc4649f..f7b83242 100644
--- a/src/util/migrations/1634308884591-Stickers.ts
+++ b/src/util/migrations/1634308884591-Stickers.ts
@@ -1,35 +1,64 @@
-import { MigrationInterface, QueryRunner, Table, TableColumn, TableForeignKey } from "typeorm";
+import {
+ MigrationInterface,
+ QueryRunner,
+ Table,
+ TableColumn,
+ TableForeignKey,
+} from "typeorm";
export class Stickers1634308884591 implements MigrationInterface {
name = "Stickers1634308884591";
public async up(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.dropForeignKey("read_states", "FK_6f255d873cfbfd7a93849b7ff74");
+ await queryRunner.dropForeignKey(
+ "read_states",
+ "FK_6f255d873cfbfd7a93849b7ff74",
+ );
await queryRunner.changeColumn(
"stickers",
"tags",
- new TableColumn({ name: "tags", type: "varchar", isNullable: true })
+ new TableColumn({
+ name: "tags",
+ type: "varchar",
+ isNullable: true,
+ }),
);
await queryRunner.changeColumn(
"stickers",
"pack_id",
- new TableColumn({ name: "pack_id", type: "varchar", isNullable: true })
+ new TableColumn({
+ name: "pack_id",
+ type: "varchar",
+ isNullable: true,
+ }),
+ );
+ await queryRunner.changeColumn(
+ "stickers",
+ "type",
+ new TableColumn({ name: "type", type: "integer" }),
);
- await queryRunner.changeColumn("stickers", "type", new TableColumn({ name: "type", type: "integer" }));
await queryRunner.changeColumn(
"stickers",
"format_type",
- new TableColumn({ name: "format_type", type: "integer" })
+ new TableColumn({ name: "format_type", type: "integer" }),
);
await queryRunner.changeColumn(
"stickers",
"available",
- new TableColumn({ name: "available", type: "boolean", isNullable: true })
+ new TableColumn({
+ name: "available",
+ type: "boolean",
+ isNullable: true,
+ }),
);
await queryRunner.changeColumn(
"stickers",
"user_id",
- new TableColumn({ name: "user_id", type: "boolean", isNullable: true })
+ new TableColumn({
+ name: "user_id",
+ type: "boolean",
+ isNullable: true,
+ }),
);
await queryRunner.createForeignKey(
"stickers",
@@ -39,17 +68,33 @@ export class Stickers1634308884591 implements MigrationInterface {
referencedColumnNames: ["id"],
referencedTableName: "users",
onDelete: "CASCADE",
- })
+ }),
);
await queryRunner.createTable(
new Table({
name: "sticker_packs",
columns: [
- new TableColumn({ name: "id", type: "varchar", isPrimary: true }),
+ new TableColumn({
+ name: "id",
+ type: "varchar",
+ isPrimary: true,
+ }),
new TableColumn({ name: "name", type: "varchar" }),
- new TableColumn({ name: "description", type: "varchar", isNullable: true }),
- new TableColumn({ name: "banner_asset_id", type: "varchar", isNullable: true }),
- new TableColumn({ name: "cover_sticker_id", type: "varchar", isNullable: true }),
+ new TableColumn({
+ name: "description",
+ type: "varchar",
+ isNullable: true,
+ }),
+ new TableColumn({
+ name: "banner_asset_id",
+ type: "varchar",
+ isNullable: true,
+ }),
+ new TableColumn({
+ name: "cover_sticker_id",
+ type: "varchar",
+ isNullable: true,
+ }),
],
foreignKeys: [
new TableForeignKey({
@@ -58,7 +103,7 @@ export class Stickers1634308884591 implements MigrationInterface {
referencedTableName: "stickers",
}),
],
- })
+ }),
);
}
diff --git a/src/util/migrations/1634424361103-Presence.ts b/src/util/migrations/1634424361103-Presence.ts
index 729955b8..a71cb253 100644
--- a/src/util/migrations/1634424361103-Presence.ts
+++ b/src/util/migrations/1634424361103-Presence.ts
@@ -4,7 +4,10 @@ export class Presence1634424361103 implements MigrationInterface {
name = "Presence1634424361103";
public async up(queryRunner: QueryRunner): Promise<void> {
- queryRunner.addColumn("sessions", new TableColumn({ name: "activites", type: "text" }));
+ queryRunner.addColumn(
+ "sessions",
+ new TableColumn({ name: "activites", type: "text" }),
+ );
}
public async down(queryRunner: QueryRunner): Promise<void> {}
diff --git a/src/util/migrations/1634426540271-MigrationTimestamp.ts b/src/util/migrations/1634426540271-MigrationTimestamp.ts
index 3208b25b..fb596906 100644
--- a/src/util/migrations/1634426540271-MigrationTimestamp.ts
+++ b/src/util/migrations/1634426540271-MigrationTimestamp.ts
@@ -7,7 +7,11 @@ export class MigrationTimestamp1634426540271 implements MigrationInterface {
await queryRunner.changeColumn(
"migrations",
"timestamp",
- new TableColumn({ name: "timestampe", type: "bigint", isNullable: false })
+ new TableColumn({
+ name: "timestampe",
+ type: "bigint",
+ isNullable: false,
+ }),
);
}
diff --git a/src/util/migrations/1660678870706-opencordFixes.ts b/src/util/migrations/1660678870706-opencordFixes.ts
index 1f10c212..53c561ce 100644
--- a/src/util/migrations/1660678870706-opencordFixes.ts
+++ b/src/util/migrations/1660678870706-opencordFixes.ts
@@ -1,53 +1,52 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class opencordFixes1660678870706 implements MigrationInterface {
- name = 'opencordFixes1660678870706'
+ name = "opencordFixes1660678870706";
- public async up(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.query(`
+ public async up(queryRunner: QueryRunner): Promise<void> {
+ await queryRunner.query(`
ALTER TABLE \`users\`
ADD \`purchased_flags\` int NOT NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`users\`
ADD \`premium_usage_flags\` int NOT NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`user_settings\`
ADD \`friend_discovery_flags\` int NOT NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`user_settings\`
ADD \`view_nsfw_guilds\` tinyint NOT NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`user_settings\`
ADD \`passwordless\` tinyint NOT NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`users\` CHANGE \`mfa_enabled\` \`mfa_enabled\` tinyint NOT NULL
`);
- }
+ }
- public async down(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.query(`
+ public async down(queryRunner: QueryRunner): Promise<void> {
+ await queryRunner.query(`
ALTER TABLE \`users\` CHANGE \`mfa_enabled\` \`mfa_enabled\` tinyint NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`user_settings\` DROP COLUMN \`passwordless\`
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`user_settings\` DROP COLUMN \`view_nsfw_guilds\`
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`user_settings\` DROP COLUMN \`friend_discovery_flags\`
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`users\` DROP COLUMN \`premium_usage_flags\`
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`users\` DROP COLUMN \`purchased_flags\`
`);
- }
-
-}
\ No newline at end of file
+ }
+}
diff --git a/src/util/migrations/1660689892073-mobileFixes2.ts b/src/util/migrations/1660689892073-mobileFixes2.ts
index bd28694e..63e7e032 100644
--- a/src/util/migrations/1660689892073-mobileFixes2.ts
+++ b/src/util/migrations/1660689892073-mobileFixes2.ts
@@ -1,37 +1,36 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class mobileFixes21660689892073 implements MigrationInterface {
- name = 'mobileFixes21660689892073'
+ name = "mobileFixes21660689892073";
- public async up(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.query(`
+ public async up(queryRunner: QueryRunner): Promise<void> {
+ await queryRunner.query(`
ALTER TABLE \`user_settings\`
ADD \`banner_color\` varchar(255) NULL
`);
await queryRunner.query(`
UPDATE \`channels\` SET \`nsfw\` = 0 WHERE \`nsfw\` = NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`channels\` CHANGE \`nsfw\` \`nsfw\` tinyint NOT NULL
`);
await queryRunner.query(`
UPDATE \`guilds\` SET \`nsfw\` = 0 WHERE \`nsfw\` = NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`guilds\` CHANGE \`nsfw\` \`nsfw\` tinyint NOT NULL
`);
- }
+ }
- public async down(queryRunner: QueryRunner): Promise<void> {
- await queryRunner.query(`
+ public async down(queryRunner: QueryRunner): Promise<void> {
+ await queryRunner.query(`
ALTER TABLE \`guilds\` CHANGE \`nsfw\` \`nsfw\` tinyint NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`channels\` CHANGE \`nsfw\` \`nsfw\` tinyint NULL
`);
- await queryRunner.query(`
+ await queryRunner.query(`
ALTER TABLE \`user_settings\` DROP COLUMN \`banner_color\`
`);
- }
-
-}
\ No newline at end of file
+ }
+}
diff --git a/src/util/schemas/ActivitySchema.ts b/src/util/schemas/ActivitySchema.ts
index d316420e..5a3d205b 100644
--- a/src/util/schemas/ActivitySchema.ts
+++ b/src/util/schemas/ActivitySchema.ts
@@ -41,7 +41,8 @@ export const ActivitySchema = {
$id: String,
$sync_id: String,
- $metadata: { // spotify
+ $metadata: {
+ // spotify
$context_uri: String,
album_id: String,
artist_ids: [String],
@@ -57,4 +58,4 @@ export interface ActivitySchema {
status: Status;
activities?: Activity[];
since?: number; // unix time (in milliseconds) of when the client went idle, or null if the client is not idle
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/BackupCodesChallengeSchema.ts b/src/util/schemas/BackupCodesChallengeSchema.ts
index d6b519b7..8e2f0649 100644
--- a/src/util/schemas/BackupCodesChallengeSchema.ts
+++ b/src/util/schemas/BackupCodesChallengeSchema.ts
@@ -1,3 +1,3 @@
export interface BackupCodesChallengeSchema {
password: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/BanCreateSchema.ts b/src/util/schemas/BanCreateSchema.ts
index 876b2a89..834577dc 100644
--- a/src/util/schemas/BanCreateSchema.ts
+++ b/src/util/schemas/BanCreateSchema.ts
@@ -1,4 +1,4 @@
export interface BanCreateSchema {
delete_message_days?: string;
reason?: string;
-};
\ No newline at end of file
+}
diff --git a/src/util/schemas/BanModeratorSchema.ts b/src/util/schemas/BanModeratorSchema.ts
index 8efa2402..afb76433 100644
--- a/src/util/schemas/BanModeratorSchema.ts
+++ b/src/util/schemas/BanModeratorSchema.ts
@@ -4,4 +4,4 @@ export interface BanModeratorSchema {
guild_id: string;
executor_id: string;
reason?: string | undefined;
-};
\ No newline at end of file
+}
diff --git a/src/util/schemas/BanRegistrySchema.ts b/src/util/schemas/BanRegistrySchema.ts
index 8680d3db..501f94dc 100644
--- a/src/util/schemas/BanRegistrySchema.ts
+++ b/src/util/schemas/BanRegistrySchema.ts
@@ -5,4 +5,4 @@ export interface BanRegistrySchema {
executor_id: string;
ip?: string;
reason?: string | undefined;
-};
\ No newline at end of file
+}
diff --git a/src/util/schemas/BulkDeleteSchema.ts b/src/util/schemas/BulkDeleteSchema.ts
index 6a71e052..bfc4df65 100644
--- a/src/util/schemas/BulkDeleteSchema.ts
+++ b/src/util/schemas/BulkDeleteSchema.ts
@@ -1,3 +1,3 @@
export interface BulkDeleteSchema {
messages: string[];
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/ChannelModifySchema.ts b/src/util/schemas/ChannelModifySchema.ts
index 835ea2d7..9a07f983 100644
--- a/src/util/schemas/ChannelModifySchema.ts
+++ b/src/util/schemas/ChannelModifySchema.ts
@@ -27,4 +27,4 @@ export interface ChannelModifySchema {
flags?: number;
default_thread_rate_limit_per_user?: number;
video_quality_mode?: number;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/CodesVerificationSchema.ts b/src/util/schemas/CodesVerificationSchema.ts
index e8e2e7b4..73c371eb 100644
--- a/src/util/schemas/CodesVerificationSchema.ts
+++ b/src/util/schemas/CodesVerificationSchema.ts
@@ -2,4 +2,4 @@ export interface CodesVerificationSchema {
key: string;
nonce: string;
regenerate?: boolean;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/DmChannelCreateSchema.ts b/src/util/schemas/DmChannelCreateSchema.ts
index 04b8ff69..1b0fe86d 100644
--- a/src/util/schemas/DmChannelCreateSchema.ts
+++ b/src/util/schemas/DmChannelCreateSchema.ts
@@ -1,4 +1,4 @@
export interface DmChannelCreateSchema {
name?: string;
recipients: string[];
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/EmojiCreateSchema.ts b/src/util/schemas/EmojiCreateSchema.ts
index 8e2a2307..34084713 100644
--- a/src/util/schemas/EmojiCreateSchema.ts
+++ b/src/util/schemas/EmojiCreateSchema.ts
@@ -3,4 +3,4 @@ export interface EmojiCreateSchema {
image: string;
require_colons?: boolean | null;
roles?: string[];
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/EmojiModifySchema.ts b/src/util/schemas/EmojiModifySchema.ts
index cd5b7e3e..05d2d395 100644
--- a/src/util/schemas/EmojiModifySchema.ts
+++ b/src/util/schemas/EmojiModifySchema.ts
@@ -1,4 +1,4 @@
export interface EmojiModifySchema {
name?: string;
roles?: string[];
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/GuildCreateSchema.ts b/src/util/schemas/GuildCreateSchema.ts
index 9b5f7dc2..f3de7007 100644
--- a/src/util/schemas/GuildCreateSchema.ts
+++ b/src/util/schemas/GuildCreateSchema.ts
@@ -11,4 +11,4 @@ export interface GuildCreateSchema {
guild_template_code?: string;
system_channel_id?: string;
rules_channel_id?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/GuildTemplateCreateSchema.ts b/src/util/schemas/GuildTemplateCreateSchema.ts
index 7caefcb8..59db8428 100644
--- a/src/util/schemas/GuildTemplateCreateSchema.ts
+++ b/src/util/schemas/GuildTemplateCreateSchema.ts
@@ -1,4 +1,4 @@
export interface GuildTemplateCreateSchema {
name: string;
avatar?: string | null;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts b/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts
index 0022da6e..e271b83e 100644
--- a/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts
+++ b/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts
@@ -7,4 +7,4 @@ export interface GuildUpdateWelcomeScreenSchema {
}[];
enabled?: boolean;
description?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/InviteCreateSchema.ts b/src/util/schemas/InviteCreateSchema.ts
index 83ae22dd..cac11147 100644
--- a/src/util/schemas/InviteCreateSchema.ts
+++ b/src/util/schemas/InviteCreateSchema.ts
@@ -8,4 +8,4 @@ export interface InviteCreateSchema {
unique?: boolean;
target_user?: string;
target_user_type?: number;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/LoginSchema.ts b/src/util/schemas/LoginSchema.ts
index 543d236c..dc889d94 100644
--- a/src/util/schemas/LoginSchema.ts
+++ b/src/util/schemas/LoginSchema.ts
@@ -5,4 +5,4 @@ export interface LoginSchema {
captcha_key?: string;
login_source?: string;
gift_code_sku_id?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/MemberChangeSchema.ts b/src/util/schemas/MemberChangeSchema.ts
index 566d7e20..2367bef3 100644
--- a/src/util/schemas/MemberChangeSchema.ts
+++ b/src/util/schemas/MemberChangeSchema.ts
@@ -1,4 +1,4 @@
export interface MemberChangeSchema {
roles?: string[];
nick?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/MemberNickChangeSchema.ts b/src/util/schemas/MemberNickChangeSchema.ts
index ed9fdb7b..d863038c 100644
--- a/src/util/schemas/MemberNickChangeSchema.ts
+++ b/src/util/schemas/MemberNickChangeSchema.ts
@@ -1,3 +1,3 @@
export interface MemberNickChangeSchema {
nick: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/MessageAcknowledgeSchema.ts b/src/util/schemas/MessageAcknowledgeSchema.ts
index 1e7fb80d..194bb4b4 100644
--- a/src/util/schemas/MessageAcknowledgeSchema.ts
+++ b/src/util/schemas/MessageAcknowledgeSchema.ts
@@ -1,4 +1,4 @@
export interface MessageAcknowledgeSchema {
manual?: boolean;
mention_count?: number;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/MessageCreateSchema.ts b/src/util/schemas/MessageCreateSchema.ts
index 9d77c485..bf3470bb 100644
--- a/src/util/schemas/MessageCreateSchema.ts
+++ b/src/util/schemas/MessageCreateSchema.ts
@@ -30,4 +30,4 @@ export interface MessageCreateSchema {
**/
attachments?: any[];
sticker_ids?: string[];
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/MfaCodesSchema.ts b/src/util/schemas/MfaCodesSchema.ts
index 226c43f1..ac05b9a4 100644
--- a/src/util/schemas/MfaCodesSchema.ts
+++ b/src/util/schemas/MfaCodesSchema.ts
@@ -1,4 +1,4 @@
export interface MfaCodesSchema {
password: string;
regenerate?: boolean;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/ModifyGuildStickerSchema.ts b/src/util/schemas/ModifyGuildStickerSchema.ts
index 06bf4ffe..159cc44f 100644
--- a/src/util/schemas/ModifyGuildStickerSchema.ts
+++ b/src/util/schemas/ModifyGuildStickerSchema.ts
@@ -12,4 +12,4 @@ export interface ModifyGuildStickerSchema {
* @maxLength 200
*/
tags: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/PruneSchema.ts b/src/util/schemas/PruneSchema.ts
index 60601d81..bea5e2b4 100644
--- a/src/util/schemas/PruneSchema.ts
+++ b/src/util/schemas/PruneSchema.ts
@@ -3,4 +3,4 @@ export interface PruneSchema {
* @min 0
*/
days: number;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/PurgeSchema.ts b/src/util/schemas/PurgeSchema.ts
index 8916be92..f5ab0a20 100644
--- a/src/util/schemas/PurgeSchema.ts
+++ b/src/util/schemas/PurgeSchema.ts
@@ -1,4 +1,4 @@
export interface PurgeSchema {
before: string;
after: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/RegisterSchema.ts b/src/util/schemas/RegisterSchema.ts
index c0cc3805..865f55b3 100644
--- a/src/util/schemas/RegisterSchema.ts
+++ b/src/util/schemas/RegisterSchema.ts
@@ -24,4 +24,4 @@ export interface RegisterSchema {
captcha_key?: string;
promotional_email_opt_in?: boolean;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/RelationshipPostSchema.ts b/src/util/schemas/RelationshipPostSchema.ts
index 3ff6eade..774c67f6 100644
--- a/src/util/schemas/RelationshipPostSchema.ts
+++ b/src/util/schemas/RelationshipPostSchema.ts
@@ -1,4 +1,4 @@
export interface RelationshipPostSchema {
discriminator: string;
username: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/RelationshipPutSchema.ts b/src/util/schemas/RelationshipPutSchema.ts
index 455f854e..0a7f9720 100644
--- a/src/util/schemas/RelationshipPutSchema.ts
+++ b/src/util/schemas/RelationshipPutSchema.ts
@@ -2,4 +2,4 @@ import { RelationshipType } from "@fosscord/util";
export interface RelationshipPutSchema {
type?: RelationshipType;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/RoleModifySchema.ts b/src/util/schemas/RoleModifySchema.ts
index adb0c1a6..f3f4a20e 100644
--- a/src/util/schemas/RoleModifySchema.ts
+++ b/src/util/schemas/RoleModifySchema.ts
@@ -7,4 +7,4 @@ export interface RoleModifySchema {
position?: number;
icon?: string;
unicode_emoji?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/SelectProtocolSchema.ts b/src/util/schemas/SelectProtocolSchema.ts
index 92958e97..0ba0c23b 100644
--- a/src/util/schemas/SelectProtocolSchema.ts
+++ b/src/util/schemas/SelectProtocolSchema.ts
@@ -1,12 +1,12 @@
export interface SelectProtocolSchema {
protocol: "webrtc" | "udp";
data:
- | string
- | {
- address: string;
- port: number;
- mode: string;
- };
+ | string
+ | {
+ address: string;
+ port: number;
+ mode: string;
+ };
sdp?: string;
codecs?: {
name: "opus" | "VP8" | "VP9" | "H264";
@@ -16,4 +16,4 @@ export interface SelectProtocolSchema {
rtx_payload_type?: number | null;
}[];
rtc_connection_id?: string; // uuid
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/TemplateCreateSchema.ts b/src/util/schemas/TemplateCreateSchema.ts
index 3f98f692..160934f5 100644
--- a/src/util/schemas/TemplateCreateSchema.ts
+++ b/src/util/schemas/TemplateCreateSchema.ts
@@ -1,4 +1,4 @@
export interface TemplateCreateSchema {
name: string;
description?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/TemplateModifySchema.ts b/src/util/schemas/TemplateModifySchema.ts
index 3e6efb74..f9c9d14b 100644
--- a/src/util/schemas/TemplateModifySchema.ts
+++ b/src/util/schemas/TemplateModifySchema.ts
@@ -1,4 +1,4 @@
export interface TemplateModifySchema {
name: string;
description?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/TotpDisableSchema.ts b/src/util/schemas/TotpDisableSchema.ts
index 05192bfa..51446e1c 100644
--- a/src/util/schemas/TotpDisableSchema.ts
+++ b/src/util/schemas/TotpDisableSchema.ts
@@ -1,3 +1,3 @@
export interface TotpDisableSchema {
code: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/TotpEnableSchema.ts b/src/util/schemas/TotpEnableSchema.ts
index 7f6fb5a9..4e3551d9 100644
--- a/src/util/schemas/TotpEnableSchema.ts
+++ b/src/util/schemas/TotpEnableSchema.ts
@@ -2,4 +2,4 @@ export interface TotpEnableSchema {
password: string;
code?: string;
secret?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/TotpSchema.ts b/src/util/schemas/TotpSchema.ts
index 889cb443..941a92ec 100644
--- a/src/util/schemas/TotpSchema.ts
+++ b/src/util/schemas/TotpSchema.ts
@@ -1,6 +1,6 @@
export interface TotpSchema {
- code: string,
- ticket: string,
- gift_code_sku_id?: string | null,
- login_source?: string | null,
-}
\ No newline at end of file
+ code: string;
+ ticket: string;
+ gift_code_sku_id?: string | null;
+ login_source?: string | null;
+}
diff --git a/src/util/schemas/UserModifySchema.ts b/src/util/schemas/UserModifySchema.ts
index 34e0f135..5327e34b 100644
--- a/src/util/schemas/UserModifySchema.ts
+++ b/src/util/schemas/UserModifySchema.ts
@@ -16,4 +16,4 @@ export interface UserModifySchema {
code?: string;
email?: string;
discriminator?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/Validator.ts b/src/util/schemas/Validator.ts
index b71bf6a1..e85cdf7b 100644
--- a/src/util/schemas/Validator.ts
+++ b/src/util/schemas/Validator.ts
@@ -3,7 +3,14 @@ import addFormats from "ajv-formats";
import fs from "fs";
import path from "path";
-const SchemaPath = path.join(__dirname, "..", "..", "..", "assets", "schemas.json");
+const SchemaPath = path.join(
+ __dirname,
+ "..",
+ "..",
+ "..",
+ "assets",
+ "schemas.json",
+);
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
export const ajv = new Ajv({
@@ -14,7 +21,7 @@ export const ajv = new Ajv({
coerceTypes: true,
messages: true,
strict: true,
- strictRequired: true
+ strictRequired: true,
});
addFormats(ajv);
@@ -41,7 +48,14 @@ export const normalizeBody = (body: any = {}) => {
} else {
for (const [key, value] of Object.entries(object)) {
if (value == null) {
- if (key === "icon" || key === "avatar" || key === "banner" || key === "splash" || key === "discovery_splash") continue;
+ if (
+ key === "icon" ||
+ key === "avatar" ||
+ key === "banner" ||
+ key === "splash" ||
+ key === "discovery_splash"
+ )
+ continue;
delete object[key];
} else if (typeof value === "object") {
normalizeObject(value);
@@ -51,4 +65,4 @@ export const normalizeBody = (body: any = {}) => {
};
normalizeObject(body);
return body;
-};
\ No newline at end of file
+};
diff --git a/src/util/schemas/VanityUrlSchema.ts b/src/util/schemas/VanityUrlSchema.ts
index 28bf7f2b..4dd9b9da 100644
--- a/src/util/schemas/VanityUrlSchema.ts
+++ b/src/util/schemas/VanityUrlSchema.ts
@@ -4,4 +4,4 @@ export interface VanityUrlSchema {
* @maxLength 20
*/
code?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/VoiceIdentifySchema.ts b/src/util/schemas/VoiceIdentifySchema.ts
index d48de347..df023713 100644
--- a/src/util/schemas/VoiceIdentifySchema.ts
+++ b/src/util/schemas/VoiceIdentifySchema.ts
@@ -9,4 +9,4 @@ export interface VoiceIdentifySchema {
rid: string;
quality: number;
}[];
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/VoiceStateUpdateSchema.ts b/src/util/schemas/VoiceStateUpdateSchema.ts
index 5f805f4d..79e93b07 100644
--- a/src/util/schemas/VoiceStateUpdateSchema.ts
+++ b/src/util/schemas/VoiceStateUpdateSchema.ts
@@ -15,8 +15,8 @@ export const VoiceStateUpdateSchema = {
$channel_id: String,
self_mute: Boolean,
self_deaf: Boolean,
- $self_video: Boolean, //required in docs but bots don't always send it
+ $self_video: Boolean, //required in docs but bots don't always send it
$preferred_region: String,
$request_to_speak_timestamp: Date,
$suppress: Boolean,
-};
\ No newline at end of file
+};
diff --git a/src/util/schemas/VoiceVideoSchema.ts b/src/util/schemas/VoiceVideoSchema.ts
index 837ee1e7..0ba519e1 100644
--- a/src/util/schemas/VoiceVideoSchema.ts
+++ b/src/util/schemas/VoiceVideoSchema.ts
@@ -12,6 +12,6 @@ export interface VoiceVideoSchema {
rtx_ssrc: number;
max_bitrate: number;
max_framerate: number;
- max_resolution: { type: string; width: number; height: number; };
+ max_resolution: { type: string; width: number; height: number };
}[];
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/WebhookCreateSchema.ts b/src/util/schemas/WebhookCreateSchema.ts
index c32b642d..99f6a12f 100644
--- a/src/util/schemas/WebhookCreateSchema.ts
+++ b/src/util/schemas/WebhookCreateSchema.ts
@@ -5,4 +5,4 @@ export interface WebhookCreateSchema {
*/
name: string;
avatar: string;
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/WidgetModifySchema.ts b/src/util/schemas/WidgetModifySchema.ts
index 3c84b3a1..26d4504f 100644
--- a/src/util/schemas/WidgetModifySchema.ts
+++ b/src/util/schemas/WidgetModifySchema.ts
@@ -1,4 +1,4 @@
export interface WidgetModifySchema {
enabled: boolean; // whether the widget is enabled
channel_id: string; // the widget channel id
-}
\ No newline at end of file
+}
diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts
index f86552f3..ae80de71 100644
--- a/src/util/schemas/index.ts
+++ b/src/util/schemas/index.ts
@@ -38,4 +38,4 @@ export * from "./VoiceStateUpdateSchema";
export * from "./VoiceVideoSchema";
export * from "./IdentifySchema";
export * from "./ActivitySchema";
-export * from "./LazyRequestSchema";
\ No newline at end of file
+export * from "./LazyRequestSchema";
diff --git a/src/util/util/ApiError.ts b/src/util/util/ApiError.ts
index f1a9b4f6..0fce6882 100644
--- a/src/util/util/ApiError.ts
+++ b/src/util/util/ApiError.ts
@@ -3,23 +3,34 @@ export class ApiError extends Error {
readonly message: string,
public readonly code: number,
public readonly httpStatus: number = 400,
- public readonly defaultParams?: string[]
+ public readonly defaultParams?: string[],
) {
super(message);
}
withDefaultParams(): ApiError {
if (this.defaultParams)
- return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus);
+ return new ApiError(
+ applyParamsToString(this.message, this.defaultParams),
+ this.code,
+ this.httpStatus,
+ );
return this;
}
withParams(...params: (string | number)[]): ApiError {
- return new ApiError(applyParamsToString(this.message, params), this.code, this.httpStatus);
+ return new ApiError(
+ applyParamsToString(this.message, params),
+ this.code,
+ this.httpStatus,
+ );
}
}
-export function applyParamsToString(s: string, params: (string | number)[]): string {
+export function applyParamsToString(
+ s: string,
+ params: (string | number)[],
+): string {
let newString = s;
params.forEach((a) => {
newString = newString.replace("{}", "" + a);
diff --git a/src/util/util/AutoUpdate.ts b/src/util/util/AutoUpdate.ts
index fd65ecf5..769d959f 100644
--- a/src/util/util/AutoUpdate.ts
+++ b/src/util/util/AutoUpdate.ts
@@ -1,6 +1,6 @@
import "missing-native-js-functions";
import fetch from "node-fetch";
-import ProxyAgent from 'proxy-agent';
+import ProxyAgent from "proxy-agent";
import readline from "readline";
import fs from "fs/promises";
import path from "path";
@@ -19,14 +19,17 @@ export function enableAutoUpdate(opts: {
}) {
if (!opts.checkInterval) return;
var interval = 1000 * 60 * 60 * 24;
- if (typeof opts.checkInterval === "number") opts.checkInterval = 1000 * interval;
+ if (typeof opts.checkInterval === "number")
+ opts.checkInterval = 1000 * interval;
const i = setInterval(async () => {
const currentVersion = await getCurrentVersion(opts.path);
const latestVersion = await getLatestVersion(opts.packageJsonLink);
if (currentVersion !== latestVersion) {
clearInterval(i);
- console.log(`[Auto Update] Current version (${currentVersion}) is out of date, updating ...`);
+ console.log(
+ `[Auto Update] Current version (${currentVersion}) is out of date, updating ...`,
+ );
await download(opts.downloadUrl, opts.path);
}
}, interval);
@@ -43,7 +46,7 @@ export function enableAutoUpdate(opts: {
} else {
console.log(`[Auto update] aborted`);
}
- }
+ },
);
}
});
@@ -65,7 +68,9 @@ async function download(url: string, dir: string) {
async function getCurrentVersion(dir: string) {
try {
- const content = await fs.readFile(path.join(dir, "package.json"), { encoding: "utf8" });
+ const content = await fs.readFile(path.join(dir, "package.json"), {
+ encoding: "utf8",
+ });
return JSON.parse(content).version;
} catch (error) {
throw new Error("[Auto update] couldn't get current version in " + dir);
@@ -76,7 +81,7 @@ async function getLatestVersion(url: string) {
try {
const agent = new ProxyAgent();
const response = await fetch(url, { agent });
- const content = await response.json() as any; // TODO: types
+ const content = (await response.json()) as any; // TODO: types
return content.version;
} catch (error) {
throw new Error("[Auto update] check failed for " + url);
diff --git a/src/util/util/BannedWords.ts b/src/util/util/BannedWords.ts
index 891a5980..74a82efe 100644
--- a/src/util/util/BannedWords.ts
+++ b/src/util/util/BannedWords.ts
@@ -6,7 +6,9 @@ var words: string[];
export const BannedWords = {
init: async function init() {
if (words) return words;
- const file = (await fs.readFile(path.join(process.cwd(), "bannedWords"))).toString();
+ const file = (
+ await fs.readFile(path.join(process.cwd(), "bannedWords"))
+ ).toString();
if (!file) {
words = [];
return [];
@@ -18,6 +20,6 @@ export const BannedWords = {
get: () => words,
find: (val: string) => {
- return words.some(x => val.indexOf(x) != -1);
- }
-};
\ No newline at end of file
+ return words.some((x) => val.indexOf(x) != -1);
+ },
+};
diff --git a/src/util/util/BitField.ts b/src/util/util/BitField.ts
index fb887e05..4e606660 100644
--- a/src/util/util/BitField.ts
+++ b/src/util/util/BitField.ts
@@ -3,7 +3,12 @@
// https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js
// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
-export type BitFieldResolvable = number | BigInt | BitField | string | BitFieldResolvable[];
+export type BitFieldResolvable =
+ | number
+ | BigInt
+ | BitField
+ | string
+ | BitFieldResolvable[];
/**
* Data structure that makes it easy to interact with a bitfield.
@@ -91,7 +96,8 @@ export class BitField {
*/
serialize() {
const serialized: Record<string, boolean> = {};
- for (const [flag, bit] of Object.entries(BitField.FLAGS)) serialized[flag] = this.has(bit);
+ for (const [flag, bit] of Object.entries(BitField.FLAGS))
+ serialized[flag] = this.has(bit);
return serialized;
}
@@ -130,14 +136,21 @@ export class BitField {
static resolve(bit: BitFieldResolvable = BigInt(0)): bigint {
// @ts-ignore
const FLAGS = this.FLAGS || this.constructor?.FLAGS;
- if ((typeof bit === "number" || typeof bit === "bigint") && bit >= BigInt(0)) return BigInt(bit);
+ if (
+ (typeof bit === "number" || typeof bit === "bigint") &&
+ bit >= BigInt(0)
+ )
+ return BigInt(bit);
if (bit instanceof BitField) return bit.bitfield;
if (Array.isArray(bit)) {
// @ts-ignore
const resolve = this.constructor?.resolve || this.resolve;
- return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), BigInt(0));
+ return bit
+ .map((p) => resolve.call(this, p))
+ .reduce((prev, p) => BigInt(prev) | BigInt(p), BigInt(0));
}
- if (typeof bit === "string" && typeof FLAGS[bit] !== "undefined") return FLAGS[bit];
+ if (typeof bit === "string" && typeof FLAGS[bit] !== "undefined")
+ return FLAGS[bit];
throw new RangeError("BITFIELD_INVALID: " + bit);
}
}
diff --git a/src/util/util/Categories.ts b/src/util/util/Categories.ts
index a3c69da7..cd706a8a 100644
--- a/src/util/util/Categories.ts
+++ b/src/util/util/Categories.ts
@@ -1 +1 @@
-//TODO: populate default discord categories + init, get and set methods
\ No newline at end of file
+//TODO: populate default discord categories + init, get and set methods
diff --git a/src/util/util/Config.ts b/src/util/util/Config.ts
index 31b8d97c..7fab7f90 100644
--- a/src/util/util/Config.ts
+++ b/src/util/util/Config.ts
@@ -1,5 +1,9 @@
import "missing-native-js-functions";
-import { ConfigValue, ConfigEntity, DefaultConfigOptions } from "../entities/Config";
+import {
+ ConfigValue,
+ ConfigEntity,
+ DefaultConfigOptions,
+} from "../entities/Config";
import path from "path";
import fs from "fs";
@@ -42,7 +46,11 @@ export const Config = {
function applyConfig(val: ConfigValue) {
async function apply(obj: any, key = ""): Promise<any> {
if (typeof obj === "object" && obj !== null)
- return Promise.all(Object.keys(obj).map((k) => apply(obj[k], key ? `${key}_${k}` : k)));
+ return Promise.all(
+ Object.keys(obj).map((k) =>
+ apply(obj[k], key ? `${key}_${k}` : k),
+ ),
+ );
let pair = pairs.find((x) => x.key === key);
if (!pair) pair = new ConfigEntity();
@@ -67,7 +75,8 @@ function pairsToConfig(pairs: ConfigEntity[]) {
let i = 0;
for (const key of keys) {
- if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = [];
+ if (!isNaN(Number(key)) && !prevObj[prev]?.length)
+ prevObj[prev] = obj = [];
if (i++ === keys.length - 1) obj[key] = p.value;
else if (!obj[key]) obj[key] = {};
diff --git a/src/util/util/Constants.ts b/src/util/util/Constants.ts
index 81a7165d..7c5b7dcb 100644
--- a/src/util/util/Constants.ts
+++ b/src/util/util/Constants.ts
@@ -77,9 +77,9 @@ export const VoiceOPCodes = {
RESUME: 7,
HELLO: 8,
RESUMED: 9,
- CLIENT_CONNECT: 12, // incorrect, op 12 is probably used for video
- CLIENT_DISCONNECT: 13, // incorrect
- VERSION: 16, //not documented
+ CLIENT_CONNECT: 12, // incorrect, op 12 is probably used for video
+ CLIENT_DISCONNECT: 13, // incorrect
+ VERSION: 16, //not documented
};
export const Events = {
@@ -160,7 +160,13 @@ export const ShardEvents = {
* sidebar for more information.</warn>
* @typedef {string} PartialType
*/
-export const PartialTypes = keyMirror(["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"]);
+export const PartialTypes = keyMirror([
+ "USER",
+ "CHANNEL",
+ "GUILD_MEMBER",
+ "MESSAGE",
+ "REACTION",
+]);
/**
* The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events:
@@ -291,7 +297,7 @@ export const MessageTypes = [
* @typedef {string} SystemMessageType
*/
export const SystemMessageTypes = MessageTypes.filter(
- (type: string | null) => type && type !== "DEFAULT" && type !== "REPLY"
+ (type: string | null) => type && type !== "DEFAULT" && type !== "REPLY",
);
/**
@@ -305,7 +311,14 @@ export const SystemMessageTypes = MessageTypes.filter(
* * COMPETING
* @typedef {string} ActivityType
*/
-export const ActivityTypes = ["PLAYING", "STREAMING", "LISTENING", "WATCHING", "CUSTOM_STATUS", "COMPETING"];
+export const ActivityTypes = [
+ "PLAYING",
+ "STREAMING",
+ "LISTENING",
+ "WATCHING",
+ "CUSTOM_STATUS",
+ "COMPETING",
+];
export const ChannelTypes = {
TEXT: 0,
@@ -361,7 +374,11 @@ export const Colors = {
* * ALL_MEMBERS
* @typedef {string} ExplicitContentFilterLevel
*/
-export const ExplicitContentFilterLevels = ["DISABLED", "MEMBERS_WITHOUT_ROLES", "ALL_MEMBERS"];
+export const ExplicitContentFilterLevels = [
+ "DISABLED",
+ "MEMBERS_WITHOUT_ROLES",
+ "ALL_MEMBERS",
+];
/**
* The value set for the verification levels for a guild:
@@ -372,7 +389,13 @@ export const ExplicitContentFilterLevels = ["DISABLED", "MEMBERS_WITHOUT_ROLES",
* * VERY_HIGH
* @typedef {string} VerificationLevel
*/
-export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"];
+export const VerificationLevels = [
+ "NONE",
+ "LOW",
+ "MEDIUM",
+ "HIGH",
+ "VERY_HIGH",
+];
/**
* An error encountered while performing an API request. Here are the potential errors:
@@ -517,7 +540,10 @@ export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"]
*/
export const DiscordApiErrors = {
//https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes
- GENERAL_ERROR: new ApiError("General error (such as a malformed request body, amongst other things)", 0),
+ GENERAL_ERROR: new ApiError(
+ "General error (such as a malformed request body, amongst other things)",
+ 0,
+ ),
UNKNOWN_ACCOUNT: new ApiError("Unknown account", 10001),
UNKNOWN_APPLICATION: new ApiError("Unknown application", 10002),
UNKNOWN_CHANNEL: new ApiError("Unknown channel", 10003),
@@ -542,185 +568,410 @@ export const DiscordApiErrors = {
UNKNOWN_BUILD: new ApiError("Unknown build", 10030),
UNKNOWN_LOBBY: new ApiError("Unknown lobby", 10031),
UNKNOWN_BRANCH: new ApiError("Unknown branch", 10032),
- UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError("Unknown store directory layout", 10033),
+ UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError(
+ "Unknown store directory layout",
+ 10033,
+ ),
UNKNOWN_REDISTRIBUTABLE: new ApiError("Unknown redistributable", 10036),
UNKNOWN_GIFT_CODE: new ApiError("Unknown gift code", 10038),
UNKNOWN_STREAM: new ApiError("Unknown stream", 10049),
- UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError("Unknown premium server subscribe cooldown", 10050),
+ UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError(
+ "Unknown premium server subscribe cooldown",
+ 10050,
+ ),
UNKNOWN_GUILD_TEMPLATE: new ApiError("Unknown guild template", 10057),
- UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError("Unknown discoverable server category", 10059),
+ UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError(
+ "Unknown discoverable server category",
+ 10059,
+ ),
UNKNOWN_STICKER: new ApiError("Unknown sticker", 10060),
UNKNOWN_INTERACTION: new ApiError("Unknown interaction", 10062),
- UNKNOWN_APPLICATION_COMMAND: new ApiError("Unknown application command", 10063),
- UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError("Unknown application command permissions", 10066),
+ UNKNOWN_APPLICATION_COMMAND: new ApiError(
+ "Unknown application command",
+ 10063,
+ ),
+ UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError(
+ "Unknown application command permissions",
+ 10066,
+ ),
UNKNOWN_STAGE_INSTANCE: new ApiError("Unknown Stage Instance", 10067),
- UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError("Unknown Guild Member Verification Form", 10068),
- UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError("Unknown Guild Welcome Screen", 10069),
- UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError("Unknown Guild Scheduled Event", 10070),
- UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071),
- BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001),
+ UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError(
+ "Unknown Guild Member Verification Form",
+ 10068,
+ ),
+ UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError(
+ "Unknown Guild Welcome Screen",
+ 10069,
+ ),
+ UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError(
+ "Unknown Guild Scheduled Event",
+ 10070,
+ ),
+ UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError(
+ "Unknown Guild Scheduled Event User",
+ 10071,
+ ),
+ BOT_PROHIBITED_ENDPOINT: new ApiError(
+ "Bots cannot use this endpoint",
+ 20001,
+ ),
BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002),
EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError(
"Explicit content cannot be sent to the desired recipient(s)",
- 20009
+ 20009,
),
ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError(
"You are not authorized to perform this action on this application",
- 20012
+ 20012,
+ ),
+ SLOWMODE_RATE_LIMIT: new ApiError(
+ "This action cannot be performed due to slowmode rate limit",
+ 20016,
+ ),
+ ONLY_OWNER: new ApiError(
+ "Only the owner of this account can perform this action",
+ 20018,
+ ),
+ ANNOUNCEMENT_RATE_LIMITS: new ApiError(
+ "This message cannot be edited due to announcement rate limits",
+ 20022,
+ ),
+ CHANNEL_WRITE_RATELIMIT: new ApiError(
+ "The channel you are writing has hit the write rate limit",
+ 20028,
),
- SLOWMODE_RATE_LIMIT: new ApiError("This action cannot be performed due to slowmode rate limit", 20016),
- ONLY_OWNER: new ApiError("Only the owner of this account can perform this action", 20018),
- ANNOUNCEMENT_RATE_LIMITS: new ApiError("This message cannot be edited due to announcement rate limits", 20022),
- CHANNEL_WRITE_RATELIMIT: new ApiError("The channel you are writing has hit the write rate limit", 20028),
WORDS_NOT_ALLOWED: new ApiError(
"Your Stage topic, server name, server description, or channel names contain words that are not allowed",
- 20031
- ),
- GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError("Guild premium subscription level too low", 20035),
- MAXIMUM_GUILDS: new ApiError("Maximum number of guilds reached ({})", 30001, undefined, ["100"]),
- MAXIMUM_FRIENDS: new ApiError("Maximum number of friends reached ({})", 30002, undefined, ["1000"]),
- MAXIMUM_PINS: new ApiError("Maximum number of pins reached for the channel ({})", 30003, undefined, ["50"]),
- MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, [
- "10",
- ]),
- MAXIMUM_ROLES: new ApiError("Maximum number of guild roles reached ({})", 30005, undefined, ["250"]),
- MAXIMUM_WEBHOOKS: new ApiError("Maximum number of webhooks reached ({})", 30007, undefined, ["10"]),
- MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError("Maximum number of emojis reached", 30008),
- MAXIMUM_REACTIONS: new ApiError("Maximum number of reactions reached ({})", 30010, undefined, ["20"]),
- MAXIMUM_CHANNELS: new ApiError("Maximum number of guild channels reached ({})", 30013, undefined, ["500"]),
- MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, [
- "10",
- ]),
- MAXIMUM_INVITES: new ApiError("Maximum number of invites reached ({})", 30016, undefined, ["1000"]),
- MAXIMUM_ANIMATED_EMOJIS: new ApiError("Maximum number of animated emojis reached", 30018),
- MAXIMUM_SERVER_MEMBERS: new ApiError("Maximum number of server members reached", 30019),
+ 20031,
+ ),
+ GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError(
+ "Guild premium subscription level too low",
+ 20035,
+ ),
+ MAXIMUM_GUILDS: new ApiError(
+ "Maximum number of guilds reached ({})",
+ 30001,
+ undefined,
+ ["100"],
+ ),
+ MAXIMUM_FRIENDS: new ApiError(
+ "Maximum number of friends reached ({})",
+ 30002,
+ undefined,
+ ["1000"],
+ ),
+ MAXIMUM_PINS: new ApiError(
+ "Maximum number of pins reached for the channel ({})",
+ 30003,
+ undefined,
+ ["50"],
+ ),
+ MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError(
+ "Maximum number of recipients reached ({})",
+ 30004,
+ undefined,
+ ["10"],
+ ),
+ MAXIMUM_ROLES: new ApiError(
+ "Maximum number of guild roles reached ({})",
+ 30005,
+ undefined,
+ ["250"],
+ ),
+ MAXIMUM_WEBHOOKS: new ApiError(
+ "Maximum number of webhooks reached ({})",
+ 30007,
+ undefined,
+ ["10"],
+ ),
+ MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError(
+ "Maximum number of emojis reached",
+ 30008,
+ ),
+ MAXIMUM_REACTIONS: new ApiError(
+ "Maximum number of reactions reached ({})",
+ 30010,
+ undefined,
+ ["20"],
+ ),
+ MAXIMUM_CHANNELS: new ApiError(
+ "Maximum number of guild channels reached ({})",
+ 30013,
+ undefined,
+ ["500"],
+ ),
+ MAXIMUM_ATTACHMENTS: new ApiError(
+ "Maximum number of attachments in a message reached ({})",
+ 30015,
+ undefined,
+ ["10"],
+ ),
+ MAXIMUM_INVITES: new ApiError(
+ "Maximum number of invites reached ({})",
+ 30016,
+ undefined,
+ ["1000"],
+ ),
+ MAXIMUM_ANIMATED_EMOJIS: new ApiError(
+ "Maximum number of animated emojis reached",
+ 30018,
+ ),
+ MAXIMUM_SERVER_MEMBERS: new ApiError(
+ "Maximum number of server members reached",
+ 30019,
+ ),
MAXIMUM_SERVER_CATEGORIES: new ApiError(
"Maximum number of server categories has been reached ({})",
30030,
undefined,
- ["5"]
+ ["5"],
+ ),
+ GUILD_ALREADY_HAS_TEMPLATE: new ApiError(
+ "Guild already has a template",
+ 30031,
+ ),
+ MAXIMUM_THREAD_PARTICIPANTS: new ApiError(
+ "Max number of thread participants has been reached",
+ 30033,
),
- GUILD_ALREADY_HAS_TEMPLATE: new ApiError("Guild already has a template", 30031),
- MAXIMUM_THREAD_PARTICIPANTS: new ApiError("Max number of thread participants has been reached", 30033),
MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError(
"Maximum number of bans for non-guild members have been exceeded",
- 30035
+ 30035,
+ ),
+ MAXIMUM_BANS_FETCHES: new ApiError(
+ "Maximum number of bans fetches has been reached",
+ 30037,
),
- MAXIMUM_BANS_FETCHES: new ApiError("Maximum number of bans fetches has been reached", 30037),
MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039),
- MAXIMUM_PRUNE_REQUESTS: new ApiError("Maximum number of prune requests has been reached. Try again later", 30040),
- UNAUTHORIZED: new ApiError("Unauthorized. Provide a valid token and try again", 40001),
+ MAXIMUM_PRUNE_REQUESTS: new ApiError(
+ "Maximum number of prune requests has been reached. Try again later",
+ 30040,
+ ),
+ UNAUTHORIZED: new ApiError(
+ "Unauthorized. Provide a valid token and try again",
+ 40001,
+ ),
ACCOUNT_VERIFICATION_REQUIRED: new ApiError(
"You need to verify your account in order to perform this action",
- 40002
+ 40002,
+ ),
+ OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError(
+ "You are opening direct messages too fast",
+ 40003,
+ ),
+ REQUEST_ENTITY_TOO_LARGE: new ApiError(
+ "Request entity too large. Try sending something smaller in size",
+ 40005,
+ ),
+ FEATURE_TEMPORARILY_DISABLED: new ApiError(
+ "This feature has been temporarily disabled server-side",
+ 40006,
),
- OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError("You are opening direct messages too fast", 40003),
- REQUEST_ENTITY_TOO_LARGE: new ApiError("Request entity too large. Try sending something smaller in size", 40005),
- FEATURE_TEMPORARILY_DISABLED: new ApiError("This feature has been temporarily disabled server-side", 40006),
USER_BANNED: new ApiError("The user is banned from this guild", 40007),
- TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError("Target user is not connected to voice", 40032),
- ALREADY_CROSSPOSTED: new ApiError("This message has already been crossposted", 40033),
- APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError("An application command with that name already exists", 40041),
+ TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError(
+ "Target user is not connected to voice",
+ 40032,
+ ),
+ ALREADY_CROSSPOSTED: new ApiError(
+ "This message has already been crossposted",
+ 40033,
+ ),
+ APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError(
+ "An application command with that name already exists",
+ 40041,
+ ),
MISSING_ACCESS: new ApiError("Missing access", 50001),
INVALID_ACCOUNT_TYPE: new ApiError("Invalid account type", 50002),
- CANNOT_EXECUTE_ON_DM: new ApiError("Cannot execute action on a DM channel", 50003),
+ CANNOT_EXECUTE_ON_DM: new ApiError(
+ "Cannot execute action on a DM channel",
+ 50003,
+ ),
EMBED_DISABLED: new ApiError("Guild widget disabled", 50004),
- CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError("Cannot edit a message authored by another user", 50005),
- CANNOT_SEND_EMPTY_MESSAGE: new ApiError("Cannot send an empty message", 50006),
- CANNOT_MESSAGE_USER: new ApiError("Cannot send messages to this user", 50007),
- CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError("Cannot send messages in a voice channel", 50008),
+ CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError(
+ "Cannot edit a message authored by another user",
+ 50005,
+ ),
+ CANNOT_SEND_EMPTY_MESSAGE: new ApiError(
+ "Cannot send an empty message",
+ 50006,
+ ),
+ CANNOT_MESSAGE_USER: new ApiError(
+ "Cannot send messages to this user",
+ 50007,
+ ),
+ CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError(
+ "Cannot send messages in a voice channel",
+ 50008,
+ ),
CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError(
"Channel verification level is too high for you to gain access",
- 50009
+ 50009,
+ ),
+ OAUTH2_APPLICATION_BOT_ABSENT: new ApiError(
+ "OAuth2 application does not have a bot",
+ 50010,
+ ),
+ MAXIMUM_OAUTH2_APPLICATIONS: new ApiError(
+ "OAuth2 application limit reached",
+ 50011,
),
- OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010),
- MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011),
INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012),
- MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action ({})", 50013, undefined, [""]),
- INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014),
+ MISSING_PERMISSIONS: new ApiError(
+ "You lack permissions to perform that action ({})",
+ 50013,
+ undefined,
+ [""],
+ ),
+ INVALID_AUTHENTICATION_TOKEN: new ApiError(
+ "Invalid authentication token provided",
+ 50014,
+ ),
NOTE_TOO_LONG: new ApiError("Note was too long", 50015),
INVALID_BULK_DELETE_QUANTITY: new ApiError(
"Provided too few or too many messages to delete. Must provide at least {} and fewer than {} messages to delete",
50016,
undefined,
- ["2", "100"]
+ ["2", "100"],
),
CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError(
"A message can only be pinned to the channel it was sent in",
- 50019
- ),
- INVALID_OR_TAKEN_INVITE_CODE: new ApiError("Invite code was either invalid or taken", 50020),
- CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError("Cannot execute action on a system message", 50021),
- CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError("Cannot execute action on this channel type", 50024),
- INVALID_OAUTH_TOKEN: new ApiError("Invalid OAuth2 access token provided", 50025),
- MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError("Missing required OAuth2 scope", 50026),
- INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError("Invalid webhook token provided", 50027),
+ 50019,
+ ),
+ INVALID_OR_TAKEN_INVITE_CODE: new ApiError(
+ "Invite code was either invalid or taken",
+ 50020,
+ ),
+ CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError(
+ "Cannot execute action on a system message",
+ 50021,
+ ),
+ CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError(
+ "Cannot execute action on this channel type",
+ 50024,
+ ),
+ INVALID_OAUTH_TOKEN: new ApiError(
+ "Invalid OAuth2 access token provided",
+ 50025,
+ ),
+ MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError(
+ "Missing required OAuth2 scope",
+ 50026,
+ ),
+ INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError(
+ "Invalid webhook token provided",
+ 50027,
+ ),
INVALID_ROLE: new ApiError("Invalid role", 50028),
INVALID_RECIPIENT: new ApiError("Invalid Recipient(s)", 50033),
- BULK_DELETE_MESSAGE_TOO_OLD: new ApiError("A message provided was too old to bulk delete", 50034),
+ BULK_DELETE_MESSAGE_TOO_OLD: new ApiError(
+ "A message provided was too old to bulk delete",
+ 50034,
+ ),
INVALID_FORM_BODY: new ApiError(
"Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided",
- 50035
+ 50035,
),
INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError(
"An invite was accepted to a guild the application's bot is not in",
- 50036
+ 50036,
),
INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041),
- FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045),
+ FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError(
+ "File uploaded exceeds the maximum size",
+ 50045,
+ ),
INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046),
- CANNOT_SELF_REDEEM_GIFT: new ApiError("Cannot self-redeem this gift", 50054),
- PAYMENT_SOURCE_REQUIRED: new ApiError("Payment source required to redeem gift", 50070),
+ CANNOT_SELF_REDEEM_GIFT: new ApiError(
+ "Cannot self-redeem this gift",
+ 50054,
+ ),
+ PAYMENT_SOURCE_REQUIRED: new ApiError(
+ "Payment source required to redeem gift",
+ 50070,
+ ),
CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError(
"Cannot delete a channel required for Community guilds",
- 50074
+ 50074,
),
INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081),
CANNOT_EDIT_ARCHIVED_THREAD: new ApiError(
"Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread",
- 50083
+ 50083,
+ ),
+ INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError(
+ "Invalid thread notification settings",
+ 50084,
),
- INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError("Invalid thread notification settings", 50084),
BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError(
"before value is earlier than the thread creation date",
- 50085
+ 50085,
+ ),
+ SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError(
+ "This server is not available in your location",
+ 50095,
),
- SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError("This server is not available in your location", 50095),
SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError(
"This server needs monetization enabled in order to perform this action",
- 50097
+ 50097,
+ ),
+ TWO_FACTOR_REQUIRED: new ApiError(
+ "Two factor is required for this operation",
+ 60003,
+ ),
+ NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError(
+ "No users with DiscordTag exist",
+ 80004,
),
- TWO_FACTOR_REQUIRED: new ApiError("Two factor is required for this operation", 60003),
- NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError("No users with DiscordTag exist", 80004),
REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001),
- RESOURCE_OVERLOADED: new ApiError("API resource is currently overloaded. Try again a little later", 130000),
+ RESOURCE_OVERLOADED: new ApiError(
+ "API resource is currently overloaded. Try again a little later",
+ 130000,
+ ),
STAGE_ALREADY_OPEN: new ApiError("The Stage is already open", 150006),
- THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004),
+ THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError(
+ "A thread has already been created for this message",
+ 160004,
+ ),
THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005),
- MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError("Maximum number of active threads reached", 160006),
+ MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError(
+ "Maximum number of active threads reached",
+ 160006,
+ ),
MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError(
"Maximum number of active announcement threads reached",
- 160007
+ 160007,
+ ),
+ INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError(
+ "Invalid JSON for uploaded Lottie file",
+ 170001,
),
- INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError("Invalid JSON for uploaded Lottie file", 170001),
LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError(
"Uploaded Lotties cannot contain rasterized images such as PNG or JPEG",
- 170002
+ 170002,
+ ),
+ STICKER_MAXIMUM_FRAMERATE: new ApiError(
+ "Sticker maximum framerate exceeded",
+ 170003,
+ ),
+ STICKER_MAXIMUM_FRAME_COUNT: new ApiError(
+ "Sticker frame count exceeds maximum of {} frames",
+ 170004,
+ undefined,
+ ["1000"],
+ ),
+ LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError(
+ "Lottie animation maximum dimensions exceeded",
+ 170005,
),
- STICKER_MAXIMUM_FRAMERATE: new ApiError("Sticker maximum framerate exceeded", 170003),
- STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, [
- "1000",
- ]),
- LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError("Lottie animation maximum dimensions exceeded", 170005),
STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError(
"Sticker frame rate is either too small or too large",
- 170006
+ 170006,
),
STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError(
"Sticker animation duration exceeds maximum of {} seconds",
170007,
undefined,
- ["5"]
+ ["5"],
),
//Other errors
@@ -731,26 +982,92 @@ export const DiscordApiErrors = {
* An error encountered while performing an API request (Fosscord only). Here are the potential errors:
*/
export const FosscordApiErrors = {
- MANUALLY_TRIGGERED_ERROR: new ApiError("This is an artificial error", 1, 500),
- PREMIUM_DISABLED_FOR_GUILD: new ApiError("This guild cannot be boosted", 25001),
- NO_FURTHER_PREMIUM: new ApiError("This guild does not receive further boosts", 25002),
- GUILD_PREMIUM_DISABLED_FOR_YOU: new ApiError("This guild cannot be boosted by you", 25003, 403),
+ MANUALLY_TRIGGERED_ERROR: new ApiError(
+ "This is an artificial error",
+ 1,
+ 500,
+ ),
+ PREMIUM_DISABLED_FOR_GUILD: new ApiError(
+ "This guild cannot be boosted",
+ 25001,
+ ),
+ NO_FURTHER_PREMIUM: new ApiError(
+ "This guild does not receive further boosts",
+ 25002,
+ ),
+ GUILD_PREMIUM_DISABLED_FOR_YOU: new ApiError(
+ "This guild cannot be boosted by you",
+ 25003,
+ 403,
+ ),
CANNOT_FRIEND_SELF: new ApiError("Cannot friend oneself", 25009),
- USER_SPECIFIC_INVITE_WRONG_RECIPIENT: new ApiError("This invite is not meant for you", 25010),
+ USER_SPECIFIC_INVITE_WRONG_RECIPIENT: new ApiError(
+ "This invite is not meant for you",
+ 25010,
+ ),
USER_SPECIFIC_INVITE_FAILED: new ApiError("Failed to invite user", 25011),
- CANNOT_MODIFY_USER_GROUP: new ApiError("This user cannot manipulate this group", 25050, 403),
- CANNOT_REMOVE_SELF_FROM_GROUP: new ApiError("This user cannot remove oneself from user group", 25051),
- CANNOT_BAN_OPERATOR: new ApiError("Non-OPERATOR cannot ban OPERATOR from instance", 25052),
- CANNOT_LEAVE_GUILD: new ApiError("You are not allowed to leave guilds that you joined by yourself", 25059, 403),
- EDITS_DISABLED: new ApiError("You are not allowed to edit your own messages", 25060, 403),
- DELETE_MESSAGE_DISABLED: new ApiError("You are not allowed to delete your own messages", 25061, 403),
- FEATURE_PERMANENTLY_DISABLED: new ApiError("This feature has been disabled server-side", 45006, 501),
- MISSING_RIGHTS: new ApiError("You lack rights to perform that action ({})", 50013, undefined, [""]),
- CANNOT_REPLACE_BY_BACKFILL: new ApiError("Cannot backfill to message ID that already exists", 55002, 409),
- CANNOT_BACKFILL_TO_THE_FUTURE: new ApiError("You cannot backfill messages in the future", 55003),
- CANNOT_GRANT_PERMISSIONS_EXCEEDING_RIGHTS: new ApiError("You cannot grant permissions exceeding your own rights", 50050),
- ROUTES_LOOPING: new ApiError("Loops in the route definition ({})", 50060, undefined, [""]),
- CANNOT_REMOVE_ROUTE: new ApiError("Cannot remove message route while it is in effect and being used", 50061),
+ CANNOT_MODIFY_USER_GROUP: new ApiError(
+ "This user cannot manipulate this group",
+ 25050,
+ 403,
+ ),
+ CANNOT_REMOVE_SELF_FROM_GROUP: new ApiError(
+ "This user cannot remove oneself from user group",
+ 25051,
+ ),
+ CANNOT_BAN_OPERATOR: new ApiError(
+ "Non-OPERATOR cannot ban OPERATOR from instance",
+ 25052,
+ ),
+ CANNOT_LEAVE_GUILD: new ApiError(
+ "You are not allowed to leave guilds that you joined by yourself",
+ 25059,
+ 403,
+ ),
+ EDITS_DISABLED: new ApiError(
+ "You are not allowed to edit your own messages",
+ 25060,
+ 403,
+ ),
+ DELETE_MESSAGE_DISABLED: new ApiError(
+ "You are not allowed to delete your own messages",
+ 25061,
+ 403,
+ ),
+ FEATURE_PERMANENTLY_DISABLED: new ApiError(
+ "This feature has been disabled server-side",
+ 45006,
+ 501,
+ ),
+ MISSING_RIGHTS: new ApiError(
+ "You lack rights to perform that action ({})",
+ 50013,
+ undefined,
+ [""],
+ ),
+ CANNOT_REPLACE_BY_BACKFILL: new ApiError(
+ "Cannot backfill to message ID that already exists",
+ 55002,
+ 409,
+ ),
+ CANNOT_BACKFILL_TO_THE_FUTURE: new ApiError(
+ "You cannot backfill messages in the future",
+ 55003,
+ ),
+ CANNOT_GRANT_PERMISSIONS_EXCEEDING_RIGHTS: new ApiError(
+ "You cannot grant permissions exceeding your own rights",
+ 50050,
+ ),
+ ROUTES_LOOPING: new ApiError(
+ "Loops in the route definition ({})",
+ 50060,
+ undefined,
+ [""],
+ ),
+ CANNOT_REMOVE_ROUTE: new ApiError(
+ "Cannot remove message route while it is in effect and being used",
+ 50061,
+ ),
};
/**
@@ -769,11 +1086,7 @@ export const DefaultMessageNotifications = ["ALL", "MENTIONS", "MUTED"];
* * INSERTED (Fosscord extension)
* @typedef {string} MembershipStates
*/
-export const MembershipStates = [
- "INSERTED",
- "INVITED",
- "ACCEPTED",
-];
+export const MembershipStates = ["INSERTED", "INVITED", "ACCEPTED"];
/**
* The value set for a webhook's type:
@@ -782,15 +1095,10 @@ export const MembershipStates = [
* * Custom (Fosscord extension)
* @typedef {string} WebhookTypes
*/
-export const WebhookTypes = [
- "Custom",
- "Incoming",
- "Channel Follower",
-];
+export const WebhookTypes = ["Custom", "Incoming", "Channel Follower"];
function keyMirror(arr: string[]) {
let tmp = Object.create(null);
for (const value of arr) tmp[value] = value;
return tmp;
}
-
diff --git a/src/util/util/Database.ts b/src/util/util/Database.ts
index ddbea57d..e96be6c4 100644
--- a/src/util/util/Database.ts
+++ b/src/util/util/Database.ts
@@ -9,7 +9,8 @@ import { yellow, green, red } from "picocolors";
// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class
var dbConnection: DataSource | undefined;
-let dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");
+let dbConnectionString =
+ process.env.DATABASE || path.join(process.cwd(), "database.db");
export function getDatabase(): DataSource | null {
// if (!dbConnection) throw new Error("Tried to get database before it was initialised");
@@ -20,18 +21,24 @@ export function getDatabase(): DataSource | null {
export async function initDatabase(): Promise<DataSource> {
if (dbConnection) return dbConnection;
- const type = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite";
+ const type = dbConnectionString.includes("://")
+ ? dbConnectionString.split(":")[0]?.replace("+srv", "")
+ : "sqlite";
const isSqlite = type.includes("sqlite");
console.log(`[Database] ${yellow(`connecting to ${type} db`)}`);
if (isSqlite) {
- console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`);
+ console.log(
+ `[Database] ${red(
+ `You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`,
+ )}`,
+ );
}
const dataSource = new DataSource({
//@ts-ignore
type,
- charset: 'utf8mb4',
+ charset: "utf8mb4",
url: isSqlite ? undefined : dbConnectionString,
database: isSqlite ? dbConnectionString : undefined,
entities: ["dist/util/entities/*.js"],
diff --git a/src/util/util/Email.ts b/src/util/util/Email.ts
index 6885da33..c98ccff0 100644
--- a/src/util/util/Email.ts
+++ b/src/util/util/Email.ts
@@ -15,7 +15,7 @@ export function adjustEmail(email?: string): string | undefined {
// replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
let v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
}
-
+
if (domain === "google.com") {
// replace .dots and +alternatives -> Google Staff GMail Dot Trick
let v = user.replace(/[.]|(\+.*)/g, "") + "@google.com";
diff --git a/src/util/util/Event.ts b/src/util/util/Event.ts
index 20a638a0..c81de951 100644
--- a/src/util/util/Event.ts
+++ b/src/util/util/Event.ts
@@ -5,15 +5,27 @@ import { EVENT, Event } from "../interfaces";
export const events = new EventEmitter();
export async function emitEvent(payload: Omit<Event, "created_at">) {
- const id = (payload.channel_id || payload.user_id || payload.guild_id) as string;
+ const id = (payload.channel_id ||
+ payload.user_id ||
+ payload.guild_id) as string;
if (!id) return console.error("event doesn't contain any id", payload);
if (RabbitMQ.connection) {
- const data = typeof payload.data === "object" ? JSON.stringify(payload.data) : payload.data; // use rabbitmq for event transmission
- await RabbitMQ.channel?.assertExchange(id, "fanout", { durable: false });
+ const data =
+ typeof payload.data === "object"
+ ? JSON.stringify(payload.data)
+ : payload.data; // use rabbitmq for event transmission
+ await RabbitMQ.channel?.assertExchange(id, "fanout", {
+ durable: false,
+ });
// assertQueue isn't needed, because a queue will automatically created if it doesn't exist
- const successful = RabbitMQ.channel?.publish(id, "", Buffer.from(`${data}`), { type: payload.event });
+ const successful = RabbitMQ.channel?.publish(
+ id,
+ "",
+ Buffer.from(`${data}`),
+ { type: payload.event },
+ );
if (!successful) throw new Error("failed to send event");
} else if (process.env.EVENT_TRANSMISSION === "process") {
process.send?.({ type: "event", event: payload, id } as ProcessEvent);
@@ -48,10 +60,19 @@ export interface ProcessEvent {
id: string;
}
-export async function listenEvent(event: string, callback: (event: EventOpts) => any, opts?: ListenEventOpts) {
+export async function listenEvent(
+ event: string,
+ callback: (event: EventOpts) => any,
+ opts?: ListenEventOpts,
+) {
if (RabbitMQ.connection) {
- // @ts-ignore
- return rabbitListen(opts?.channel || RabbitMQ.channel, event, callback, { acknowledge: opts?.acknowledge });
+ return rabbitListen(
+ // @ts-ignore
+ opts?.channel || RabbitMQ.channel,
+ event,
+ callback,
+ { acknowledge: opts?.acknowledge },
+ );
} else if (process.env.EVENT_TRANSMISSION === "process") {
const cancel = () => {
process.removeListener("message", listener);
@@ -59,7 +80,9 @@ export async function listenEvent(event: string, callback: (event: EventOpts) =>
};
const listener = (msg: ProcessEvent) => {
- msg.type === "event" && msg.id === event && callback({ ...msg.event, cancel });
+ msg.type === "event" &&
+ msg.id === event &&
+ callback({ ...msg.event, cancel });
};
//@ts-ignore apparently theres no function addListener with this signature
@@ -84,10 +107,13 @@ async function rabbitListen(
channel: Channel,
id: string,
callback: (event: EventOpts) => any,
- opts?: { acknowledge?: boolean }
+ opts?: { acknowledge?: boolean },
) {
await channel.assertExchange(id, "fanout", { durable: false });
- const q = await channel.assertQueue("", { exclusive: true, autoDelete: true });
+ const q = await channel.assertQueue("", {
+ exclusive: true,
+ autoDelete: true,
+ });
const cancel = () => {
channel.cancel(q.queue);
@@ -116,7 +142,7 @@ async function rabbitListen(
},
{
noAck: !opts?.acknowledge,
- }
+ },
);
return cancel;
diff --git a/src/util/util/FieldError.ts b/src/util/util/FieldError.ts
index 406b33e8..24818fed 100644
--- a/src/util/util/FieldError.ts
+++ b/src/util/util/FieldError.ts
@@ -1,6 +1,8 @@
import "missing-native-js-functions";
-export function FieldErrors(fields: Record<string, { code?: string; message: string }>) {
+export function FieldErrors(
+ fields: Record<string, { code?: string; message: string }>,
+) {
return new FieldError(
50035,
"Invalid Form Body",
@@ -11,7 +13,7 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
code: code || "BASE_TYPE_INVALID",
},
],
- }))
+ })),
);
}
@@ -19,7 +21,11 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
// Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided.
export class FieldError extends Error {
- constructor(public code: string | number, public message: string, public errors?: any) {
+ constructor(
+ public code: string | number,
+ public message: string,
+ public errors?: any,
+ ) {
super(message);
}
}
diff --git a/src/util/util/Intents.ts b/src/util/util/Intents.ts
index 1e840b76..b9f4d65a 100644
--- a/src/util/util/Intents.ts
+++ b/src/util/util/Intents.ts
@@ -22,13 +22,12 @@ export class Intents extends BitField {
GUILD_POLICY_EXECUTION: BigInt(1) << BigInt(21), // guild policy execution
LIVE_MESSAGE_COMPOSITION: BigInt(1) << BigInt(32), // allow composing messages using the gateway
GUILD_ROUTES: BigInt(1) << BigInt(41), // message routes affecting the guild
- DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42), // direct message threads
+ DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42), // direct message threads
JUMBO_EVENTS: BigInt(1) << BigInt(43), // jumbo events (size limits to be defined later)
LOBBIES: BigInt(1) << BigInt(44), // lobbies
- INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes
+ INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes
INSTANCE_GUILD_CHANGES: BigInt(1) << BigInt(61), // all guild create, guild object patch, split, merge and delete events
INSTANCE_POLICY_UPDATES: BigInt(1) << BigInt(62), // all instance policy updates
- INSTANCE_USER_UPDATES: BigInt(1) << BigInt(63) // all instance user updates
+ INSTANCE_USER_UPDATES: BigInt(1) << BigInt(63), // all instance user updates
};
}
-
diff --git a/src/util/util/InvisibleCharacters.ts b/src/util/util/InvisibleCharacters.ts
index a48cfab0..da295d68 100644
--- a/src/util/util/InvisibleCharacters.ts
+++ b/src/util/util/InvisibleCharacters.ts
@@ -1,56 +1,56 @@
-// List from https://invisible-characters.com/
-export const InvisibleCharacters = [
- '\u{9}', //Tab
- //'\u{20}', //Space //categories can have spaces in them
- '\u{ad}', //Soft hyphen
- '\u{34f}', //Combining grapheme joiner
- '\u{61c}', //Arabic letter mark
- '\u{115f}', //Hangul choseong filler
- '\u{1160}', //Hangul jungseong filler
- '\u{17b4}', //Khmer vowel inherent AQ
- '\u{17b5}', //Khmer vowel inherent AA
- '\u{180e}', //Mongolian vowel separator
- '\u{2000}', //En quad
- '\u{2001}', //Em quad
- '\u{2002}', //En space
- '\u{2003}', //Em space
- '\u{2004}', //Three-per-em space
- '\u{2005}', //Four-per-em space
- '\u{2006}', //Six-per-em space
- '\u{2007}', //Figure space
- '\u{2008}', //Punctuation space
- '\u{2009}', //Thin space
- '\u{200a}', //Hair space
- '\u{200b}', //Zero width space
- '\u{200c}', //Zero width non-joiner
- '\u{200d}', //Zero width joiner
- '\u{200e}', //Left-to-right mark
- '\u{200f}', //Right-to-left mark
- '\u{202f}', //Narrow no-break space
- '\u{205f}', //Medium mathematical space
- '\u{2060}', //Word joiner
- '\u{2061}', //Function application
- '\u{2062}', //Invisible times
- '\u{2063}', //Invisible separator
- '\u{2064}', //Invisible plus
- '\u{206a}', //Inhibit symmetric swapping
- '\u{206b}', //Activate symmetric swapping
- '\u{206c}', //Inhibit arabic form shaping
- '\u{206d}', //Activate arabic form shaping
- '\u{206e}', //National digit shapes
- '\u{206f}', //Nominal digit shapes
- '\u{3000}', //Ideographic space
- '\u{2800}', //Braille pattern blank
- '\u{3164}', //Hangul filler
- '\u{feff}', //Zero width no-break space
- '\u{ffa0}', //Haldwidth hangul filler
- '\u{1d159}', //Musical symbol null notehead
- '\u{1d173}', //Musical symbol begin beam
- '\u{1d174}', //Musical symbol end beam
- '\u{1d175}', //Musical symbol begin tie
- '\u{1d176}', //Musical symbol end tie
- '\u{1d177}', //Musical symbol begin slur
- '\u{1d178}', //Musical symbol end slur
- '\u{1d179}', //Musical symbol begin phrase
- '\u{1d17a}' //Musical symbol end phrase
-];
\ No newline at end of file
+// List from https://invisible-characters.com/
+export const InvisibleCharacters = [
+ "\u{9}", //Tab
+ //'\u{20}', //Space //categories can have spaces in them
+ "\u{ad}", //Soft hyphen
+ "\u{34f}", //Combining grapheme joiner
+ "\u{61c}", //Arabic letter mark
+ "\u{115f}", //Hangul choseong filler
+ "\u{1160}", //Hangul jungseong filler
+ "\u{17b4}", //Khmer vowel inherent AQ
+ "\u{17b5}", //Khmer vowel inherent AA
+ "\u{180e}", //Mongolian vowel separator
+ "\u{2000}", //En quad
+ "\u{2001}", //Em quad
+ "\u{2002}", //En space
+ "\u{2003}", //Em space
+ "\u{2004}", //Three-per-em space
+ "\u{2005}", //Four-per-em space
+ "\u{2006}", //Six-per-em space
+ "\u{2007}", //Figure space
+ "\u{2008}", //Punctuation space
+ "\u{2009}", //Thin space
+ "\u{200a}", //Hair space
+ "\u{200b}", //Zero width space
+ "\u{200c}", //Zero width non-joiner
+ "\u{200d}", //Zero width joiner
+ "\u{200e}", //Left-to-right mark
+ "\u{200f}", //Right-to-left mark
+ "\u{202f}", //Narrow no-break space
+ "\u{205f}", //Medium mathematical space
+ "\u{2060}", //Word joiner
+ "\u{2061}", //Function application
+ "\u{2062}", //Invisible times
+ "\u{2063}", //Invisible separator
+ "\u{2064}", //Invisible plus
+ "\u{206a}", //Inhibit symmetric swapping
+ "\u{206b}", //Activate symmetric swapping
+ "\u{206c}", //Inhibit arabic form shaping
+ "\u{206d}", //Activate arabic form shaping
+ "\u{206e}", //National digit shapes
+ "\u{206f}", //Nominal digit shapes
+ "\u{3000}", //Ideographic space
+ "\u{2800}", //Braille pattern blank
+ "\u{3164}", //Hangul filler
+ "\u{feff}", //Zero width no-break space
+ "\u{ffa0}", //Haldwidth hangul filler
+ "\u{1d159}", //Musical symbol null notehead
+ "\u{1d173}", //Musical symbol begin beam
+ "\u{1d174}", //Musical symbol end beam
+ "\u{1d175}", //Musical symbol begin tie
+ "\u{1d176}", //Musical symbol end tie
+ "\u{1d177}", //Musical symbol begin slur
+ "\u{1d178}", //Musical symbol end slur
+ "\u{1d179}", //Musical symbol begin phrase
+ "\u{1d17a}", //Musical symbol end phrase
+];
diff --git a/src/util/util/Permissions.ts b/src/util/util/Permissions.ts
index a432af76..0c12487e 100644
--- a/src/util/util/Permissions.ts
+++ b/src/util/util/Permissions.ts
@@ -1,6 +1,12 @@
// https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js
// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
-import { Channel, ChannelPermissionOverwrite, Guild, Member, Role } from "../entities";
+import {
+ Channel,
+ ChannelPermissionOverwrite,
+ Guild,
+ Member,
+ Role,
+} from "../entities";
import { BitField } from "./BitField";
import "missing-native-js-functions";
import { BitFieldResolvable, BitFlag } from "./BitField";
@@ -13,7 +19,12 @@ try {
HTTPError = Error;
}
-export type PermissionResolvable = bigint | number | Permissions | PermissionResolvable[] | PermissionString;
+export type PermissionResolvable =
+ | bigint
+ | number
+ | Permissions
+ | PermissionResolvable[]
+ | PermissionString;
type PermissionString = keyof typeof Permissions.FLAGS;
@@ -80,14 +91,20 @@ export class Permissions extends BitField {
};
any(permission: PermissionResolvable, checkAdmin = true) {
- return (checkAdmin && super.any(Permissions.FLAGS.ADMINISTRATOR)) || super.any(permission);
+ return (
+ (checkAdmin && super.any(Permissions.FLAGS.ADMINISTRATOR)) ||
+ super.any(permission)
+ );
}
/**
* Checks whether the bitfield has a permission, or multiple permissions.
*/
has(permission: PermissionResolvable, checkAdmin = true) {
- return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.has(permission);
+ return (
+ (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) ||
+ super.has(permission)
+ );
}
/**
@@ -96,28 +113,39 @@ export class Permissions extends BitField {
hasThrow(permission: PermissionResolvable) {
if (this.has(permission) && this.has("VIEW_CHANNEL")) return true;
// @ts-ignore
- throw new HTTPError(`You are missing the following permissions ${permission}`, 403);
+ throw new HTTPError(
+ `You are missing the following permissions ${permission}`,
+ 403,
+ );
}
overwriteChannel(overwrites: ChannelPermissionOverwrite[]) {
if (!overwrites) return this;
if (!this.cache) throw new Error("permission chache not available");
overwrites = overwrites.filter((x) => {
- if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id)) return true;
+ if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id))
+ return true;
if (x.type === 1 && x.id == this.cache.user_id) return true;
return false;
});
- return new Permissions(Permissions.channelPermission(overwrites, this.bitfield));
+ return new Permissions(
+ Permissions.channelPermission(overwrites, this.bitfield),
+ );
}
- static channelPermission(overwrites: ChannelPermissionOverwrite[], init?: bigint) {
+ static channelPermission(
+ overwrites: ChannelPermissionOverwrite[],
+ init?: bigint,
+ ) {
// TODO: do not deny any permissions if admin
return overwrites.reduce((permission, overwrite) => {
// apply disallowed permission
// * permission: current calculated permission (e.g. 010)
// * deny contains all denied permissions (e.g. 011)
// * allow contains all explicitly allowed permisions (e.g. 100)
- return (permission & ~BigInt(overwrite.deny)) | BigInt(overwrite.allow);
+ return (
+ (permission & ~BigInt(overwrite.deny)) | BigInt(overwrite.allow)
+ );
// ~ operator inverts deny (e.g. 011 -> 100)
// & operator only allows 1 for both ~deny and permission (e.g. 010 & 100 -> 000)
// | operators adds both together (e.g. 000 + 100 -> 100)
@@ -126,7 +154,10 @@ export class Permissions extends BitField {
static rolePermission(roles: Role[]) {
// adds all permissions of all roles together (Bit OR)
- return roles.reduce((permission, role) => permission | BigInt(role.permissions), BigInt(0));
+ return roles.reduce(
+ (permission, role) => permission | BigInt(role.permissions),
+ BigInt(0),
+ );
}
static finalPermission({
@@ -157,7 +188,8 @@ export class Permissions extends BitField {
}
if (channel?.recipient_ids) {
- if (channel?.owner_id === user.id) return new Permissions("ADMINISTRATOR");
+ if (channel?.owner_id === user.id)
+ return new Permissions("ADMINISTRATOR");
if (channel.recipient_ids.includes(user.id)) {
// Default dm permissions
return new Permissions([
@@ -183,7 +215,10 @@ export class Permissions extends BitField {
}
}
-const ALL_PERMISSIONS = Object.values(Permissions.FLAGS).reduce((total, val) => total | val, BigInt(0));
+const ALL_PERMISSIONS = Object.values(Permissions.FLAGS).reduce(
+ (total, val) => total | val,
+ BigInt(0),
+);
export type PermissionCache = {
channel?: Channel | undefined;
@@ -204,7 +239,7 @@ export async function getPermission(
channel_relations?: string[];
member_select?: (keyof Member)[];
member_relations?: string[];
- } = {}
+ } = {},
) {
if (!user_id) throw new HTTPError("User not found");
var channel: Channel | undefined;
@@ -239,16 +274,17 @@ export async function getPermission(
],
relations: opts.guild_relations,
});
- if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR);
+ if (guild.owner_id === user_id)
+ return new Permissions(Permissions.FLAGS.ADMINISTRATOR);
member = await Member.findOneOrFail({
where: { guild_id, id: user_id },
relations: ["roles", ...(opts.member_relations || [])],
// select: [
- // "id", // TODO: Bug in typeorm? adding these selects breaks the query.
- // "roles",
- // @ts-ignore
- // ...(opts.member_select || []),
+ // "id", // TODO: Bug in typeorm? adding these selects breaks the query.
+ // "roles",
+ // @ts-ignore
+ // ...(opts.member_select || []),
// ],
});
}
diff --git a/src/util/util/RabbitMQ.ts b/src/util/util/RabbitMQ.ts
index 0f5eb6aa..1bfb3f5c 100644
--- a/src/util/util/RabbitMQ.ts
+++ b/src/util/util/RabbitMQ.ts
@@ -1,7 +1,11 @@
import amqp, { Connection, Channel } from "amqplib";
// import Config from "./Config";
-export const RabbitMQ: { connection: Connection | null; channel: Channel | null; init: () => Promise<void> } = {
+export const RabbitMQ: {
+ connection: Connection | null;
+ channel: Channel | null;
+ init: () => Promise<void>;
+} = {
connection: null,
channel: null,
init: async function () {
diff --git a/src/util/util/Rights.ts b/src/util/util/Rights.ts
index b28c75b7..659353a6 100644
--- a/src/util/util/Rights.ts
+++ b/src/util/util/Rights.ts
@@ -11,7 +11,12 @@ try {
HTTPError = Error;
}
-export type RightResolvable = bigint | number | Rights | RightResolvable[] | RightString;
+export type RightResolvable =
+ | bigint
+ | number
+ | Rights
+ | RightResolvable[]
+ | RightString;
type RightString = keyof typeof Rights.FLAGS;
// TODO: just like roles for members, users should have privilidges which combine multiple rights into one and make it easy to assign
@@ -60,7 +65,7 @@ export class Rights extends BitField {
CREDITABLE: BitFlag(32), // can receive money from monetisation related features
KICK_BAN_MEMBERS: BitFlag(33),
// can kick or ban guild or group DM members in the guilds/groups that they have KICK_MEMBERS, or BAN_MEMBERS
- SELF_LEAVE_GROUPS: BitFlag(34),
+ SELF_LEAVE_GROUPS: BitFlag(34),
// can leave the guilds or group DMs that they joined on their own (one can always leave a guild or group DMs they have been force-added)
PRESENCE: BitFlag(35),
// inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user
@@ -72,31 +77,44 @@ export class Rights extends BitField {
RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions
SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events
USE_MASS_INVITES: BitFlag(43), // added per @xnacly's request — can accept mass invites
- ACCEPT_INVITES: BitFlag(44) // added per @xnacly's request — can accept user-specific invites and DM requests
+ ACCEPT_INVITES: BitFlag(44), // added per @xnacly's request — can accept user-specific invites and DM requests
};
any(permission: RightResolvable, checkOperator = true) {
- return (checkOperator && super.any(Rights.FLAGS.OPERATOR)) || super.any(permission);
+ return (
+ (checkOperator && super.any(Rights.FLAGS.OPERATOR)) ||
+ super.any(permission)
+ );
}
has(permission: RightResolvable, checkOperator = true) {
- return (checkOperator && super.has(Rights.FLAGS.OPERATOR)) || super.has(permission);
+ return (
+ (checkOperator && super.has(Rights.FLAGS.OPERATOR)) ||
+ super.has(permission)
+ );
}
hasThrow(permission: RightResolvable) {
if (this.has(permission)) return true;
// @ts-ignore
- throw new HTTPError(`You are missing the following rights ${permission}`, 403);
+ throw new HTTPError(
+ `You are missing the following rights ${permission}`,
+ 403,
+ );
}
-
}
-const ALL_RIGHTS = Object.values(Rights.FLAGS).reduce((total, val) => total | val, BigInt(0));
+const ALL_RIGHTS = Object.values(Rights.FLAGS).reduce(
+ (total, val) => total | val,
+ BigInt(0),
+);
-export async function getRights( user_id: string
+export async function getRights(
+ user_id: string,
/**, opts: {
in_behalf?: (keyof User)[];
- } = {} **/) {
+ } = {} **/
+) {
let user = await User.findOneOrFail({ where: { id: user_id } });
return new Rights(user.rights);
-}
+}
diff --git a/src/util/util/Snowflake.ts b/src/util/util/Snowflake.ts
index 134d526e..69effb2e 100644
--- a/src/util/util/Snowflake.ts
+++ b/src/util/util/Snowflake.ts
@@ -17,7 +17,9 @@ export class Snowflake {
static workerId = BigInt((cluster.worker?.id || 0) % 31); // max 31
constructor() {
- throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
+ throw new Error(
+ `The ${this.constructor.name} class may not be instantiated.`,
+ );
}
/**
@@ -83,14 +85,15 @@ export class Snowflake {
return dec;
}
- static generateWorkerProcess() { // worker process - returns a number
+ static generateWorkerProcess() {
+ // worker process - returns a number
var time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22);
var worker = Snowflake.workerId << 17n;
var process = Snowflake.processId << 12n;
var increment = Snowflake.INCREMENT++;
return BigInt(time | worker | process | increment);
}
-
+
static generate() {
return Snowflake.generateWorkerProcess().toString();
}
@@ -111,7 +114,9 @@ export class Snowflake {
* @returns {DeconstructedSnowflake} Deconstructed snowflake
*/
static deconstruct(snowflake) {
- const BINARY = Snowflake.idToBinary(snowflake).toString(2).padStart(64, "0");
+ const BINARY = Snowflake.idToBinary(snowflake)
+ .toString(2)
+ .padStart(64, "0");
const res = {
timestamp: parseInt(BINARY.substring(0, 42), 2) + Snowflake.EPOCH,
workerID: parseInt(BINARY.substring(42, 47), 2),
diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts
index 5ba3e1ec..19e64f47 100644
--- a/src/util/util/Token.ts
+++ b/src/util/util/Token.ts
@@ -17,11 +17,14 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> {
const user = await User.findOne({
where: { id: decoded.id },
- select: ["data", "bot", "disabled", "deleted", "rights"]
+ select: ["data", "bot", "disabled", "deleted", "rights"],
});
if (!user) return rej("Invalid Token");
// we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds
- if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0))
+ if (
+ decoded.iat * 1000 <
+ new Date(user.data.valid_tokens_since).setSeconds(0, 0)
+ )
return rej("Invalid Token");
if (user.disabled) return rej("User disabled");
if (user.deleted) return rej("User not found");
@@ -45,7 +48,7 @@ export async function generateToken(id: string) {
(err, token) => {
if (err) return rej(err);
return res(token);
- }
+ },
);
});
}
diff --git a/src/util/util/TraverseDirectory.ts b/src/util/util/TraverseDirectory.ts
index 3d0d6279..3f5b0385 100644
--- a/src/util/util/TraverseDirectory.ts
+++ b/src/util/util/TraverseDirectory.ts
@@ -1,13 +1,14 @@
import { Server, traverseDirectory } from "lambert-server";
//if we're using ts-node, use ts files instead of js
-const extension = Symbol.for("ts-node.register.instance") in process ? "ts" : "js"
+const extension =
+ Symbol.for("ts-node.register.instance") in process ? "ts" : "js";
-const DEFAULT_FILTER = new RegExp("^([^\.].*)(?<!\.d)\.(" + extension + ")$");
+const DEFAULT_FILTER = new RegExp("^([^.].*)(?<!.d).(" + extension + ")$");
export function registerRoutes(server: Server, root: string) {
return traverseDirectory(
{ dirname: root, recursive: true, filter: DEFAULT_FILTER },
- server.registerRoute.bind(server, root)
+ server.registerRoute.bind(server, root),
);
}
diff --git a/src/util/util/cdn.ts b/src/util/util/cdn.ts
index 812a4e1d..6b2c8424 100644
--- a/src/util/util/cdn.ts
+++ b/src/util/util/cdn.ts
@@ -4,7 +4,10 @@ import fetch from "node-fetch";
import { Attachment } from "../entities";
import { Config } from "./Config";
-export async function uploadFile(path: string, file?: Express.Multer.File): Promise<Attachment> {
+export async function uploadFile(
+ path: string,
+ file?: Express.Multer.File,
+): Promise<Attachment> {
if (!file?.buffer) throw new HTTPError("Missing file in body");
const form = new FormData();
@@ -13,28 +16,38 @@ export async function uploadFile(path: string, file?: Express.Multer.File): Prom
filename: file.originalname,
});
- const response = await fetch(`${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, {
- headers: {
- signature: Config.get().security.requestSignature,
- ...form.getHeaders(),
+ const response = await fetch(
+ `${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`,
+ {
+ headers: {
+ signature: Config.get().security.requestSignature,
+ ...form.getHeaders(),
+ },
+ method: "POST",
+ body: form,
},
- method: "POST",
- body: form,
- });
- const result = await response.json() as Attachment;
+ );
+ const result = (await response.json()) as Attachment;
if (response.status !== 200) throw result;
return result;
}
-export async function handleFile(path: string, body?: string): Promise<string | undefined> {
+export async function handleFile(
+ path: string,
+ body?: string,
+): Promise<string | undefined> {
if (!body || !body.startsWith("data:")) return undefined;
try {
const mimetype = body.split(":")[1].split(";")[0];
const buffer = Buffer.from(body.split(",")[1], "base64");
// @ts-ignore
- const { id } = await uploadFile(path, { buffer, mimetype, originalname: "banner" });
+ const { id } = await uploadFile(path, {
+ buffer,
+ mimetype,
+ originalname: "banner",
+ });
return id;
} catch (error) {
console.error(error);
@@ -43,12 +56,15 @@ export async function handleFile(path: string, body?: string): Promise<string |
}
export async function deleteFile(path: string) {
- const response = await fetch(`${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, {
- headers: {
- signature: Config.get().security.requestSignature,
+ const response = await fetch(
+ `${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`,
+ {
+ headers: {
+ signature: Config.get().security.requestSignature,
+ },
+ method: "DELETE",
},
- method: "DELETE",
- });
+ );
const result = await response.json();
if (response.status !== 200) throw result;
diff --git a/src/util/util/index.ts b/src/util/util/index.ts
index b2bd6489..aa38e472 100644
--- a/src/util/util/index.ts
+++ b/src/util/util/index.ts
@@ -20,4 +20,4 @@ export * from "./String";
export * from "./Array";
export * from "./TraverseDirectory";
export * from "./InvisibleCharacters";
-export * from "./BannedWords";
\ No newline at end of file
+export * from "./BannedWords";
diff --git a/src/webrtc/Server.ts b/src/webrtc/Server.ts
index 32b795ea..d9a892a3 100644
--- a/src/webrtc/Server.ts
+++ b/src/webrtc/Server.ts
@@ -11,7 +11,15 @@ export class Server {
public server: http.Server;
public production: boolean;
- constructor({ port, server, production }: { port: number; server?: http.Server; production?: boolean }) {
+ constructor({
+ port,
+ server,
+ production,
+ }: {
+ port: number;
+ server?: http.Server;
+ production?: boolean;
+ }) {
this.port = port;
this.production = production || false;
@@ -53,4 +61,4 @@ export class Server {
closeDatabase();
this.server.close();
}
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/events/Close.ts b/src/webrtc/events/Close.ts
index 1c203653..4cf80bb2 100644
--- a/src/webrtc/events/Close.ts
+++ b/src/webrtc/events/Close.ts
@@ -6,4 +6,4 @@ export async function onClose(this: WebSocket, code: number, reason: string) {
if (this.session_id) await Session.delete({ session_id: this.session_id });
this.removeAllListeners();
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/events/Connection.ts b/src/webrtc/events/Connection.ts
index bf228d64..9300b6b2 100644
--- a/src/webrtc/events/Connection.ts
+++ b/src/webrtc/events/Connection.ts
@@ -14,7 +14,11 @@ try {
// TODO: specify rate limit in config
// TODO: check msg max size
-export async function Connection(this: WS.Server, socket: WebSocket, request: IncomingMessage) {
+export async function Connection(
+ this: WS.Server,
+ socket: WebSocket,
+ request: IncomingMessage,
+) {
try {
socket.on("close", onClose.bind(socket));
socket.on("message", onMessage.bind(socket));
@@ -29,7 +33,7 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In
"open",
"ping",
"pong",
- "unexpected-response"
+ "unexpected-response",
].forEach((x) => {
socket.on(x, (y) => console.log("[WebRTC]", x, y));
});
@@ -39,7 +43,8 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In
socket.encoding = "json";
socket.version = Number(searchParams.get("v")) || 5;
- if (socket.version < 3) return socket.close(CLOSECODES.Unknown_error, "invalid version");
+ if (socket.version < 3)
+ return socket.close(CLOSECODES.Unknown_error, "invalid version");
setHeartbeat(socket);
@@ -50,11 +55,11 @@ export async function Connection(this: WS.Server, socket: WebSocket, request: In
await Send(socket, {
op: VoiceOPCodes.HELLO,
d: {
- heartbeat_interval: 1000 * 30
- }
+ heartbeat_interval: 1000 * 30,
+ },
});
} catch (error) {
console.error("[WebRTC]", error);
return socket.close(CLOSECODES.Unknown_error);
}
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/events/Message.ts b/src/webrtc/events/Message.ts
index 8f75a815..38676f6c 100644
--- a/src/webrtc/events/Message.ts
+++ b/src/webrtc/events/Message.ts
@@ -7,13 +7,14 @@ const PayloadSchema = {
op: Number,
$d: new Tuple(Object, Number), // or number for heartbeat sequence
$s: Number,
- $t: String
+ $t: String,
};
export async function onMessage(this: WebSocket, buffer: Buffer) {
try {
var data: Payload = JSON.parse(buffer.toString());
- if (data.op !== VoiceOPCodes.IDENTIFY && !this.user_id) return this.close(CLOSECODES.Not_authenticated);
+ if (data.op !== VoiceOPCodes.IDENTIFY && !this.user_id)
+ return this.close(CLOSECODES.Not_authenticated);
// @ts-ignore
const OPCodeHandler = OPCodeHandlers[data.op];
@@ -25,7 +26,11 @@ export async function onMessage(this: WebSocket, buffer: Buffer) {
return;
}
- if (![VoiceOPCodes.HEARTBEAT, VoiceOPCodes.SPEAKING].includes(data.op as VoiceOPCodes)) {
+ if (
+ ![VoiceOPCodes.HEARTBEAT, VoiceOPCodes.SPEAKING].includes(
+ data.op as VoiceOPCodes,
+ )
+ ) {
// @ts-ignore
console.log("[WebRTC] Opcode " + VoiceOPCodes[data.op]);
}
@@ -35,4 +40,4 @@ export async function onMessage(this: WebSocket, buffer: Buffer) {
console.error("[WebRTC] error", error);
// if (!this.CLOSED && this.CLOSING) return this.close(CloseCodes.Unknown_error);
}
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/index.ts b/src/webrtc/index.ts
index 7cecc9b6..ccb088ac 100644
--- a/src/webrtc/index.ts
+++ b/src/webrtc/index.ts
@@ -1,2 +1,2 @@
export * from "./Server";
-export * from "./util/index";
\ No newline at end of file
+export * from "./util/index";
diff --git a/src/webrtc/opcodes/BackendVersion.ts b/src/webrtc/opcodes/BackendVersion.ts
index b4b61c7d..375dd0cc 100644
--- a/src/webrtc/opcodes/BackendVersion.ts
+++ b/src/webrtc/opcodes/BackendVersion.ts
@@ -2,5 +2,8 @@ import { Payload, Send, WebSocket } from "@fosscord/gateway";
import { VoiceOPCodes } from "../util";
export async function onBackendVersion(this: WebSocket, data: Payload) {
- await Send(this, { op: VoiceOPCodes.VOICE_BACKEND_VERSION, d: { voice: "0.8.43", rtc_worker: "0.3.26" } });
-}
\ No newline at end of file
+ await Send(this, {
+ op: VoiceOPCodes.VOICE_BACKEND_VERSION,
+ d: { voice: "0.8.43", rtc_worker: "0.3.26" },
+ });
+}
diff --git a/src/webrtc/opcodes/Heartbeat.ts b/src/webrtc/opcodes/Heartbeat.ts
index 1b6c5bcd..932cd458 100644
--- a/src/webrtc/opcodes/Heartbeat.ts
+++ b/src/webrtc/opcodes/Heartbeat.ts
@@ -1,4 +1,10 @@
-import { CLOSECODES, Payload, Send, setHeartbeat, WebSocket } from "@fosscord/gateway";
+import {
+ CLOSECODES,
+ Payload,
+ Send,
+ setHeartbeat,
+ WebSocket,
+} from "@fosscord/gateway";
import { VoiceOPCodes } from "../util";
export async function onHeartbeat(this: WebSocket, data: Payload) {
@@ -6,4 +12,4 @@ export async function onHeartbeat(this: WebSocket, data: Payload) {
if (isNaN(data.d)) return this.close(CLOSECODES.Decode_error);
await Send(this, { op: VoiceOPCodes.HEARTBEAT_ACK, d: data.d });
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/opcodes/Identify.ts b/src/webrtc/opcodes/Identify.ts
index 19a575ab..45ad6c0a 100644
--- a/src/webrtc/opcodes/Identify.ts
+++ b/src/webrtc/opcodes/Identify.ts
@@ -1,33 +1,46 @@
import { CLOSECODES, Payload, Send, WebSocket } from "@fosscord/gateway";
-import { validateSchema, VoiceIdentifySchema, VoiceState } from "@fosscord/util";
+import {
+ validateSchema,
+ VoiceIdentifySchema,
+ VoiceState,
+} from "@fosscord/util";
import { endpoint, getClients, VoiceOPCodes, PublicIP } from "@fosscord/webrtc";
import SemanticSDP from "semantic-sdp";
const defaultSDP = require("./sdp.json");
export async function onIdentify(this: WebSocket, data: Payload) {
clearTimeout(this.readyTimeout);
- const { server_id, user_id, session_id, token, streams, video } = validateSchema("VoiceIdentifySchema", data.d) as VoiceIdentifySchema;
+ const { server_id, user_id, session_id, token, streams, video } =
+ validateSchema("VoiceIdentifySchema", data.d) as VoiceIdentifySchema;
- const voiceState = await VoiceState.findOne({ where: { guild_id: server_id, user_id, token, session_id } });
+ const voiceState = await VoiceState.findOne({
+ where: { guild_id: server_id, user_id, token, session_id },
+ });
if (!voiceState) return this.close(CLOSECODES.Authentication_failed);
this.user_id = user_id;
this.session_id = session_id;
const sdp = SemanticSDP.SDPInfo.expand(defaultSDP);
- sdp.setDTLS(SemanticSDP.DTLSInfo.expand({ setup: "actpass", hash: "sha-256", fingerprint: endpoint.getDTLSFingerprint() }));
+ sdp.setDTLS(
+ SemanticSDP.DTLSInfo.expand({
+ setup: "actpass",
+ hash: "sha-256",
+ fingerprint: endpoint.getDTLSFingerprint(),
+ }),
+ );
this.client = {
websocket: this,
out: {
- tracks: new Map()
+ tracks: new Map(),
},
in: {
audio_ssrc: 0,
video_ssrc: 0,
- rtx_ssrc: 0
+ rtx_ssrc: 0,
},
sdp,
- channel_id: voiceState.channel_id
+ channel_id: voiceState.channel_id,
};
const clients = getClients(voiceState.channel_id)!;
@@ -51,10 +64,10 @@ export async function onIdentify(this: WebSocket, data: Payload) {
"xsalsa20_poly1305_lite_rtpsize",
"xsalsa20_poly1305_lite",
"xsalsa20_poly1305_suffix",
- "xsalsa20_poly1305"
+ "xsalsa20_poly1305",
],
ip: PublicIP,
- experiments: []
- }
+ experiments: [],
+ },
});
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/opcodes/SelectProtocol.ts b/src/webrtc/opcodes/SelectProtocol.ts
index a3579b34..eadba283 100644
--- a/src/webrtc/opcodes/SelectProtocol.ts
+++ b/src/webrtc/opcodes/SelectProtocol.ts
@@ -6,7 +6,10 @@ import SemanticSDP, { MediaInfo, SDPInfo } from "semantic-sdp";
export async function onSelectProtocol(this: WebSocket, payload: Payload) {
if (!this.client) return;
- const data = validateSchema("SelectProtocolSchema", payload.d) as SelectProtocolSchema;
+ const data = validateSchema(
+ "SelectProtocolSchema",
+ payload.d,
+ ) as SelectProtocolSchema;
const offer = SemanticSDP.SDPInfo.parse("m=audio\n" + data.sdp!);
this.client.sdp!.setICE(offer.getICE());
@@ -25,14 +28,14 @@ export async function onSelectProtocol(this: WebSocket, payload: Payload) {
const candidate = candidates[0];
const answer =
- `m=audio ${port} ICE/SDP`
- + `a=fingerprint:${fingerprint}`
- + `c=IN IP4 ${PublicIP}`
- + `a=rtcp:${port}`
- + `a=ice-ufrag:${ice.getUfrag()}`
- + `a=ice-pwd:${ice.getPwd()}`
- + `a=fingerprint:${fingerprint}`
- + `a=candidate:1 1 ${candidate.getTransport()} ${candidate.getFoundation()} ${candidate.getAddress()} ${candidate.getPort()} typ host`;
+ `m=audio ${port} ICE/SDP` +
+ `a=fingerprint:${fingerprint}` +
+ `c=IN IP4 ${PublicIP}` +
+ `a=rtcp:${port}` +
+ `a=ice-ufrag:${ice.getUfrag()}` +
+ `a=ice-pwd:${ice.getPwd()}` +
+ `a=fingerprint:${fingerprint}` +
+ `a=candidate:1 1 ${candidate.getTransport()} ${candidate.getFoundation()} ${candidate.getAddress()} ${candidate.getPort()} typ host`;
await Send(this, {
op: VoiceOPCodes.SELECT_PROTOCOL_ACK,
@@ -40,7 +43,7 @@ export async function onSelectProtocol(this: WebSocket, payload: Payload) {
video_codec: "H264",
sdp: answer,
media_session_id: this.session_id,
- audio_codec: "opus"
- }
+ audio_codec: "opus",
+ },
});
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/opcodes/Speaking.ts b/src/webrtc/opcodes/Speaking.ts
index e2227040..8488acf8 100644
--- a/src/webrtc/opcodes/Speaking.ts
+++ b/src/webrtc/opcodes/Speaking.ts
@@ -15,8 +15,8 @@ export async function onSpeaking(this: WebSocket, data: Payload) {
d: {
user_id: client.websocket.user_id,
speaking: data.d.speaking,
- ssrc: ssrc?.audio_ssrc || 0
- }
+ ssrc: ssrc?.audio_ssrc || 0,
+ },
});
});
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/opcodes/Video.ts b/src/webrtc/opcodes/Video.ts
index ff20d5a9..dcbc9aa0 100644
--- a/src/webrtc/opcodes/Video.ts
+++ b/src/webrtc/opcodes/Video.ts
@@ -21,8 +21,8 @@ export async function onVideo(this: WebSocket, payload: Payload) {
SemanticSDP.StreamInfo.expand({
id,
// @ts-ignore
- tracks: []
- })
+ tracks: [],
+ }),
);
this.client.in.stream = stream;
@@ -46,8 +46,8 @@ export async function onVideo(this: WebSocket, payload: Payload) {
SemanticSDP.StreamInfo.expand({
id: "out" + this.user_id,
// @ts-ignore
- tracks: []
- })
+ tracks: [],
+ }),
);
this.client.out.stream = out;
@@ -64,20 +64,35 @@ export async function onVideo(this: WebSocket, payload: Payload) {
}
if (d.audio_ssrc) {
- handleSSRC.call(this, "audio", { media: d.audio_ssrc, rtx: d.audio_ssrc + 1 });
+ handleSSRC.call(this, "audio", {
+ media: d.audio_ssrc,
+ rtx: d.audio_ssrc + 1,
+ });
}
if (d.video_ssrc && d.rtx_ssrc) {
- handleSSRC.call(this, "video", { media: d.video_ssrc, rtx: d.rtx_ssrc });
+ handleSSRC.call(this, "video", {
+ media: d.video_ssrc,
+ rtx: d.rtx_ssrc,
+ });
}
}
-function attachTrack(this: WebSocket, track: IncomingStreamTrack, user_id: string) {
+function attachTrack(
+ this: WebSocket,
+ track: IncomingStreamTrack,
+ user_id: string,
+) {
if (!this.client) return;
- const outTrack = this.client.transport!.createOutgoingStreamTrack(track.getMedia());
+ const outTrack = this.client.transport!.createOutgoingStreamTrack(
+ track.getMedia(),
+ );
outTrack.attachTo(track);
this.client.out.stream!.addTrack(outTrack);
var ssrcs = this.client.out.tracks.get(user_id)!;
- if (!ssrcs) ssrcs = this.client.out.tracks.set(user_id, { audio_ssrc: 0, rtx_ssrc: 0, video_ssrc: 0 }).get(user_id)!;
+ if (!ssrcs)
+ ssrcs = this.client.out.tracks
+ .set(user_id, { audio_ssrc: 0, rtx_ssrc: 0, video_ssrc: 0 })
+ .get(user_id)!;
if (track.getMedia() === "audio") {
ssrcs.audio_ssrc = outTrack.getSSRCs().media!;
@@ -90,8 +105,8 @@ function attachTrack(this: WebSocket, track: IncomingStreamTrack, user_id: strin
op: VoiceOPCodes.VIDEO,
d: {
user_id: user_id,
- ...ssrcs
- } as VoiceVideoSchema
+ ...ssrcs,
+ } as VoiceVideoSchema,
});
}
@@ -115,4 +130,4 @@ function handleSSRC(this: WebSocket, type: "audio" | "video", ssrcs: SSRCs) {
attachTrack.call(this, track, client.websocket.user_id);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/opcodes/index.ts b/src/webrtc/opcodes/index.ts
index 8c664cce..86e39687 100644
--- a/src/webrtc/opcodes/index.ts
+++ b/src/webrtc/opcodes/index.ts
@@ -15,5 +15,5 @@ export default {
[VoiceOPCodes.VOICE_BACKEND_VERSION]: onBackendVersion,
[VoiceOPCodes.VIDEO]: onVideo,
[VoiceOPCodes.SPEAKING]: onSpeaking,
- [VoiceOPCodes.SELECT_PROTOCOL]: onSelectProtocol
-};
\ No newline at end of file
+ [VoiceOPCodes.SELECT_PROTOCOL]: onSelectProtocol,
+};
diff --git a/src/webrtc/opcodes/sdp.json b/src/webrtc/opcodes/sdp.json
index 4867b9c7..5f7eba38 100644
--- a/src/webrtc/opcodes/sdp.json
+++ b/src/webrtc/opcodes/sdp.json
@@ -417,4 +417,4 @@
}
],
"candidates": []
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/start.ts b/src/webrtc/start.ts
index 9a5f38ee..57361909 100644
--- a/src/webrtc/start.ts
+++ b/src/webrtc/start.ts
@@ -8,6 +8,6 @@ config();
const port = Number(process.env.PORT) || 3004;
const server = new Server({
- port
+ port,
});
-server.start();
\ No newline at end of file
+server.start();
diff --git a/src/webrtc/util/Constants.ts b/src/webrtc/util/Constants.ts
index 64d78e22..d9f1ff60 100644
--- a/src/webrtc/util/Constants.ts
+++ b/src/webrtc/util/Constants.ts
@@ -3,7 +3,7 @@ export enum VoiceStatus {
CONNECTING = 1,
AUTHENTICATING = 2,
RECONNECTING = 3,
- DISCONNECTED = 4
+ DISCONNECTED = 4,
}
export enum VoiceOPCodes {
@@ -22,5 +22,5 @@ export enum VoiceOPCodes {
SESSION_UPDATE = 14,
MEDIA_SINK_WANTS = 15,
VOICE_BACKEND_VERSION = 16,
- CHANNEL_OPTIONS_UPDATE = 17
-}
\ No newline at end of file
+ CHANNEL_OPTIONS_UPDATE = 17,
+}
diff --git a/src/webrtc/util/MediaServer.ts b/src/webrtc/util/MediaServer.ts
index 93230c91..520b8682 100644
--- a/src/webrtc/util/MediaServer.ts
+++ b/src/webrtc/util/MediaServer.ts
@@ -1,5 +1,9 @@
import { WebSocket } from "@fosscord/gateway";
-import MediaServer, { IncomingStream, OutgoingStream, Transport } from "medooze-media-server";
+import MediaServer, {
+ IncomingStream,
+ OutgoingStream,
+ Transport,
+} from "medooze-media-server";
import SemanticSDP from "semantic-sdp";
MediaServer.enableLog(true);
@@ -13,7 +17,11 @@ try {
MediaServer.setPortRange(min, max);
} catch (error) {
- console.error("Invalid env var: WEBRTC_PORT_RANGE", process.env.WEBRTC_PORT_RANGE, error);
+ console.error(
+ "Invalid env var: WEBRTC_PORT_RANGE",
+ process.env.WEBRTC_PORT_RANGE,
+ error,
+ );
process.exit(1);
}
@@ -48,4 +56,4 @@ export interface Client {
export function getClients(channel_id: string) {
if (!channels.has(channel_id)) channels.set(channel_id, new Set());
return channels.get(channel_id)!;
-}
\ No newline at end of file
+}
diff --git a/src/webrtc/util/index.ts b/src/webrtc/util/index.ts
index 2e09bc48..f0d49049 100644
--- a/src/webrtc/util/index.ts
+++ b/src/webrtc/util/index.ts
@@ -1,2 +1,2 @@
export * from "./Constants";
-export * from "./MediaServer";
\ No newline at end of file
+export * from "./MediaServer";
diff --git a/tsconfig.json b/tsconfig.json
index 9797ae10..48187ced 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,113 +1,114 @@
{
- "exclude": [
- "./src/webrtc",
- "./src-slowcord"
- ],
- "include": ["./src"],
- "compilerOptions": {
- /* Visit https://aka.ms/tsconfig to read more about this file */
+ "exclude": ["./src/webrtc", "./src-slowcord"],
+ "include": ["./src"],
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig to read more about this file */
- /* Projects */
- "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
- // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
- // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
- // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
- // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+ /* Projects */
+ "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
- /* Language and Environment */
- "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
- "lib": ["ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
- // "jsx": "preserve", /* Specify what JSX code is generated. */
- "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
- "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
- // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
- // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
- // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
- // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
- // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
- // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
- // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+ /* Language and Environment */
+ "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ "lib": [
+ "ESNext"
+ ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
+ "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
- /* Modules */
- "module": "commonjs", /* Specify what module code is generated. */
- // "rootDir": "./src", /* Specify the root folder within your source files. */
- "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
- "baseUrl": "./src/",
- "paths": {
- "@fosscord/api*": ["./api"],
- "@fosscord/gateway*": ["./gateway"],
- "@fosscord/cdn*": ["./cdn"],
- "@fosscord/util*": ["./util"]
- }, /* Specify a set of entries that re-map imports to additional lookup locations. */
- // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
- // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
- "types": ["node"], /* Specify type package names to be included without being referenced in a source file. */
- // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
- // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
- "resolveJsonModule": true, /* Enable importing .json files. */
- // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
+ /* Modules */
+ "module": "commonjs" /* Specify what module code is generated. */,
+ // "rootDir": "./src", /* Specify the root folder within your source files. */
+ "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
+ "baseUrl": "./src/",
+ "paths": {
+ "@fosscord/api*": ["./api"],
+ "@fosscord/gateway*": ["./gateway"],
+ "@fosscord/cdn*": ["./cdn"],
+ "@fosscord/util*": ["./util"]
+ } /* Specify a set of entries that re-map imports to additional lookup locations. */,
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
+ "types": [
+ "node"
+ ] /* Specify type package names to be included without being referenced in a source file. */,
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ "resolveJsonModule": true /* Enable importing .json files. */,
+ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
- /* JavaScript Support */
- "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
- "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
- // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+ /* JavaScript Support */
+ "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */,
+ "checkJs": true /* Enable error reporting in type-checked JavaScript files. */,
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
- /* Emit */
- "declaration": false, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
- "declarationMap": false, /* Create sourcemaps for d.ts files. */
- // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- "sourceMap": true, /* Create source map files for emitted JavaScript files. */
- // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
- "outDir": "./dist/", /* Specify an output folder for all emitted files. */
- // "removeComments": true, /* Disable emitting comments. */
- // "noEmit": true, /* Disable emitting files from a compilation. */
- "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
- // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
- // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
- // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
- // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
- // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
- // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
- // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
- // "newLine": "crlf", /* Set the newline character for emitting files. */
- // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
- "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
- // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
- // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
- // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
- // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+ /* Emit */
+ "declaration": false /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
+ "declarationMap": false /* Create sourcemaps for d.ts files. */,
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ "sourceMap": true /* Create source map files for emitted JavaScript files. */,
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./dist/" /* Specify an output folder for all emitted files. */,
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ "importHelpers": true /* Allow importing helper functions from tslib once per project, instead of including them per-file. */,
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ "noEmitHelpers": true /* Disable generating custom helper functions like '__extends' in compiled output. */,
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
- /* Interop Constraints */
- // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
- // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
- // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
- /* Type Checking */
- "strict": true, /* Enable all strict type-checking options. */
- "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
- "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
- // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
- // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
- "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */
- // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
- // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
- "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
- // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
- // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
- // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
- // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
- // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
- // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
- // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
- // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
- // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
- // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+ /* Type Checking */
+ "strict": true /* Enable all strict type-checking options. */,
+ "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
+ "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */,
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */,
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ "alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
- /* Completeness */
- // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
- }
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
}
|