mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-22 02:12:40 +01:00
Prettier
This commit is contained in:
parent
ea903baa15
commit
a3f2f997a3
@ -27,4 +27,4 @@ This repository contains:
|
||||
|
||||
- [Contributing](https://docs.fosscord.com/contributing/server/)
|
||||
|
||||
## [Setup](https://docs.fosscord.com/server/setup/)
|
||||
## [Setup](https://docs.fosscord.com/server/setup/)
|
||||
|
@ -26,13 +26,13 @@ const CHANGELOG_SCRIPT = "4ec0b5948572d31df88b.js";
|
||||
.toString()
|
||||
.replaceAll("\r", "")
|
||||
.replaceAll("\n", "\\n")
|
||||
.replaceAll("\'", "\\'");
|
||||
.replaceAll("'", "\\'");
|
||||
|
||||
const index = text.indexOf("t.exports='---changelog---") + 11;
|
||||
const endIndex = text.indexOf("'\n", index); // hmm
|
||||
const endIndex = text.indexOf("'\n", index); // hmm
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(CACHE_PATH, CHANGELOG_SCRIPT),
|
||||
text.substring(0, index) + newChangelogText + text.substring(endIndex)
|
||||
text.substring(0, index) + newChangelogText + text.substring(endIndex),
|
||||
);
|
||||
})();
|
||||
})();
|
||||
|
@ -18,15 +18,16 @@
|
||||
|
||||
const path = require("path");
|
||||
const fetch = require("node-fetch");
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const http = require("http");
|
||||
const https = require("https");
|
||||
const fs = require("fs/promises");
|
||||
const { existsSync } = require("fs");
|
||||
|
||||
// https://stackoverflow.com/a/62500224
|
||||
const httpAgent = new http.Agent({ keepAlive: true });
|
||||
const httpsAgent = new https.Agent({ keepAlive: true });
|
||||
const agent = (_parsedURL) => _parsedURL.protocol == 'http:' ? httpAgent : httpsAgent;
|
||||
const agent = (_parsedURL) =>
|
||||
_parsedURL.protocol == "http:" ? httpAgent : httpsAgent;
|
||||
|
||||
const CACHE_PATH = path.join(__dirname, "..", "assets", "cache");
|
||||
const BASE_URL = "https://discord.com";
|
||||
@ -55,7 +56,10 @@ const doPatch = (content) => {
|
||||
content = content.replaceAll(/ Discord /g, ` ${INSTANCE_NAME} `);
|
||||
content = content.replaceAll(/Discord /g, `${INSTANCE_NAME} `);
|
||||
content = content.replaceAll(/ Discord/g, ` ${INSTANCE_NAME}`);
|
||||
content = content.replaceAll(/Discord Premium/g, `${INSTANCE_NAME} Premium`);
|
||||
content = content.replaceAll(
|
||||
/Discord Premium/g,
|
||||
`${INSTANCE_NAME} Premium`,
|
||||
);
|
||||
content = content.replaceAll(/Discord Nitro/g, `${INSTANCE_NAME} Premium`);
|
||||
content = content.replaceAll(/Discord's/g, `${INSTANCE_NAME}'s`);
|
||||
//content = content.replaceAll(/DiscordTag/g, "FosscordTag");
|
||||
@ -66,41 +70,49 @@ const doPatch = (content) => {
|
||||
['"Server"', '"Guild"'],
|
||||
['"Server ', '"Guild '],
|
||||
[' Server"', ' Guild"'],
|
||||
[' Server ', ' Guild '],
|
||||
[" Server ", " Guild "],
|
||||
|
||||
['"Server."', '"Guild."'],
|
||||
[' Server."', ' Guild."'],
|
||||
|
||||
['"Server."', '"Guild,"'],
|
||||
[' Server,"', ' Guild,"'],
|
||||
[' Server,', ' Guild,'],
|
||||
[" Server,", " Guild,"],
|
||||
|
||||
['"Servers"', '"Guilds"'],
|
||||
['"Servers ', '"Guilds '],
|
||||
[' Servers"', ' Guilds"'],
|
||||
[' Servers ', ' Guilds '],
|
||||
[" Servers ", " Guilds "],
|
||||
|
||||
['"Servers."', '"Guilds."'],
|
||||
[' Servers."', ' Guilds,"'],
|
||||
|
||||
['"Servers,"', '"Guilds,"'],
|
||||
[' Servers,"', ' Guilds,"'],
|
||||
[' Servers,', ' Guilds,'],
|
||||
[" Servers,", " Guilds,"],
|
||||
|
||||
['\nServers', '\nGuilds'],
|
||||
["\nServers", "\nGuilds"],
|
||||
];
|
||||
serverVariations.forEach(x => serverVariations.push([x[0].toLowerCase(), x[1].toLowerCase()]));
|
||||
serverVariations.forEach(x => content = content.replaceAll(x[0], x[1]));
|
||||
serverVariations.forEach((x) =>
|
||||
serverVariations.push([x[0].toLowerCase(), x[1].toLowerCase()]),
|
||||
);
|
||||
serverVariations.forEach((x) => (content = content.replaceAll(x[0], x[1])));
|
||||
|
||||
// sentry
|
||||
content = content.replaceAll("https://fa97a90475514c03a42f80cd36d147c4@sentry.io/140984", "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6");
|
||||
content = content.replaceAll(
|
||||
"https://fa97a90475514c03a42f80cd36d147c4@sentry.io/140984",
|
||||
"https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6",
|
||||
);
|
||||
|
||||
//logos
|
||||
content = content.replaceAll(
|
||||
"M23.0212 1.67671C21.3107 0.879656 19.5079 0.318797 17.6584 0C17.4062 0.461742 17.1749 0.934541 16.9708 1.4184C15.003 1.12145 12.9974 1.12145 11.0283 1.4184C10.819 0.934541 10.589 0.461744 10.3368 0.00546311C8.48074 0.324393 6.67795 0.885118 4.96746 1.68231C1.56727 6.77853 0.649666 11.7538 1.11108 16.652C3.10102 18.1418 5.3262 19.2743 7.69177 20C8.22338 19.2743 8.69519 18.4993 9.09812 17.691C8.32996 17.3997 7.58522 17.0424 6.87684 16.6135C7.06531 16.4762 7.24726 16.3387 7.42403 16.1847C11.5911 18.1749 16.408 18.1749 20.5763 16.1847C20.7531 16.3332 20.9351 16.4762 21.1171 16.6135C20.41 17.0369 19.6639 17.3997 18.897 17.691C19.3052 18.4993 19.7718 19.2689 20.3021 19.9945C22.6677 19.2689 24.8929 18.1364 26.8828 16.6466H26.8893C27.43 10.9731 25.9665 6.04728 23.0212 1.67671ZM9.68041 13.6383C8.39754 13.6383 7.34085 12.4453 7.34085 10.994C7.34085 9.54272 8.37155 8.34973 9.68041 8.34973C10.9893 8.34973 12.0395 9.54272 12.0187 10.994C12.0187 12.4453 10.9828 13.6383 9.68041 13.6383ZM18.3161 13.6383C17.0332 13.6383 15.9765 12.4453 15.9765 10.994C15.9765 9.54272 17.0124 8.34973 18.3161 8.34973C19.6184 8.34973 20.6751 9.54272 20.6543 10.994C20.6543 12.4453 19.6184 13.6383 18.3161 13.6383Z",
|
||||
"M 0,0 47.999993,2.7036528e-4 C 48.001796,3.3028172 47.663993,6.5968018 46.991821,9.8301938 43.116101,28.454191 28.452575,43.116441 9.8293509,46.992163 6.5960834,47.664163 3.3023222,48.001868 0,47.999992 Z m 9.8293509,28.735114 v 9.248482 C 22.673599,33.047696 32.857154,22.749268 37.63852,9.829938 H 9.8293509 v 8.679899 H 22.931288 c -3.554489,3.93617 -7.735383,7.257633 -12.373436,9.829938 -0.241031,0.133684 -0.483864,0.265492 -0.7285011,0.395339 z"
|
||||
"M 0,0 47.999993,2.7036528e-4 C 48.001796,3.3028172 47.663993,6.5968018 46.991821,9.8301938 43.116101,28.454191 28.452575,43.116441 9.8293509,46.992163 6.5960834,47.664163 3.3023222,48.001868 0,47.999992 Z m 9.8293509,28.735114 v 9.248482 C 22.673599,33.047696 32.857154,22.749268 37.63852,9.829938 H 9.8293509 v 8.679899 H 22.931288 c -3.554489,3.93617 -7.735383,7.257633 -12.373436,9.829938 -0.241031,0.133684 -0.483864,0.265492 -0.7285011,0.395339 z",
|
||||
);
|
||||
content = content.replaceAll(
|
||||
'width:n,height:c,viewBox:"0 0 28 20"',
|
||||
'width:50,height:50,viewBox:"0 0 50 50"',
|
||||
);
|
||||
content = content.replaceAll('width:n,height:c,viewBox:"0 0 28 20"', 'width:50,height:50,viewBox:"0 0 50 50"');
|
||||
|
||||
// app download links
|
||||
// content = content.replaceAll(
|
||||
@ -126,28 +138,34 @@ const doPatch = (content) => {
|
||||
// Stop client from deleting `localStorage` global. Makes `plugins` and `preload-plugins` less annoying.
|
||||
content = content.replaceAll(
|
||||
"delete window.localStorage",
|
||||
"console.log('Prevented deletion of localStorage')"
|
||||
"console.log('Prevented deletion of localStorage')",
|
||||
);
|
||||
|
||||
// fast identify
|
||||
content = content.replaceAll(
|
||||
"e.isFastConnect=t;t?e._doFastConnectIdentify():e._doResumeOrIdentify()",
|
||||
"e.isFastConnect=t; if (t !== undefined) e._doResumeOrIdentify();"
|
||||
"e.isFastConnect=t; if (t !== undefined) e._doResumeOrIdentify();",
|
||||
);
|
||||
|
||||
// disable qr code login
|
||||
content = content.replaceAll(/\w\?\(\d,\w\.jsx\)\(\w*\,{authTokenCallback:this\.handleAuthToken}\):null/g, "null");
|
||||
content = content.replaceAll(
|
||||
/\w\?\(\d,\w\.jsx\)\(\w*\,{authTokenCallback:this\.handleAuthToken}\):null/g,
|
||||
"null",
|
||||
);
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
const processFile = async (name) => {
|
||||
const res = await fetch(`${BASE_URL}/assets/${name}${name.includes(".") ? "" : ".js"}`, {
|
||||
agent,
|
||||
});
|
||||
const res = await fetch(
|
||||
`${BASE_URL}/assets/${name}${name.includes(".") ? "" : ".js"}`,
|
||||
{
|
||||
agent,
|
||||
},
|
||||
);
|
||||
if (res.status !== 200) {
|
||||
return [];
|
||||
};
|
||||
}
|
||||
|
||||
if (name.includes(".") && !name.includes(".js") && !name.includes(".css")) {
|
||||
await fs.writeFile(path.join(CACHE_PATH, name), await res.buffer());
|
||||
@ -158,9 +176,14 @@ const processFile = async (name) => {
|
||||
|
||||
text = doPatch(text);
|
||||
|
||||
await fs.writeFile(path.join(CACHE_PATH, `${name}${name.includes(".") ? "" : ".js"}`), text);
|
||||
await fs.writeFile(
|
||||
path.join(CACHE_PATH, `${name}${name.includes(".") ? "" : ".js"}`),
|
||||
text,
|
||||
);
|
||||
|
||||
return [...new Set(text.match(/\"[A-Fa-f0-9]{20}\"/g))].map(x => x.replaceAll("\"", ""));
|
||||
return [...new Set(text.match(/\"[A-Fa-f0-9]{20}\"/g))].map((x) =>
|
||||
x.replaceAll('"', ""),
|
||||
);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
@ -187,7 +210,13 @@ const processFile = async (name) => {
|
||||
|
||||
process.stdout.moveCursor(0, 1);
|
||||
|
||||
const CACHE_MISSES = (await fs.readFile(path.join(CACHE_PATH, "..", "cacheMisses"))).toString().split("\r").join("").split("\n");
|
||||
const CACHE_MISSES = (
|
||||
await fs.readFile(path.join(CACHE_PATH, "..", "cacheMisses"))
|
||||
)
|
||||
.toString()
|
||||
.split("\r")
|
||||
.join("")
|
||||
.split("\n");
|
||||
while (CACHE_MISSES.length > 0) {
|
||||
const asset = CACHE_MISSES.shift();
|
||||
process.stdout.clearLine(0);
|
||||
@ -216,11 +245,15 @@ const processFile = async (name) => {
|
||||
`Patching existing ${file}. Remaining: ${existing.length}.`,
|
||||
);
|
||||
|
||||
var text = (await fs.readFile(path.join(CACHE_PATH, file)));
|
||||
var text = await fs.readFile(path.join(CACHE_PATH, file));
|
||||
if (file.includes(".js") || file.includes(".css")) {
|
||||
text = doPatch(text.toString());
|
||||
await fs.writeFile(path.join(CACHE_PATH, file), text.toString());
|
||||
assets.push(...[...new Set(text.match(/\"[A-Fa-f0-9]{20}\"/g))].map(x => x.replaceAll("\"", "")));
|
||||
assets.push(
|
||||
...[...new Set(text.match(/\"[A-Fa-f0-9]{20}\"/g))].map((x) =>
|
||||
x.replaceAll('"', ""),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,9 +282,9 @@ const processFile = async (name) => {
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(
|
||||
`Caching asset ${asset}. ` +
|
||||
`${i}/${assets.length - 1} = ${Math.floor(
|
||||
(i / (assets.length - 1)) * 100,
|
||||
)}% `
|
||||
`${i}/${assets.length - 1} = ${Math.floor(
|
||||
(i / (assets.length - 1)) * 100,
|
||||
)}% `,
|
||||
// + `Finish at: ${new Date(
|
||||
// Date.now() + finishTime,
|
||||
// ).toLocaleTimeString()}`,
|
||||
|
@ -51,7 +51,7 @@ function modify(obj) {
|
||||
function main() {
|
||||
const program = TJS.programFromConfig(
|
||||
path.join(__dirname, "..", "tsconfig.json"),
|
||||
walk(path.join(__dirname, "..", "src", "util", "schemas"))
|
||||
walk(path.join(__dirname, "..", "src", "util", "schemas")),
|
||||
);
|
||||
const generator = TJS.buildGenerator(program, settings);
|
||||
if (!generator || !program) return;
|
||||
|
@ -4,11 +4,19 @@ const path = require("path");
|
||||
(async () => {
|
||||
DataSourceOptions.setOptions({
|
||||
logging: true,
|
||||
migrations: [path.join(process.cwd(), "scripts", "stagingMigration", DatabaseType, "*.js")]
|
||||
migrations: [
|
||||
path.join(
|
||||
process.cwd(),
|
||||
"scripts",
|
||||
"stagingMigration",
|
||||
DatabaseType,
|
||||
"*.js",
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const dbConnection = await DataSourceOptions.initialize();
|
||||
await dbConnection.runMigrations();
|
||||
await dbConnection.destroy();
|
||||
console.log("migration done");
|
||||
})();
|
||||
})();
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,58 +1,149 @@
|
||||
const { MigrationInterface, QueryRunner } = require("typeorm");
|
||||
|
||||
module.exports = class staging1672815835837 {
|
||||
name = 'staging1672815835837'
|
||||
name = "staging1672815835837";
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "FK_76ba283779c8441fd5ff819c8cf"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" RENAME COLUMN "id" TO "index"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" RENAME CONSTRAINT "PK_00f004f5922a0744d174530d639" TO "PK_e81f8bb92802737337d35c00981"`);
|
||||
await queryRunner.query(`CREATE TABLE "embed_cache" ("id" character varying NOT NULL, "url" character varying NOT NULL, "embed" text NOT NULL, CONSTRAINT "PK_0abb7581d4efc5a8b1361389c5e" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "security_settings" ("id" character varying NOT NULL, "guild_id" character varying, "channel_id" character varying, "encryption_permission_mask" integer NOT NULL, "allowed_algorithms" text NOT NULL, "current_algorithm" character varying NOT NULL, "used_since_message" character varying, CONSTRAINT "PK_4aec436cf81177ae97a1bcec3c7" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "deb_url"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "osx_url"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "win_url"`);
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "REL_76ba283779c8441fd5ff819c8c"`);
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN IF EXISTS "settingsId"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" ADD "platform" character varying NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" ADD "enabled" boolean NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "purchased_flags" integer NOT NULL DEFAULT 0`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "premium_usage_flags" integer NOT NULl DEFAULT 0`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "settingsIndex" integer`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD CONSTRAINT "UQ_0c14beb78d8c5ccba66072adbc7" UNIQUE ("settingsIndex")`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "pub_date"`);
|
||||
await queryRunner.query(`ALTER TABLE "client_release" ADD "pub_date" TIMESTAMP NOT NULL`);
|
||||
await queryRunner.query(`UPDATE channels SET nsfw = false WHERE nsfw IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "nsfw" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE channels SET flags = 0 WHERE flags IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "flags" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE channels SET default_thread_rate_limit_per_user = 0 WHERE default_thread_rate_limit_per_user IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" DROP CONSTRAINT IF EXISTS "PK_e81f8bb92802737337d35c00981"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" DROP COLUMN IF EXISTS "index"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" ADD "index" SERIAL NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "user_settings" ADD CONSTRAINT "PK_e81f8bb92802737337d35c00981" PRIMARY KEY ("index")`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" DROP COLUMN IF EXISTS "primary_category_id"`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ADD "primary_category_id" character varying`);
|
||||
await queryRunner.query(`UPDATE guilds SET large = false WHERE large IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "large" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE guilds SET premium_tier = 0 WHERE premium_tier IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "premium_tier" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE guilds SET unavailable = false WHERE unavailable IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "unavailable" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE guilds SET widget_enabled = false WHERE widget_enabled IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "widget_enabled" SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE guilds SET nsfw = false WHERE nsfw IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "nsfw" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "members" DROP COLUMN IF EXISTS "premium_since"`);
|
||||
await queryRunner.query(`ALTER TABLE "members" ADD "premium_since" bigint`);
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "FK_76ba283779c8441fd5ff819c8cf"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" RENAME COLUMN "id" TO "index"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" RENAME CONSTRAINT "PK_00f004f5922a0744d174530d639" TO "PK_e81f8bb92802737337d35c00981"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "embed_cache" ("id" character varying NOT NULL, "url" character varying NOT NULL, "embed" text NOT NULL, CONSTRAINT "PK_0abb7581d4efc5a8b1361389c5e" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "security_settings" ("id" character varying NOT NULL, "guild_id" character varying, "channel_id" character varying, "encryption_permission_mask" integer NOT NULL, "allowed_algorithms" text NOT NULL, "current_algorithm" character varying NOT NULL, "used_since_message" character varying, CONSTRAINT "PK_4aec436cf81177ae97a1bcec3c7" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "deb_url"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "osx_url"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "win_url"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "REL_76ba283779c8441fd5ff819c8c"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" DROP COLUMN IF EXISTS "settingsId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" ADD "platform" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" ADD "enabled" boolean NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD "purchased_flags" integer NOT NULL DEFAULT 0`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD "premium_usage_flags" integer NOT NULl DEFAULT 0`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD "settingsIndex" integer`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD CONSTRAINT "UQ_0c14beb78d8c5ccba66072adbc7" UNIQUE ("settingsIndex")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "pub_date"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "client_release" ADD "pub_date" TIMESTAMP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE channels SET nsfw = false WHERE nsfw IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "channels" ALTER COLUMN "nsfw" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE channels SET flags = 0 WHERE flags IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "channels" ALTER COLUMN "flags" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE channels SET default_thread_rate_limit_per_user = 0 WHERE default_thread_rate_limit_per_user IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" DROP CONSTRAINT IF EXISTS "PK_e81f8bb92802737337d35c00981"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" DROP COLUMN IF EXISTS "index"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" ADD "index" SERIAL NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_settings" ADD CONSTRAINT "PK_e81f8bb92802737337d35c00981" PRIMARY KEY ("index")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" DROP COLUMN IF EXISTS "primary_category_id"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ADD "primary_category_id" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET large = false WHERE large IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "large" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET premium_tier = 0 WHERE premium_tier IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "premium_tier" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET unavailable = false WHERE unavailable IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "unavailable" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET widget_enabled = false WHERE widget_enabled IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "widget_enabled" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE guilds SET nsfw = false WHERE nsfw IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "guilds" ALTER COLUMN "nsfw" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "members" DROP COLUMN IF EXISTS "premium_since"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "members" ADD "premium_since" bigint`,
|
||||
);
|
||||
await queryRunner.query(`UPDATE users SET bio = '' WHERE bio IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE users ALTER COLUMN bio SET NOT NULL`);
|
||||
await queryRunner.query(`UPDATE users SET mfa_enabled = false WHERE mfa_enabled IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE users ALTER COLUMN mfa_enabled SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD CONSTRAINT "FK_0c14beb78d8c5ccba66072adbc7" FOREIGN KEY ("settingsIndex") REFERENCES "user_settings"("index") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE users ALTER COLUMN bio SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE users SET mfa_enabled = false WHERE mfa_enabled IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE users ALTER COLUMN mfa_enabled SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "users" ADD CONSTRAINT "FK_0c14beb78d8c5ccba66072adbc7" FOREIGN KEY ("settingsIndex") REFERENCES "user_settings"("index") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
}
|
||||
}
|
||||
async down(queryRunner) {}
|
||||
};
|
||||
|
@ -17,4 +17,4 @@ const { initDatabase } = require("..");
|
||||
await db.synchronize();
|
||||
console.log("done");
|
||||
db.destroy();
|
||||
})();
|
||||
})();
|
||||
|
@ -14,7 +14,7 @@ import { initInstance } from "./util/handlers/Instance";
|
||||
import { registerRoutes } from "@fosscord/util";
|
||||
import { red } from "picocolors";
|
||||
|
||||
export interface FosscordServerOptions extends ServerOptions { }
|
||||
export interface FosscordServerOptions extends ServerOptions {}
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
@ -76,15 +76,12 @@ export class FosscordServer extends Server {
|
||||
// 404 is not an error in express, so this should not be an error middleware
|
||||
// this is a fine place to put the 404 handler because its after we register the routes
|
||||
// and since its not an error middleware, our error handler below still works.
|
||||
api.use(
|
||||
"*",
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
res.status(404).json({
|
||||
message: "404 endpoint not found",
|
||||
code: 0,
|
||||
});
|
||||
},
|
||||
);
|
||||
api.use("*", (req: Request, res: Response, next: NextFunction) => {
|
||||
res.status(404).json({
|
||||
message: "404 endpoint not found",
|
||||
code: 0,
|
||||
});
|
||||
});
|
||||
|
||||
this.app = app;
|
||||
|
||||
|
@ -22,18 +22,30 @@ export default function TestClient(app: Application) {
|
||||
|
||||
const agent = new ProxyAgent();
|
||||
|
||||
let html = fs.readFileSync(path.join(ASSET_FOLDER_PATH, "client_test", "index.html"), { encoding: "utf-8" });
|
||||
let html = fs.readFileSync(
|
||||
path.join(ASSET_FOLDER_PATH, "client_test", "index.html"),
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
|
||||
html = applyEnv(html); // update window.GLOBAL_ENV according to config
|
||||
html = applyEnv(html); // update window.GLOBAL_ENV according to config
|
||||
|
||||
html = applyPlugins(html); // inject our plugins
|
||||
app.use("/assets/plugins", express.static(path.join(ASSET_FOLDER_PATH, "plugins")));
|
||||
app.use("/assets/inline-plugins", express.static(path.join(ASSET_FOLDER_PATH, "inline-plugins")));
|
||||
html = applyPlugins(html); // inject our plugins
|
||||
app.use(
|
||||
"/assets/plugins",
|
||||
express.static(path.join(ASSET_FOLDER_PATH, "plugins")),
|
||||
);
|
||||
app.use(
|
||||
"/assets/inline-plugins",
|
||||
express.static(path.join(ASSET_FOLDER_PATH, "inline-plugins")),
|
||||
);
|
||||
|
||||
// Asset memory cache
|
||||
const assetCache = new Map<string, { response: FetchResponse; buffer: Buffer; }>();
|
||||
const assetCache = new Map<
|
||||
string,
|
||||
{ response: FetchResponse; buffer: Buffer }
|
||||
>();
|
||||
|
||||
// Fetches uncached ( on disk ) assets from discord.com and stores them in memory cache.
|
||||
// Fetches uncached ( on disk ) assets from discord.com and stores them in memory cache.
|
||||
app.get("/assets/:file", async (req, res) => {
|
||||
delete req.headers.host;
|
||||
|
||||
@ -43,13 +55,15 @@ export default function TestClient(app: Application) {
|
||||
let buffer: Buffer;
|
||||
const cache = assetCache.get(req.params.file);
|
||||
if (!cache) {
|
||||
response = await fetch(`https://discord.com/assets/${req.params.file}`, {
|
||||
agent,
|
||||
headers: { ...req.headers as { [key: string]: string; } },
|
||||
});
|
||||
response = await fetch(
|
||||
`https://discord.com/assets/${req.params.file}`,
|
||||
{
|
||||
agent,
|
||||
headers: { ...(req.headers as { [key: string]: string }) },
|
||||
},
|
||||
);
|
||||
buffer = await response.buffer();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
response = cache.response;
|
||||
buffer = cache.buffer;
|
||||
}
|
||||
@ -62,8 +76,8 @@ export default function TestClient(app: Application) {
|
||||
"transfer-encoding",
|
||||
"expect-ct",
|
||||
"access-control-allow-origin",
|
||||
"content-encoding"
|
||||
].forEach(headerName => {
|
||||
"content-encoding",
|
||||
].forEach((headerName) => {
|
||||
response.headers.delete(headerName);
|
||||
});
|
||||
response.headers.forEach((value, name) => res.set(name, value));
|
||||
@ -72,8 +86,13 @@ export default function TestClient(app: Application) {
|
||||
|
||||
// TODO: I don't like this. Figure out a way to get client cacher to download *all* assets.
|
||||
if (response.status == 200) {
|
||||
console.warn(`[TestClient] Cache miss for file ${req.params.file}! Use 'npm run generate:client' to cache and patch.`);
|
||||
await fs.promises.appendFile(path.join(ASSET_FOLDER_PATH, "cacheMisses"), req.params.file + "\n");
|
||||
console.warn(
|
||||
`[TestClient] Cache miss for file ${req.params.file}! Use 'npm run generate:client' to cache and patch.`,
|
||||
);
|
||||
await fs.promises.appendFile(
|
||||
path.join(ASSET_FOLDER_PATH, "cacheMisses"),
|
||||
req.params.file + "\n",
|
||||
);
|
||||
}
|
||||
|
||||
return res.send(buffer);
|
||||
@ -81,17 +100,23 @@ export default function TestClient(app: Application) {
|
||||
|
||||
// Instead of our generated html, send developers.html for developers endpoint
|
||||
app.get("/developers*", (req, res) => {
|
||||
res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24); // 24 hours
|
||||
res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24); // 24 hours
|
||||
res.set("content-type", "text/html");
|
||||
res.send(fs.readFileSync(path.join(ASSET_FOLDER_PATH, "client_test", "developers.html"), { encoding: "utf-8" }));
|
||||
res.send(
|
||||
fs.readFileSync(
|
||||
path.join(ASSET_FOLDER_PATH, "client_test", "developers.html"),
|
||||
{ encoding: "utf-8" },
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Send our generated index.html for all routes.
|
||||
app.get("*", (req, res) => {
|
||||
res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24); // 24 hours
|
||||
res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24); // 24 hours
|
||||
res.set("content-type", "text/html");
|
||||
|
||||
if (req.url.startsWith("/api") || req.url.startsWith("/__development")) return;
|
||||
if (req.url.startsWith("/api") || req.url.startsWith("/__development"))
|
||||
return;
|
||||
|
||||
return res.send(html);
|
||||
});
|
||||
@ -101,16 +126,26 @@ export default function TestClient(app: Application) {
|
||||
const applyEnv = (html: string): string => {
|
||||
const config = Config.get();
|
||||
|
||||
const cdn = (config.cdn.endpointClient || config.cdn.endpointPublic || process.env.CDN || "")
|
||||
.replace(/(https?)?(:\/\/?)/g, "");
|
||||
const cdn = (
|
||||
config.cdn.endpointClient ||
|
||||
config.cdn.endpointPublic ||
|
||||
process.env.CDN ||
|
||||
""
|
||||
).replace(/(https?)?(:\/\/?)/g, "");
|
||||
|
||||
const gateway = (config.gateway.endpointClient || config.gateway.endpointPublic || process.env.GATEWAY || "");
|
||||
const gateway =
|
||||
config.gateway.endpointClient ||
|
||||
config.gateway.endpointPublic ||
|
||||
process.env.GATEWAY ||
|
||||
"";
|
||||
|
||||
if (cdn)
|
||||
html = html.replace(/CDN_HOST: .+/, `CDN_HOST: \`${cdn}\`,`);
|
||||
if (cdn) html = html.replace(/CDN_HOST: .+/, `CDN_HOST: \`${cdn}\`,`);
|
||||
|
||||
if (gateway)
|
||||
html = html.replace(/GATEWAY_ENDPOINT: .+/, `GATEWAY_ENDPOINT: \`${gateway}\`,`);
|
||||
html = html.replace(
|
||||
/GATEWAY_ENDPOINT: .+/,
|
||||
`GATEWAY_ENDPOINT: \`${gateway}\`,`,
|
||||
);
|
||||
|
||||
return html;
|
||||
};
|
||||
@ -118,28 +153,37 @@ const applyEnv = (html: string): string => {
|
||||
// Injects inline, preload, and standard plugins into index.html.
|
||||
const applyPlugins = (html: string): string => {
|
||||
// Inline plugins. Injected as <script src="/assets/inline-plugins/name.js"> into head.
|
||||
const inlineFiles = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "inline-plugins"));
|
||||
const inlineFiles = fs.readdirSync(
|
||||
path.join(ASSET_FOLDER_PATH, "inline-plugins"),
|
||||
);
|
||||
const inline = inlineFiles
|
||||
.filter(x => x.endsWith(".js"))
|
||||
.map(x => `<script src="/assets/inline-plugins/${x}"></script>`)
|
||||
.filter((x) => x.endsWith(".js"))
|
||||
.map((x) => `<script src="/assets/inline-plugins/${x}"></script>`)
|
||||
.join("\n");
|
||||
html = html.replace("<!-- inline plugin marker -->", inline);
|
||||
|
||||
// Preload plugins. Text content of each plugin is injected into head.
|
||||
const preloadFiles = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "preload-plugins"));
|
||||
const preloadFiles = fs.readdirSync(
|
||||
path.join(ASSET_FOLDER_PATH, "preload-plugins"),
|
||||
);
|
||||
const preload = preloadFiles
|
||||
.filter(x => x.endsWith(".js"))
|
||||
.map(x => `<script>${fs.readFileSync(path.join(ASSET_FOLDER_PATH, "preload-plugins", x))}</script>`)
|
||||
.filter((x) => x.endsWith(".js"))
|
||||
.map(
|
||||
(x) =>
|
||||
`<script>${fs.readFileSync(
|
||||
path.join(ASSET_FOLDER_PATH, "preload-plugins", x),
|
||||
)}</script>`,
|
||||
)
|
||||
.join("\n");
|
||||
html = html.replace("<!-- preload plugin marker -->", preload);
|
||||
|
||||
// Normal plugins. Injected as <script src="/assets/plugins/name.js"> into body.
|
||||
const pluginFiles = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "plugins"));
|
||||
const plugins = pluginFiles
|
||||
.filter(x => x.endsWith(".js"))
|
||||
.map(x => `<script src="/assets/plugins/${x}"></script>`)
|
||||
.filter((x) => x.endsWith(".js"))
|
||||
.map((x) => `<script src="/assets/plugins/${x}"></script>`)
|
||||
.join("\n");
|
||||
html = html.replace("<!-- plugin marker -->", plugins);
|
||||
|
||||
return html;
|
||||
};
|
||||
};
|
||||
|
@ -1,13 +1,23 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
import { route } from "@fosscord/api";
|
||||
import { Application, generateToken, User, BotModifySchema, handleFile, DiscordApiErrors } from "@fosscord/util";
|
||||
import {
|
||||
Application,
|
||||
generateToken,
|
||||
User,
|
||||
BotModifySchema,
|
||||
handleFile,
|
||||
DiscordApiErrors,
|
||||
} from "@fosscord/util";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { verifyToken } from "node-2fa";
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
router.post("/", route({}), async (req: Request, res: Response) => {
|
||||
const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["owner"] });
|
||||
const app = await Application.findOneOrFail({
|
||||
where: { id: req.params.id },
|
||||
relations: ["owner"],
|
||||
});
|
||||
|
||||
if (app.owner.id != req.user_id)
|
||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||
@ -31,7 +41,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
|
||||
await app.save();
|
||||
|
||||
res.send({
|
||||
token: await generateToken(user.id)
|
||||
token: await generateToken(user.id),
|
||||
}).status(204);
|
||||
});
|
||||
|
||||
@ -42,7 +52,10 @@ router.post("/reset", route({}), async (req: Request, res: Response) => {
|
||||
if (owner.id != req.user_id)
|
||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||
|
||||
if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code)))
|
||||
if (
|
||||
owner.totp_secret &&
|
||||
(!req.body.code || verifyToken(owner.totp_secret, req.body.code))
|
||||
)
|
||||
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||
|
||||
bot.data = { hash: undefined, valid_tokens_since: new Date() };
|
||||
@ -54,30 +67,36 @@ router.post("/reset", route({}), async (req: Request, res: Response) => {
|
||||
res.json({ token }).status(200);
|
||||
});
|
||||
|
||||
router.patch("/", route({ body: "BotModifySchema" }), async (req: Request, res: Response) => {
|
||||
const body = req.body as BotModifySchema;
|
||||
if (!body.avatar?.trim()) delete body.avatar;
|
||||
router.patch(
|
||||
"/",
|
||||
route({ body: "BotModifySchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const body = req.body as BotModifySchema;
|
||||
if (!body.avatar?.trim()) delete body.avatar;
|
||||
|
||||
const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["bot", "owner"] });
|
||||
const app = await Application.findOneOrFail({
|
||||
where: { id: req.params.id },
|
||||
relations: ["bot", "owner"],
|
||||
});
|
||||
|
||||
if (!app.bot)
|
||||
throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
|
||||
if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
|
||||
|
||||
if (app.owner.id != req.user_id)
|
||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||
if (app.owner.id != req.user_id)
|
||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||
|
||||
if (body.avatar)
|
||||
body.avatar = await handleFile(
|
||||
`/avatars/${app.id}`,
|
||||
body.avatar as string,
|
||||
);
|
||||
if (body.avatar)
|
||||
body.avatar = await handleFile(
|
||||
`/avatars/${app.id}`,
|
||||
body.avatar as string,
|
||||
);
|
||||
|
||||
app.bot.assign(body);
|
||||
app.bot.assign(body);
|
||||
|
||||
app.bot.save();
|
||||
app.bot.save();
|
||||
|
||||
await app.save();
|
||||
res.json(app).status(200);
|
||||
});
|
||||
await app.save();
|
||||
res.json(app).status(200);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
@ -1,57 +1,81 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
import { route } from "@fosscord/api";
|
||||
import { Application, OrmUtils, DiscordApiErrors, ApplicationModifySchema, User } from "@fosscord/util";
|
||||
import {
|
||||
Application,
|
||||
OrmUtils,
|
||||
DiscordApiErrors,
|
||||
ApplicationModifySchema,
|
||||
User,
|
||||
} from "@fosscord/util";
|
||||
import { verifyToken } from "node-2fa";
|
||||
import { HTTPError } from "lambert-server";
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["owner", "bot"] });
|
||||
const app = await Application.findOneOrFail({
|
||||
where: { id: req.params.id },
|
||||
relations: ["owner", "bot"],
|
||||
});
|
||||
if (app.owner.id != req.user_id)
|
||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||
|
||||
return res.json(app);
|
||||
});
|
||||
|
||||
router.patch("/", route({ body: "ApplicationModifySchema" }), async (req: Request, res: Response) => {
|
||||
const body = req.body as ApplicationModifySchema;
|
||||
router.patch(
|
||||
"/",
|
||||
route({ body: "ApplicationModifySchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const body = req.body as ApplicationModifySchema;
|
||||
|
||||
const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["owner", "bot"] });
|
||||
const app = await Application.findOneOrFail({
|
||||
where: { id: req.params.id },
|
||||
relations: ["owner", "bot"],
|
||||
});
|
||||
|
||||
if (app.owner.id != req.user_id)
|
||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||
if (app.owner.id != req.user_id)
|
||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||
|
||||
if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code)))
|
||||
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||
if (
|
||||
app.owner.totp_secret &&
|
||||
(!req.body.code ||
|
||||
verifyToken(app.owner.totp_secret, req.body.code))
|
||||
)
|
||||
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||
|
||||
if (app.bot) {
|
||||
app.bot.assign({ bio: body.description });
|
||||
await app.bot.save();
|
||||
}
|
||||
if (app.bot) {
|
||||
app.bot.assign({ bio: body.description });
|
||||
await app.bot.save();
|
||||
}
|
||||
|
||||
app.assign(body);
|
||||
app.assign(body);
|
||||
|
||||
await app.save();
|
||||
await app.save();
|
||||
|
||||
return res.json(app);
|
||||
});
|
||||
return res.json(app);
|
||||
},
|
||||
);
|
||||
|
||||
router.post("/delete", route({}), async (req: Request, res: Response) => {
|
||||
const app = await Application.findOneOrFail({ where: { id: req.params.id }, relations: ["bot", "owner"] });
|
||||
const app = await Application.findOneOrFail({
|
||||
where: { id: req.params.id },
|
||||
relations: ["bot", "owner"],
|
||||
});
|
||||
if (app.owner.id != req.user_id)
|
||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||
|
||||
if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code)))
|
||||
if (
|
||||
app.owner.totp_secret &&
|
||||
(!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))
|
||||
)
|
||||
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||
|
||||
if (app.bot)
|
||||
await User.delete({ id: app.bot.id });
|
||||
if (app.bot) await User.delete({ id: app.bot.id });
|
||||
|
||||
await Application.delete({ id: app.id });
|
||||
|
||||
res.send().status(200);
|
||||
});
|
||||
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
@ -8,4 +8,4 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
res.json([]).status(200);
|
||||
});
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
@ -1,30 +1,42 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
import { route } from "@fosscord/api";
|
||||
import { Application, ApplicationCreateSchema, trimSpecial, User } from "@fosscord/util";
|
||||
import {
|
||||
Application,
|
||||
ApplicationCreateSchema,
|
||||
trimSpecial,
|
||||
User,
|
||||
} from "@fosscord/util";
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
let results = await Application.find({ where: { owner: { id: req.user_id } }, relations: ["owner", "bot"] });
|
||||
let results = await Application.find({
|
||||
where: { owner: { id: req.user_id } },
|
||||
relations: ["owner", "bot"],
|
||||
});
|
||||
res.json(results).status(200);
|
||||
});
|
||||
|
||||
router.post("/", route({ body: "ApplicationCreateSchema" }), async (req: Request, res: Response) => {
|
||||
const body = req.body as ApplicationCreateSchema;
|
||||
const user = await User.findOneOrFail({ where: { id: req.user_id } });
|
||||
router.post(
|
||||
"/",
|
||||
route({ body: "ApplicationCreateSchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const body = req.body as ApplicationCreateSchema;
|
||||
const user = await User.findOneOrFail({ where: { id: req.user_id } });
|
||||
|
||||
const app = Application.create({
|
||||
name: trimSpecial(body.name),
|
||||
description: "",
|
||||
bot_public: true,
|
||||
owner: user,
|
||||
verify_key: "IMPLEMENTME",
|
||||
flags: 0,
|
||||
});
|
||||
const app = Application.create({
|
||||
name: trimSpecial(body.name),
|
||||
description: "",
|
||||
bot_public: true,
|
||||
owner: user,
|
||||
verify_key: "IMPLEMENTME",
|
||||
flags: 0,
|
||||
});
|
||||
|
||||
await app.save();
|
||||
await app.save();
|
||||
|
||||
res.json(app);
|
||||
});
|
||||
res.json(app);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
@ -5,24 +5,37 @@ import { Request, Response, Router } from "express";
|
||||
const router: Router = Router();
|
||||
export default router;
|
||||
|
||||
router.get("/", route({ right: "OPERATOR" }), async (req: Request, res: Response) => {
|
||||
const count = req.query.count ? parseInt(req.query.count as string) : 1;
|
||||
const length = req.query.length ? parseInt(req.query.length as string) : 255;
|
||||
router.get(
|
||||
"/",
|
||||
route({ right: "OPERATOR" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const count = req.query.count ? parseInt(req.query.count as string) : 1;
|
||||
const length = req.query.length
|
||||
? parseInt(req.query.length as string)
|
||||
: 255;
|
||||
|
||||
let tokens: ValidRegistrationToken[] = [];
|
||||
let tokens: ValidRegistrationToken[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const token = ValidRegistrationToken.create({
|
||||
token: random(length),
|
||||
expires_at: Date.now() + Config.get().security.defaultRegistrationTokenExpiration
|
||||
for (let i = 0; i < count; i++) {
|
||||
const token = ValidRegistrationToken.create({
|
||||
token: random(length),
|
||||
expires_at:
|
||||
Date.now() +
|
||||
Config.get().security.defaultRegistrationTokenExpiration,
|
||||
});
|
||||
tokens.push(token);
|
||||
}
|
||||
|
||||
// Why are these options used, exactly?
|
||||
await ValidRegistrationToken.save(tokens, {
|
||||
chunk: 1000,
|
||||
reload: false,
|
||||
transaction: false,
|
||||
});
|
||||
tokens.push(token);
|
||||
}
|
||||
|
||||
// Why are these options used, exactly?
|
||||
await ValidRegistrationToken.save(tokens, { chunk: 1000, reload: false, transaction: false });
|
||||
if (req.query.plain)
|
||||
return res.send(tokens.map((x) => x.token).join("\n"));
|
||||
|
||||
if (req.query.plain) return res.send(tokens.map(x => x.token).join("\n"));
|
||||
|
||||
return res.json({ tokens: tokens.map(x => x.token) });
|
||||
});
|
||||
return res.json({ tokens: tokens.map((x) => x.token) });
|
||||
},
|
||||
);
|
||||
|
@ -33,16 +33,22 @@ router.post(
|
||||
// Reg tokens
|
||||
// They're a one time use token that bypasses registration limits ( rates, disabled reg, etc )
|
||||
let regTokenUsed = false;
|
||||
if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { // eg theyre on https://staging.fosscord.com/register?token=whatever
|
||||
if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) {
|
||||
// eg theyre on https://staging.fosscord.com/register?token=whatever
|
||||
const token = req.get("Referrer")!.split("token=")[1].split("&")[0];
|
||||
if (token) {
|
||||
const regToken = await ValidRegistrationToken.findOne({ where: { token, expires_at: MoreThan(new Date()), } });
|
||||
const regToken = await ValidRegistrationToken.findOne({
|
||||
where: { token, expires_at: MoreThan(new Date()) },
|
||||
});
|
||||
await ValidRegistrationToken.delete({ token });
|
||||
regTokenUsed = true;
|
||||
console.log(`[REGISTER] Registration token ${token} used for registration!`);
|
||||
}
|
||||
else {
|
||||
console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`);
|
||||
console.log(
|
||||
`[REGISTER] Registration token ${token} used for registration!`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +84,11 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) {
|
||||
if (
|
||||
!regTokenUsed &&
|
||||
register.requireCaptcha &&
|
||||
security.captcha.enabled
|
||||
) {
|
||||
const { sitekey, service } = security.captcha;
|
||||
if (!body.captcha_key) {
|
||||
return res?.status(400).json({
|
||||
@ -220,14 +230,26 @@ router.post(
|
||||
if (
|
||||
!regTokenUsed &&
|
||||
limits.absoluteRate.register.enabled &&
|
||||
(await User.count({ where: { created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)) } }))
|
||||
>= limits.absoluteRate.register.limit
|
||||
(await User.count({
|
||||
where: {
|
||||
created_at: MoreThan(
|
||||
new Date(
|
||||
Date.now() - limits.absoluteRate.register.window,
|
||||
),
|
||||
),
|
||||
},
|
||||
})) >= limits.absoluteRate.register.limit
|
||||
) {
|
||||
console.log(
|
||||
`Global register ratelimit exceeded for ${getIpAdress(req)}, ${req.body.username}, ${req.body.invite || "No invite given"}`
|
||||
`Global register ratelimit exceeded for ${getIpAdress(req)}, ${
|
||||
req.body.username
|
||||
}, ${req.body.invite || "No invite given"}`,
|
||||
);
|
||||
throw FieldErrors({
|
||||
email: { code: "TOO_MANY_REGISTRATIONS", message: req.t("auth:register.TOO_MANY_REGISTRATIONS") }
|
||||
email: {
|
||||
code: "TOO_MANY_REGISTRATIONS",
|
||||
message: req.t("auth:register.TOO_MANY_REGISTRATIONS"),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -179,7 +179,7 @@ router.put(
|
||||
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);
|
||||
},
|
||||
|
@ -21,7 +21,12 @@ import {
|
||||
Rights,
|
||||
} from "@fosscord/util";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { handleMessage, postHandleMessage, route, getIpAdress } from "@fosscord/api";
|
||||
import {
|
||||
handleMessage,
|
||||
postHandleMessage,
|
||||
route,
|
||||
getIpAdress,
|
||||
} from "@fosscord/api";
|
||||
import multer from "multer";
|
||||
import { yellow } from "picocolors";
|
||||
import { FindManyOptions, LessThan, MoreThan } from "typeorm";
|
||||
@ -80,7 +85,7 @@ router.get("/", async (req: Request, res: Response) => {
|
||||
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 },
|
||||
@ -138,8 +143,9 @@ router.get("/", async (req: Request, res: Response) => {
|
||||
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
|
||||
}`;
|
||||
y.proxy_url = `${endpoint == null ? "" : endpoint}${
|
||||
new URL(uri).pathname
|
||||
}`;
|
||||
});
|
||||
|
||||
/**
|
||||
@ -211,8 +217,8 @@ router.post(
|
||||
where: {
|
||||
nonce: body.nonce,
|
||||
channel_id: channel.id,
|
||||
author_id: req.user_id
|
||||
}
|
||||
author_id: req.user_id,
|
||||
},
|
||||
});
|
||||
if (existing) {
|
||||
return res.json(existing);
|
||||
@ -225,13 +231,21 @@ router.post(
|
||||
const count = await Message.count({
|
||||
where: {
|
||||
channel_id,
|
||||
timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window))
|
||||
}
|
||||
timestamp: MoreThan(
|
||||
new Date(
|
||||
Date.now() -
|
||||
limits.absoluteRate.sendMessage.window,
|
||||
),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
if (count >= limits.absoluteRate.sendMessage.limit)
|
||||
throw FieldErrors({
|
||||
channel_id: { code: "TOO_MANY_MESSAGES", message: req.t("common:toomany.MESSAGE") }
|
||||
channel_id: {
|
||||
code: "TOO_MANY_MESSAGES",
|
||||
message: req.t("common:toomany.MESSAGE"),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -247,7 +261,7 @@ router.post(
|
||||
Attachment.create({ ...file, proxy_url: file.url }),
|
||||
);
|
||||
} catch (error) {
|
||||
return res.status(400).json({ message: error!.toString() })
|
||||
return res.status(400).json({ message: error!.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,19 +310,18 @@ router.post(
|
||||
if (!message.member) {
|
||||
message.member = await Member.findOneOrFail({
|
||||
where: { id: req.user_id, guild_id: message.guild_id },
|
||||
relations: ["roles"]
|
||||
relations: ["roles"],
|
||||
});
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
message.member.roles =
|
||||
message.member.roles.
|
||||
filter(x => x.id != x.guild_id)
|
||||
.map(x => x.id);
|
||||
message.member.roles = message.member.roles
|
||||
.filter((x) => x.id != x.guild_id)
|
||||
.map((x) => x.id);
|
||||
}
|
||||
|
||||
let read_state = await ReadState.findOne({
|
||||
where: { user_id: req.user_id, channel_id }
|
||||
where: { user_id: req.user_id, channel_id },
|
||||
});
|
||||
if (!read_state)
|
||||
read_state = ReadState.create({ user_id: req.user_id, channel_id });
|
||||
@ -324,14 +337,14 @@ router.post(
|
||||
} as MessageCreateEvent),
|
||||
message.guild_id
|
||||
? Member.update(
|
||||
{ id: req.user_id, guild_id: message.guild_id },
|
||||
{ last_message_id: message.id },
|
||||
)
|
||||
{ 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);
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
emitEvent,
|
||||
Member,
|
||||
Role,
|
||||
ChannelPermissionOverwriteSchema
|
||||
ChannelPermissionOverwriteSchema,
|
||||
} from "@fosscord/util";
|
||||
import { Router, Response, Request } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { Router, Response, Request } from "express";
|
||||
import { route } from "@fosscord/api";
|
||||
import { Channel, Config, handleFile, trimSpecial, User, Webhook, WebhookCreateSchema, WebhookType } from "@fosscord/util";
|
||||
import {
|
||||
Channel,
|
||||
Config,
|
||||
handleFile,
|
||||
trimSpecial,
|
||||
User,
|
||||
Webhook,
|
||||
WebhookCreateSchema,
|
||||
WebhookType,
|
||||
} from "@fosscord/util";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { isTextChannel } from "./messages/index";
|
||||
import { DiscordApiErrors } from "@fosscord/util";
|
||||
@ -38,8 +47,7 @@ router.post(
|
||||
if (name === "clyde") throw new HTTPError("Invalid name", 400);
|
||||
if (name === "Fosscord Ghost") throw new HTTPError("Invalid name", 400);
|
||||
|
||||
if (avatar)
|
||||
avatar = await handleFile(`/avatars/${channel_id}`, avatar);
|
||||
if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar);
|
||||
|
||||
const hook = Webhook.create({
|
||||
type: WebhookType.Incoming,
|
||||
|
@ -12,19 +12,20 @@ const router = Router();
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const { platform } = req.query;
|
||||
|
||||
if (!platform) throw FieldErrors({
|
||||
platform: {
|
||||
code: "BASE_TYPE_REQUIRED",
|
||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||
}
|
||||
});
|
||||
if (!platform)
|
||||
throw FieldErrors({
|
||||
platform: {
|
||||
code: "BASE_TYPE_REQUIRED",
|
||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||
},
|
||||
});
|
||||
|
||||
const release = await Release.findOneOrFail({
|
||||
where: {
|
||||
enabled: true,
|
||||
platform: platform as string,
|
||||
},
|
||||
order: { pub_date: "DESC" }
|
||||
order: { pub_date: "DESC" },
|
||||
});
|
||||
|
||||
res.redirect(release.url);
|
||||
|
@ -9,7 +9,7 @@ const router = Router();
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const { limit, personalization_disabled } = req.query;
|
||||
var showAllGuilds = Config.get().guild.discovery.showAllGuilds;
|
||||
|
||||
|
||||
const genLoadId = (size: Number) =>
|
||||
[...Array(size)]
|
||||
.map(() => Math.floor(Math.random() * 16).toString(16))
|
||||
|
@ -69,15 +69,21 @@ router.patch(
|
||||
body.splash,
|
||||
);
|
||||
|
||||
if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash)
|
||||
if (
|
||||
body.discovery_splash &&
|
||||
body.discovery_splash !== guild.discovery_splash
|
||||
)
|
||||
body.discovery_splash = await handleFile(
|
||||
`/discovery-splashes/${guild_id}`,
|
||||
body.discovery_splash,
|
||||
);
|
||||
|
||||
if (body.features) {
|
||||
const diff = guild.features.filter(x => !body.features?.includes(x))
|
||||
.concat(body.features.filter(x => !guild.features.includes(x)));
|
||||
const diff = guild.features
|
||||
.filter((x) => !body.features?.includes(x))
|
||||
.concat(
|
||||
body.features.filter((x) => !guild.features.includes(x)),
|
||||
);
|
||||
|
||||
// TODO move these
|
||||
const MUTABLE_FEATURES = [
|
||||
@ -89,7 +95,9 @@ router.patch(
|
||||
for (var feature of diff) {
|
||||
if (MUTABLE_FEATURES.includes(feature)) continue;
|
||||
|
||||
throw FosscordApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature);
|
||||
throw FosscordApiErrors.FEATURE_IS_IMMUTABLE.withParams(
|
||||
feature,
|
||||
);
|
||||
}
|
||||
|
||||
// for some reason, they don't update in the assign.
|
||||
|
@ -27,42 +27,56 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
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;
|
||||
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;
|
||||
|
||||
let 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 } });
|
||||
let 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.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string);
|
||||
if (body.avatar)
|
||||
body.avatar = await handleFile(
|
||||
`/guilds/${guild_id}/users/${member_id}/avatars`,
|
||||
body.avatar as string,
|
||||
);
|
||||
|
||||
member.assign(body);
|
||||
member.assign(body);
|
||||
|
||||
if ('roles' in body) {
|
||||
permission.hasThrow("MANAGE_ROLES");
|
||||
if ("roles" in body) {
|
||||
permission.hasThrow("MANAGE_ROLES");
|
||||
|
||||
body.roles = body.roles || [];
|
||||
body.roles.filter(x => !!x);
|
||||
body.roles = body.roles || [];
|
||||
body.roles.filter((x) => !!x);
|
||||
|
||||
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 (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
|
||||
}
|
||||
|
||||
await member.save();
|
||||
await member.save();
|
||||
|
||||
member.roles = member.roles.filter((x) => x.id !== everyone.id);
|
||||
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);
|
||||
// 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);
|
||||
});
|
||||
res.json(member);
|
||||
},
|
||||
);
|
||||
|
||||
router.put("/", route({}), async (req: Request, res: Response) => {
|
||||
// TODO: Lurker mode
|
||||
|
@ -72,12 +72,20 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
if (channel_id) query.where!.channel = { id: channel_id };
|
||||
else {
|
||||
// get all channel IDs that this user can access
|
||||
const channels = await Channel.find({ where: { guild_id: req.params.guild_id }, select: ["id"] });
|
||||
const channels = await Channel.find({
|
||||
where: { guild_id: req.params.guild_id },
|
||||
select: ["id"],
|
||||
});
|
||||
const ids = [];
|
||||
|
||||
for (var channel of channels) {
|
||||
const perm = await getPermission(req.user_id, req.params.guild_id, channel.id);
|
||||
if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue;
|
||||
const perm = await getPermission(
|
||||
req.user_id,
|
||||
req.params.guild_id,
|
||||
channel.id,
|
||||
);
|
||||
if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY"))
|
||||
continue;
|
||||
ids.push(channel.id);
|
||||
}
|
||||
|
||||
|
@ -1,30 +1,48 @@
|
||||
import { route } from "@fosscord/api";
|
||||
import { emitEvent, GuildMemberUpdateEvent, handleFile, Member, MemberChangeProfileSchema, OrmUtils } from "@fosscord/util";
|
||||
import {
|
||||
emitEvent,
|
||||
GuildMemberUpdateEvent,
|
||||
handleFile,
|
||||
Member,
|
||||
MemberChangeProfileSchema,
|
||||
OrmUtils,
|
||||
} from "@fosscord/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.patch("/:member_id", route({ body: "MemberChangeProfileSchema" }), 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 MemberChangeProfileSchema;
|
||||
router.patch(
|
||||
"/:member_id",
|
||||
route({ body: "MemberChangeProfileSchema" }),
|
||||
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 MemberChangeProfileSchema;
|
||||
|
||||
let member = await Member.findOneOrFail({ where: { id: req.user_id, guild_id }, relations: ["roles", "user"] });
|
||||
let member = await Member.findOneOrFail({
|
||||
where: { id: req.user_id, guild_id },
|
||||
relations: ["roles", "user"],
|
||||
});
|
||||
|
||||
if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string);
|
||||
if (body.banner)
|
||||
body.banner = await handleFile(
|
||||
`/guilds/${guild_id}/users/${req.user_id}/avatars`,
|
||||
body.banner as string,
|
||||
);
|
||||
|
||||
member = await OrmUtils.mergeDeep(member, body);
|
||||
member = await OrmUtils.mergeDeep(member, body);
|
||||
|
||||
await member.save();
|
||||
await member.save();
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
});
|
||||
res.json(member);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -63,12 +63,14 @@ router.patch(
|
||||
);
|
||||
else body.icon = undefined;
|
||||
|
||||
const role = await Role.findOneOrFail({ where: { id: role_id, guild: { id: guild_id } } });
|
||||
const role = await Role.findOneOrFail({
|
||||
where: { id: role_id, guild: { id: guild_id } },
|
||||
});
|
||||
role.assign({
|
||||
...body,
|
||||
permissions: String(
|
||||
req.permission!.bitfield & BigInt(body.permissions || "0")
|
||||
)
|
||||
req.permission!.bitfield & BigInt(body.permissions || "0"),
|
||||
),
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
|
@ -61,9 +61,13 @@ router.post(
|
||||
await Promise.all([
|
||||
role.save(),
|
||||
// Move all existing roles up one position, to accommodate the new role
|
||||
Role.createQueryBuilder('roles')
|
||||
.where({ guild: { id: guild_id }, name: Not("@everyone"), id: Not(role.id) })
|
||||
.update({ position: () => 'position + 1' })
|
||||
Role.createQueryBuilder("roles")
|
||||
.where({
|
||||
guild: { id: guild_id },
|
||||
name: Not("@everyone"),
|
||||
id: Not(role.id),
|
||||
})
|
||||
.update({ position: () => "position + 1" })
|
||||
.execute(),
|
||||
emitEvent({
|
||||
event: "GUILD_ROLE_CREATE",
|
||||
|
@ -1,17 +1,24 @@
|
||||
import { Router, Request, Response } from "express";
|
||||
import { route } from "@fosscord/api";
|
||||
import { ApiError, Application, ApplicationAuthorizeSchema, getPermission, DiscordApiErrors, Member, Permissions, User, getRights, Rights, MemberPrivateProjection } from "@fosscord/util";
|
||||
import {
|
||||
ApiError,
|
||||
Application,
|
||||
ApplicationAuthorizeSchema,
|
||||
getPermission,
|
||||
DiscordApiErrors,
|
||||
Member,
|
||||
Permissions,
|
||||
User,
|
||||
getRights,
|
||||
Rights,
|
||||
MemberPrivateProjection,
|
||||
} from "@fosscord/util";
|
||||
const router = Router();
|
||||
|
||||
// TODO: scopes, other oauth types
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const {
|
||||
client_id,
|
||||
scope,
|
||||
response_type,
|
||||
redirect_url,
|
||||
} = req.query;
|
||||
const { client_id, scope, response_type, redirect_url } = req.query;
|
||||
|
||||
const app = await Application.findOne({
|
||||
where: {
|
||||
@ -33,7 +40,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
id: req.user_id,
|
||||
bot: false,
|
||||
},
|
||||
select: ["id", "username", "avatar", "discriminator", "public_flags"]
|
||||
select: ["id", "username", "avatar", "discriminator", "public_flags"],
|
||||
});
|
||||
|
||||
const guilds = await Member.find({
|
||||
@ -44,21 +51,23 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
},
|
||||
relations: ["guild", "roles"],
|
||||
//@ts-ignore
|
||||
select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"]
|
||||
// prettier-ignore
|
||||
select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"],
|
||||
});
|
||||
|
||||
const guildsWithPermissions = guilds.map(x => {
|
||||
const perms = x.guild.owner_id === user.id
|
||||
? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
|
||||
: Permissions.finalPermission({
|
||||
user: {
|
||||
id: user.id,
|
||||
roles: x.roles?.map(x => x.id) || [],
|
||||
},
|
||||
guild: {
|
||||
roles: x?.roles || [],
|
||||
}
|
||||
});
|
||||
const guildsWithPermissions = guilds.map((x) => {
|
||||
const perms =
|
||||
x.guild.owner_id === user.id
|
||||
? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
|
||||
: Permissions.finalPermission({
|
||||
user: {
|
||||
id: user.id,
|
||||
roles: x.roles?.map((x) => x.id) || [],
|
||||
},
|
||||
guild: {
|
||||
roles: x?.roles || [],
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: x.guild.id,
|
||||
@ -75,7 +84,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
avatar: user.avatar,
|
||||
avatar_decoration: null, // TODO
|
||||
avatar_decoration: null, // TODO
|
||||
discriminator: user.discriminator,
|
||||
public_flags: user.public_flags,
|
||||
},
|
||||
@ -87,7 +96,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
summary: app.summary,
|
||||
type: app.type,
|
||||
hook: app.hook,
|
||||
guild_id: null, // TODO support guilds
|
||||
guild_id: null, // TODO support guilds
|
||||
bot_public: app.bot_public,
|
||||
bot_require_code_grant: app.bot_require_code_grant,
|
||||
verify_key: app.verify_key,
|
||||
@ -97,50 +106,63 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
id: bot.id,
|
||||
username: bot.username,
|
||||
avatar: bot.avatar,
|
||||
avatar_decoration: null, // TODO
|
||||
avatar_decoration: null, // TODO
|
||||
discriminator: bot.discriminator,
|
||||
public_flags: bot.public_flags,
|
||||
bot: true,
|
||||
approximated_guild_count: 0, // TODO
|
||||
approximated_guild_count: 0, // TODO
|
||||
},
|
||||
authorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
router.post("/", route({ body: "ApplicationAuthorizeSchema" }), async (req: Request, res: Response) => {
|
||||
const body = req.body as ApplicationAuthorizeSchema;
|
||||
const {
|
||||
client_id,
|
||||
scope,
|
||||
response_type,
|
||||
redirect_url
|
||||
} = req.query;
|
||||
router.post(
|
||||
"/",
|
||||
route({ body: "ApplicationAuthorizeSchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const body = req.body as ApplicationAuthorizeSchema;
|
||||
const { client_id, scope, response_type, redirect_url } = req.query;
|
||||
|
||||
// TODO: captcha verification
|
||||
// TODO: MFA verification
|
||||
// TODO: captcha verification
|
||||
// TODO: MFA verification
|
||||
|
||||
const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
|
||||
// getPermission cache won't exist if we're owner
|
||||
if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member!.user.bot) throw DiscordApiErrors.UNAUTHORIZED;
|
||||
perms.hasThrow("MANAGE_GUILD");
|
||||
const perms = await getPermission(
|
||||
req.user_id,
|
||||
body.guild_id,
|
||||
undefined,
|
||||
{ member_relations: ["user"] },
|
||||
);
|
||||
// getPermission cache won't exist if we're owner
|
||||
if (
|
||||
Object.keys(perms.cache || {}).length > 0 &&
|
||||
perms.cache.member!.user.bot
|
||||
)
|
||||
throw DiscordApiErrors.UNAUTHORIZED;
|
||||
perms.hasThrow("MANAGE_GUILD");
|
||||
|
||||
const app = await Application.findOne({
|
||||
where: {
|
||||
id: client_id as string,
|
||||
},
|
||||
relations: ["bot"],
|
||||
});
|
||||
const app = await Application.findOne({
|
||||
where: {
|
||||
id: client_id as string,
|
||||
},
|
||||
relations: ["bot"],
|
||||
});
|
||||
|
||||
// TODO: use DiscordApiErrors
|
||||
// findOneOrFail throws code 404
|
||||
if (!app) throw new ApiError("Unknown Application", 10002, 404);
|
||||
if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
|
||||
// TODO: use DiscordApiErrors
|
||||
// findOneOrFail throws code 404
|
||||
if (!app) throw new ApiError("Unknown Application", 10002, 404);
|
||||
if (!app.bot)
|
||||
throw new ApiError(
|
||||
"OAuth2 application does not have a bot",
|
||||
50010,
|
||||
400,
|
||||
);
|
||||
|
||||
await Member.addToGuild(app.id, body.guild_id);
|
||||
await Member.addToGuild(app.id, body.guild_id);
|
||||
|
||||
return res.json({
|
||||
location: "/oauth2/authorized", // redirect URL
|
||||
});
|
||||
});
|
||||
return res.json({
|
||||
location: "/oauth2/authorized", // redirect URL
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { route } from "@fosscord/api";
|
||||
import { Config, getRights, Guild, Member, Message, User } from "@fosscord/util";
|
||||
import {
|
||||
Config,
|
||||
getRights,
|
||||
Guild,
|
||||
Member,
|
||||
Message,
|
||||
User,
|
||||
} from "@fosscord/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router();
|
||||
|
||||
@ -15,7 +22,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
guild: await Guild.count(),
|
||||
message: await Message.count(),
|
||||
members: await Member.count(),
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -3,10 +3,14 @@ import { route } from "@fosscord/api";
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
router.post("/", route({ right: "OPERATOR" }), async (req: Request, res: Response) => {
|
||||
console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
|
||||
res.sendStatus(200);
|
||||
process.kill(process.pid, "SIGTERM");
|
||||
});
|
||||
router.post(
|
||||
"/",
|
||||
route({ right: "OPERATOR" }),
|
||||
async (req: Request, res: Response) => {
|
||||
console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
|
||||
res.sendStatus(200);
|
||||
process.kill(process.pid, "SIGTERM");
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
@ -16,7 +16,7 @@ const skus = new Map([
|
||||
sku_id: "521842865731534868",
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
price_tier: null,
|
||||
},
|
||||
{
|
||||
id: "511651860671627264",
|
||||
@ -27,9 +27,9 @@ const skus = new Map([
|
||||
sku_id: "521842865731534868",
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
}
|
||||
]
|
||||
price_tier: null,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"521846918637420545",
|
||||
@ -43,7 +43,7 @@ const skus = new Map([
|
||||
sku_id: "521846918637420545",
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
price_tier: null,
|
||||
},
|
||||
{
|
||||
id: "511651876987469824",
|
||||
@ -54,7 +54,7 @@ const skus = new Map([
|
||||
sku_id: "521846918637420545",
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
price_tier: null,
|
||||
},
|
||||
{
|
||||
id: "978380684370378761",
|
||||
@ -65,9 +65,9 @@ const skus = new Map([
|
||||
sku_id: "521846918637420545",
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
}
|
||||
]
|
||||
price_tier: null,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"521847234246082599",
|
||||
@ -81,7 +81,7 @@ const skus = new Map([
|
||||
sku_id: "521847234246082599",
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
price_tier: null,
|
||||
},
|
||||
{
|
||||
id: "511651880837840896",
|
||||
@ -92,7 +92,7 @@ const skus = new Map([
|
||||
sku_id: "521847234246082599",
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
price_tier: null,
|
||||
},
|
||||
{
|
||||
id: "511651885459963904",
|
||||
@ -103,9 +103,9 @@ const skus = new Map([
|
||||
sku_id: "521847234246082599",
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
}
|
||||
]
|
||||
price_tier: null,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"590663762298667008",
|
||||
@ -120,7 +120,7 @@ const skus = new Map([
|
||||
discount_price: 0,
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
price_tier: null,
|
||||
},
|
||||
{
|
||||
id: "590665538238152709",
|
||||
@ -132,9 +132,9 @@ const skus = new Map([
|
||||
discount_price: 0,
|
||||
currency: "eur",
|
||||
price: 0,
|
||||
price_tier: null
|
||||
}
|
||||
]
|
||||
price_tier: null,
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"978380684370378762",
|
||||
@ -158,33 +158,33 @@ const skus = new Map([
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
]
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_source_prices: {
|
||||
"775487223059316758": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
"736345864146255982": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
"683074999590060249": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"3": {
|
||||
country_prices: {
|
||||
@ -193,33 +193,33 @@ const skus = new Map([
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
]
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_source_prices: {
|
||||
"775487223059316758": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
"736345864146255982": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
"683074999590060249": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"4": {
|
||||
country_prices: {
|
||||
@ -228,33 +228,33 @@ const skus = new Map([
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
]
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_source_prices: {
|
||||
"775487223059316758": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
"736345864146255982": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
"683074999590060249": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"1": {
|
||||
country_prices: {
|
||||
@ -263,39 +263,39 @@ const skus = new Map([
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
]
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_source_prices: {
|
||||
"775487223059316758": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
"736345864146255982": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
"683074999590060249": [
|
||||
{
|
||||
currency: "usd",
|
||||
amount: 0,
|
||||
exponent: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
exponent: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
|
@ -8,19 +8,20 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const { client } = Config.get();
|
||||
const platform = req.query.platform;
|
||||
|
||||
if (!platform) throw FieldErrors({
|
||||
platform: {
|
||||
code: "BASE_TYPE_REQUIRED",
|
||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||
}
|
||||
});
|
||||
if (!platform)
|
||||
throw FieldErrors({
|
||||
platform: {
|
||||
code: "BASE_TYPE_REQUIRED",
|
||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||
},
|
||||
});
|
||||
|
||||
const release = await Release.findOneOrFail({
|
||||
where: {
|
||||
enabled: true,
|
||||
platform: platform as string,
|
||||
},
|
||||
order: { pub_date: "DESC" }
|
||||
order: { pub_date: "DESC" },
|
||||
});
|
||||
|
||||
res.json({
|
||||
|
@ -89,79 +89,94 @@ router.get(
|
||||
bot: user.bot,
|
||||
};
|
||||
|
||||
const userProfile = {
|
||||
bio: req.user_bot ? null : user.bio,
|
||||
accent_color: user.accent_color,
|
||||
banner: user.banner,
|
||||
pronouns: user.pronouns,
|
||||
theme_colors: user.theme_colors,
|
||||
};
|
||||
const userProfile = {
|
||||
bio: req.user_bot ? null : user.bio,
|
||||
accent_color: user.accent_color,
|
||||
banner: user.banner,
|
||||
pronouns: user.pronouns,
|
||||
theme_colors: user.theme_colors,
|
||||
};
|
||||
|
||||
const guildMemberDto = guild_member
|
||||
? {
|
||||
avatar: guild_member.avatar,
|
||||
banner: guild_member.banner,
|
||||
bio: req.user_bot ? null : guild_member.bio,
|
||||
communication_disabled_until: guild_member.communication_disabled_until,
|
||||
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: guild_member.avatar,
|
||||
banner: guild_member.banner,
|
||||
bio: req.user_bot ? null : guild_member.bio,
|
||||
communication_disabled_until:
|
||||
guild_member.communication_disabled_until,
|
||||
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 guildMemberProfile = {
|
||||
accent_color: null,
|
||||
banner: guild_member?.banner || null,
|
||||
bio: guild_member?.bio || "",
|
||||
guild_id
|
||||
};
|
||||
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,
|
||||
premium_type: user.premium_type,
|
||||
profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
|
||||
user_profile: userProfile,
|
||||
guild_member: guild_id && guildMemberDto,
|
||||
guild_member_profile: guild_id && guildMemberProfile
|
||||
});
|
||||
});
|
||||
const guildMemberProfile = {
|
||||
accent_color: null,
|
||||
banner: guild_member?.banner || null,
|
||||
bio: guild_member?.bio || "",
|
||||
guild_id,
|
||||
};
|
||||
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,
|
||||
premium_type: user.premium_type,
|
||||
profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
|
||||
user_profile: userProfile,
|
||||
guild_member: guild_id && guildMemberDto,
|
||||
guild_member_profile: guild_id && guildMemberProfile,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
router.patch("/", route({ body: "UserProfileModifySchema" }), async (req: Request, res: Response) => {
|
||||
const body = req.body as UserProfileModifySchema;
|
||||
router.patch(
|
||||
"/",
|
||||
route({ body: "UserProfileModifySchema" }),
|
||||
async (req: Request, res: Response) => {
|
||||
const body = req.body as UserProfileModifySchema;
|
||||
|
||||
if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
|
||||
let user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
|
||||
if (body.banner)
|
||||
body.banner = await handleFile(
|
||||
`/banners/${req.user_id}`,
|
||||
body.banner as string,
|
||||
);
|
||||
let user = await User.findOneOrFail({
|
||||
where: { id: req.user_id },
|
||||
select: [...PrivateUserProjection, "data"],
|
||||
});
|
||||
|
||||
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({
|
||||
accent_color: user.accent_color,
|
||||
bio: user.bio,
|
||||
banner: user.banner,
|
||||
theme_colors: user.theme_colors,
|
||||
pronouns: user.pronouns,
|
||||
});
|
||||
});
|
||||
res.json({
|
||||
accent_color: user.accent_color,
|
||||
bio: user.bio,
|
||||
banner: user.banner,
|
||||
theme_colors: user.theme_colors,
|
||||
pronouns: user.pronouns,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -32,8 +32,7 @@ router.patch(
|
||||
|
||||
const user = await Member.findOneOrFail({
|
||||
where: { id: req.user_id, guild_id: req.params.guild_id },
|
||||
select: ["settings"]
|
||||
|
||||
select: ["settings"],
|
||||
});
|
||||
OrmUtils.mergeDeep(user.settings || {}, body);
|
||||
Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user);
|
||||
|
@ -98,7 +98,7 @@ router.patch(
|
||||
}
|
||||
user.data.hash = await bcrypt.hash(body.new_password, 12);
|
||||
user.data.valid_tokens_since = new Date();
|
||||
newToken = await generateToken(user.id) as string;
|
||||
newToken = (await generateToken(user.id)) as string;
|
||||
}
|
||||
|
||||
if (body.username) {
|
||||
|
@ -21,7 +21,7 @@ router.patch(
|
||||
|
||||
const user = await User.findOneOrFail({
|
||||
where: { id: req.user_id, bot: false },
|
||||
relations: ["settings"]
|
||||
relations: ["settings"],
|
||||
});
|
||||
|
||||
user.settings.assign(body);
|
||||
|
@ -53,7 +53,7 @@ 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,
|
||||
});
|
||||
|
||||
@ -180,7 +180,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
|
||||
// TODO: cache link result in db
|
||||
export async function postHandleMessage(message: Message) {
|
||||
const content = message.content?.replace(/ *\`[^)]*\` */g, ""); // remove markdown
|
||||
const content = message.content?.replace(/ *\`[^)]*\` */g, ""); // remove markdown
|
||||
var links = content?.match(LINK_REGEX);
|
||||
if (!links) return;
|
||||
|
||||
@ -201,8 +201,12 @@ export async function postHandleMessage(message: Message) {
|
||||
}
|
||||
|
||||
// bit gross, but whatever!
|
||||
const endpointPublic = Config.get().cdn.endpointPublic || "http://127.0.0.1"; // lol
|
||||
const handler = url.hostname == new URL(endpointPublic).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"];
|
||||
const endpointPublic =
|
||||
Config.get().cdn.endpointPublic || "http://127.0.0.1"; // lol
|
||||
const handler =
|
||||
url.hostname == new URL(endpointPublic).hostname
|
||||
? EmbedHandlers["self"]
|
||||
: EmbedHandlers[url.hostname] || EmbedHandlers["default"];
|
||||
|
||||
try {
|
||||
let res = await handler(url);
|
||||
@ -218,11 +222,10 @@ export async function postHandleMessage(message: Message) {
|
||||
cachePromises.push(cache.save());
|
||||
data.embeds.push(embed);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Sentry.captureException(e, scope => {
|
||||
} catch (e) {
|
||||
Sentry.captureException(e, (scope) => {
|
||||
scope.clear();
|
||||
scope.setContext("request", { url })
|
||||
scope.setContext("request", { url });
|
||||
return scope;
|
||||
});
|
||||
continue;
|
||||
@ -257,7 +260,7 @@ export async function sendMessage(opts: MessageOptions) {
|
||||
} 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;
|
||||
}
|
||||
|
@ -7,4 +7,4 @@ export * from "./handlers/route";
|
||||
export * from "./utility/String";
|
||||
export * from "./handlers/Voice";
|
||||
export * from "./utility/captcha";
|
||||
export * from "./utility/EmbedHandlers";
|
||||
export * from "./utility/EmbedHandlers";
|
||||
|
@ -16,8 +16,13 @@ export const DEFAULT_FETCH_OPTIONS: any = {
|
||||
method: "GET",
|
||||
};
|
||||
|
||||
export const getProxyUrl = (url: URL, width: number, height: number): string => {
|
||||
const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn;
|
||||
export const getProxyUrl = (
|
||||
url: URL,
|
||||
width: number,
|
||||
height: number,
|
||||
): string => {
|
||||
const { resizeWidthMax, resizeHeightMax, imagorServerUrl } =
|
||||
Config.get().cdn;
|
||||
const secret = Config.get().security.requestSignature;
|
||||
width = Math.min(width || 500, resizeWidthMax || width);
|
||||
height = Math.min(height || 500, resizeHeightMax || width);
|
||||
@ -26,16 +31,20 @@ export const getProxyUrl = (url: URL, width: number, height: number): string =>
|
||||
if (imagorServerUrl) {
|
||||
let path = `${width}x${height}/${url.host}${url.pathname}`;
|
||||
|
||||
const hash = crypto.createHmac('sha1', secret)
|
||||
const hash = crypto
|
||||
.createHmac("sha1", secret)
|
||||
.update(path)
|
||||
.digest('base64')
|
||||
.replace(/\+/g, '-').replace(/\//g, '_');
|
||||
.digest("base64")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_");
|
||||
|
||||
return `${imagorServerUrl}/${hash}/${path}`;
|
||||
}
|
||||
|
||||
// TODO: Imagor documentation
|
||||
console.log("Imagor has not been set up correctly. docs.fosscord.com/set/up/a/page/about/this");
|
||||
console.log(
|
||||
"Imagor has not been set up correctly. docs.fosscord.com/set/up/a/page/about/this",
|
||||
);
|
||||
return "";
|
||||
};
|
||||
|
||||
@ -69,8 +78,7 @@ const doFetch = async (url: URL) => {
|
||||
...DEFAULT_FETCH_OPTIONS,
|
||||
size: Config.get().limits.message.maxEmbedDownloadSize,
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@ -88,12 +96,10 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
|
||||
width = result.width;
|
||||
height = result.height;
|
||||
image = url.href;
|
||||
}
|
||||
else if (type.headers.get("content-type")?.indexOf("video") !== -1) {
|
||||
} else if (type.headers.get("content-type")?.indexOf("video") !== -1) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// have to download the page, unfortunately
|
||||
const response = await doFetch(url);
|
||||
if (!response) return null;
|
||||
@ -113,13 +119,15 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
|
||||
height: height,
|
||||
url: url.href,
|
||||
proxy_url: getProxyUrl(new URL(image), width, height),
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed[] | null>; } = {
|
||||
export const EmbedHandlers: {
|
||||
[key: string]: (url: URL) => Promise<Embed | Embed[] | null>;
|
||||
} = {
|
||||
// the url does not have a special handler
|
||||
"default": async (url: URL) => {
|
||||
default: async (url: URL) => {
|
||||
const type = await fetch(url, {
|
||||
...DEFAULT_FETCH_OPTIONS,
|
||||
method: "HEAD",
|
||||
@ -154,7 +162,13 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
width: metas.width,
|
||||
height: metas.height,
|
||||
url: metas.image,
|
||||
proxy_url: metas.image ? getProxyUrl(new URL(metas.image), metas.width!, metas.height!) : undefined,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(
|
||||
new URL(metas.image),
|
||||
metas.width!,
|
||||
metas.height!,
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
description: metas.description,
|
||||
};
|
||||
@ -169,26 +183,28 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
// TODO: facebook
|
||||
// have to use their APIs or something because they don't send the metas in initial html
|
||||
|
||||
"twitter.com": (url: URL) => { return EmbedHandlers["www.twitter.com"](url); },
|
||||
"twitter.com": (url: URL) => {
|
||||
return EmbedHandlers["www.twitter.com"](url);
|
||||
},
|
||||
"www.twitter.com": async (url: URL) => {
|
||||
const token = Config.get().external.twitter;
|
||||
if (!token) return null;
|
||||
|
||||
if (!url.href.includes("/status/")) return null; // TODO;
|
||||
const id = url.pathname.split("/")[3]; // super bad lol
|
||||
if (!url.href.includes("/status/")) return null; // TODO;
|
||||
const id = url.pathname.split("/")[3]; // super bad lol
|
||||
if (!parseInt(id)) return null;
|
||||
const endpointUrl = `https://api.twitter.com/2/tweets/${id}` +
|
||||
const endpointUrl =
|
||||
`https://api.twitter.com/2/tweets/${id}` +
|
||||
`?expansions=author_id,attachments.media_keys` +
|
||||
`&media.fields=url,width,height` +
|
||||
`&tweet.fields=created_at,public_metrics` +
|
||||
`&user.fields=profile_image_url`;
|
||||
|
||||
`&media.fields=url,width,height` +
|
||||
`&tweet.fields=created_at,public_metrics` +
|
||||
`&user.fields=profile_image_url`;
|
||||
|
||||
const response = await fetch(endpointUrl, {
|
||||
...DEFAULT_FETCH_OPTIONS,
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
}
|
||||
},
|
||||
});
|
||||
const json = await response.json();
|
||||
if (json.errors) return null;
|
||||
@ -196,7 +212,9 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
const text = json.data.text;
|
||||
const created_at = new Date(json.data.created_at);
|
||||
const metrics = json.data.public_metrics;
|
||||
let media = json.includes.media?.filter((x: any) => x.type == "photo") as any[]; // TODO: video
|
||||
let media = json.includes.media?.filter(
|
||||
(x: any) => x.type == "photo",
|
||||
) as any[]; // TODO: video
|
||||
|
||||
const embed: Embed = {
|
||||
type: EmbedType.rich,
|
||||
@ -205,19 +223,38 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
author: {
|
||||
url: `https://twitter.com/${author.username}`,
|
||||
name: `${author.name} (@${author.username})`,
|
||||
proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400),
|
||||
proxy_icon_url: getProxyUrl(
|
||||
new URL(author.profile_image_url),
|
||||
400,
|
||||
400,
|
||||
),
|
||||
icon_url: author.profile_image_url,
|
||||
},
|
||||
timestamp: created_at,
|
||||
fields: [
|
||||
{ inline: true, name: "Likes", value: metrics.like_count.toString() },
|
||||
{ inline: true, name: "Retweet", value: metrics.retweet_count.toString() },
|
||||
{
|
||||
inline: true,
|
||||
name: "Likes",
|
||||
value: metrics.like_count.toString(),
|
||||
},
|
||||
{
|
||||
inline: true,
|
||||
name: "Retweet",
|
||||
value: metrics.retweet_count.toString(),
|
||||
},
|
||||
],
|
||||
color: 1942002,
|
||||
footer: {
|
||||
text: "Twitter",
|
||||
proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192),
|
||||
icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png"
|
||||
proxy_icon_url: getProxyUrl(
|
||||
new URL(
|
||||
"https://abs.twimg.com/icons/apple-touch-icon-192x192.png",
|
||||
),
|
||||
192,
|
||||
192,
|
||||
),
|
||||
icon_url:
|
||||
"https://abs.twimg.com/icons/apple-touch-icon-192x192.png",
|
||||
},
|
||||
// Discord doesn't send this?
|
||||
// provider: {
|
||||
@ -231,7 +268,11 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
width: media[0].width,
|
||||
height: media[0].height,
|
||||
url: media[0].url,
|
||||
proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height)
|
||||
proxy_url: getProxyUrl(
|
||||
new URL(media[0].url),
|
||||
media[0].width,
|
||||
media[0].height,
|
||||
),
|
||||
};
|
||||
media.shift();
|
||||
}
|
||||
@ -265,17 +306,21 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
thumbnail: {
|
||||
width: 640,
|
||||
height: 640,
|
||||
proxy_url: metas.image ? getProxyUrl(new URL(metas.image!), 640, 640) : undefined,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(new URL(metas.image!), 640, 640)
|
||||
: undefined,
|
||||
url: metas.image,
|
||||
},
|
||||
provider: {
|
||||
url: "https://spotify.com",
|
||||
name: "Spotify",
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
"pixiv.net": (url: URL) => { return EmbedHandlers["www.pixiv.net"](url); },
|
||||
"pixiv.net": (url: URL) => {
|
||||
return EmbedHandlers["www.pixiv.net"](url);
|
||||
},
|
||||
"www.pixiv.net": async (url: URL) => {
|
||||
const response = await doFetch(url);
|
||||
if (!response) return null;
|
||||
@ -291,12 +336,18 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
width: metas.width,
|
||||
height: metas.height,
|
||||
url: url.href,
|
||||
proxy_url: metas.image ? getProxyUrl(new URL(metas.image!), metas.width!, metas.height!) : undefined,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(
|
||||
new URL(metas.image!),
|
||||
metas.width!,
|
||||
metas.height!,
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
provider: {
|
||||
url: "https://pixiv.net",
|
||||
name: "Pixiv"
|
||||
}
|
||||
name: "Pixiv",
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@ -310,35 +361,42 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
type: EmbedType.rich,
|
||||
title: metas.title,
|
||||
description: metas.description,
|
||||
image: { // TODO: meant to be thumbnail.
|
||||
image: {
|
||||
// TODO: meant to be thumbnail.
|
||||
// isn't this standard across all of steam?
|
||||
width: 460,
|
||||
height: 215,
|
||||
url: metas.image,
|
||||
proxy_url: metas.image ? getProxyUrl(new URL(metas.image!), 460, 215) : undefined,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(new URL(metas.image!), 460, 215)
|
||||
: undefined,
|
||||
},
|
||||
provider: {
|
||||
url: "https://store.steampowered.com",
|
||||
name: "Steam"
|
||||
name: "Steam",
|
||||
},
|
||||
// TODO: fields for release date
|
||||
// TODO: Video
|
||||
};
|
||||
},
|
||||
|
||||
"reddit.com": (url: URL) => { return EmbedHandlers["www.reddit.com"](url); },
|
||||
"reddit.com": (url: URL) => {
|
||||
return EmbedHandlers["www.reddit.com"](url);
|
||||
},
|
||||
"www.reddit.com": async (url: URL) => {
|
||||
const res = await EmbedHandlers["default"](url);
|
||||
return {
|
||||
...res,
|
||||
color: 16777215,
|
||||
provider: {
|
||||
name: "reddit"
|
||||
}
|
||||
name: "reddit",
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
"youtube.com": (url: URL) => { return EmbedHandlers["www.youtube.com"](url); },
|
||||
"youtube.com": (url: URL) => {
|
||||
return EmbedHandlers["www.youtube.com"](url);
|
||||
},
|
||||
"www.youtube.com": async (url: URL): Promise<Embed | null> => {
|
||||
const response = await doFetch(url);
|
||||
if (!response) return null;
|
||||
@ -358,7 +416,13 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
width: metas.width,
|
||||
height: metas.height,
|
||||
url: metas.image,
|
||||
proxy_url: metas.image ? getProxyUrl(new URL(metas.image!), metas.width!, metas.height!) : undefined,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(
|
||||
new URL(metas.image!),
|
||||
metas.width!,
|
||||
metas.height!,
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
provider: {
|
||||
url: "https://www.youtube.com",
|
||||
@ -369,12 +433,12 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
author: {
|
||||
name: metas.author,
|
||||
// TODO: author channel url
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// the url is an image from this instance
|
||||
"self": async (url: URL): Promise<Embed | null> => {
|
||||
self: async (url: URL): Promise<Embed | null> => {
|
||||
const result = await probe(url.href);
|
||||
|
||||
return {
|
||||
@ -385,7 +449,7 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
||||
height: result.height,
|
||||
url: url.href,
|
||||
proxy_url: url.href,
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};;
|
||||
};
|
||||
|
@ -75,8 +75,14 @@ async function main() {
|
||||
// Filter breadcrumbs that we don't care about
|
||||
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;
|
||||
});
|
||||
|
@ -56,7 +56,7 @@ export class CDNServer extends Server {
|
||||
|
||||
this.app.use("/splashes/", avatarsRoute);
|
||||
this.log("verbose", "[Server] Route /splashes registered");
|
||||
|
||||
|
||||
this.app.use("/discovery-splashes/", avatarsRoute);
|
||||
this.log("verbose", "[Server] Route /discovery-splashes registered");
|
||||
|
||||
@ -75,10 +75,16 @@ export class CDNServer extends Server {
|
||||
this.app.use("/channel-icons/", avatarsRoute);
|
||||
this.log("verbose", "[Server] Route /channel-icons registered");
|
||||
|
||||
this.app.use("/guilds/:guild_id/users/:user_id/avatars", guildProfilesRoute);
|
||||
this.app.use(
|
||||
"/guilds/:guild_id/users/:user_id/avatars",
|
||||
guildProfilesRoute,
|
||||
);
|
||||
this.log("verbose", "[Server] Route /guilds/avatars registered");
|
||||
|
||||
this.app.use("/guilds/:guild_id/users/:user_id/banners", guildProfilesRoute);
|
||||
this.app.use(
|
||||
"/guilds/:guild_id/users/:user_id/banners",
|
||||
guildProfilesRoute,
|
||||
);
|
||||
this.log("verbose", "[Server] Route /guilds/banners registered");
|
||||
|
||||
return super.start();
|
||||
|
@ -12,21 +12,32 @@ import { storage } from "../util/Storage";
|
||||
// TODO: delete old icons
|
||||
|
||||
const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"];
|
||||
const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"];
|
||||
const STATIC_MIME_TYPES = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/svg",
|
||||
];
|
||||
const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES];
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post("/", multer.single("file"), async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
|
||||
if (req.headers.signature !== Config.get().security.requestSignature)
|
||||
throw new HTTPError("Invalid request signature");
|
||||
if (!req.file) throw new HTTPError("Missing file");
|
||||
const { buffer, mimetype, size, originalname, fieldname } = req.file;
|
||||
const { guild_id, user_id } = req.params;
|
||||
|
||||
let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex");
|
||||
let hash = crypto
|
||||
.createHash("md5")
|
||||
.update(Snowflake.generate())
|
||||
.digest("hex");
|
||||
|
||||
const type = await FileType.fromBuffer(buffer);
|
||||
if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type");
|
||||
if (!type || !ALLOWED_MIME_TYPES.includes(type.mime))
|
||||
throw new HTTPError("Invalid file type");
|
||||
if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash
|
||||
|
||||
const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`;
|
||||
@ -38,7 +49,7 @@ router.post("/", multer.single("file"), async (req: Request, res: Response) => {
|
||||
id: hash,
|
||||
content_type: type.mime,
|
||||
size,
|
||||
url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`
|
||||
url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`,
|
||||
});
|
||||
});
|
||||
|
||||
@ -73,7 +84,8 @@ router.get("/:hash", async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
router.delete("/:id", async (req: Request, res: Response) => {
|
||||
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
|
||||
if (req.headers.signature !== Config.get().security.requestSignature)
|
||||
throw new HTTPError("Invalid request signature");
|
||||
const { guild_id, user_id, id } = req.params;
|
||||
const path = `guilds/${guild_id}/users/${user_id}/avatars/${id}`;
|
||||
|
||||
|
@ -35,7 +35,8 @@ export class FileStorage implements Storage {
|
||||
|
||||
async set(path: string, value: any) {
|
||||
path = getPath(path);
|
||||
if (!fs.existsSync(dirname(path))) fs.mkdirSync(dirname(path), { recursive: true });
|
||||
if (!fs.existsSync(dirname(path)))
|
||||
fs.mkdirSync(dirname(path), { recursive: true });
|
||||
|
||||
value = Readable.from(value);
|
||||
const cleaned_file = fs.createWriteStream(path);
|
||||
|
@ -12,7 +12,7 @@ import { Config } from "@fosscord/util";
|
||||
var erlpack: any;
|
||||
try {
|
||||
erlpack = require("@yukikaze-bot/erlpack");
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
|
||||
// TODO: check rate limit
|
||||
// TODO: specify rate limit in config
|
||||
@ -48,7 +48,7 @@ export async function Connection(
|
||||
"open",
|
||||
"ping",
|
||||
"pong",
|
||||
"unexpected-response"
|
||||
"unexpected-response",
|
||||
].forEach((x) => {
|
||||
socket.on(x, (y) => console.log(x, y));
|
||||
});
|
||||
|
@ -10,30 +10,36 @@ const bigIntJson = BigIntJson({ storeAsString: true });
|
||||
var erlpack: any;
|
||||
try {
|
||||
erlpack = require("@yukikaze-bot/erlpack");
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
|
||||
export async function Message(this: WebSocket, buffer: WS.Data) {
|
||||
// TODO: compression
|
||||
var data: Payload;
|
||||
|
||||
if ((buffer instanceof Buffer && buffer[0] === 123) || // ASCII 123 = `{`. Bad check for JSON
|
||||
(typeof buffer === "string")) {
|
||||
if (
|
||||
(buffer instanceof Buffer && buffer[0] === 123) || // ASCII 123 = `{`. Bad check for JSON
|
||||
typeof buffer === "string"
|
||||
) {
|
||||
data = bigIntJson.parse(buffer.toString());
|
||||
}
|
||||
else if (this.encoding === "json" && buffer instanceof Buffer) {
|
||||
} else if (this.encoding === "json" && buffer instanceof Buffer) {
|
||||
if (this.inflate) {
|
||||
try { buffer = this.inflate.process(buffer) as any; }
|
||||
catch { buffer = buffer.toString() as any; }
|
||||
try {
|
||||
buffer = this.inflate.process(buffer) as any;
|
||||
} catch {
|
||||
buffer = buffer.toString() as any;
|
||||
}
|
||||
}
|
||||
data = bigIntJson.parse(buffer as string);
|
||||
}
|
||||
else if (this.encoding === "etf" && buffer instanceof Buffer) {
|
||||
try { data = erlpack.unpack(buffer); }
|
||||
catch { return this.close(CLOSECODES.Decode_error); }
|
||||
}
|
||||
else return this.close(CLOSECODES.Decode_error);
|
||||
} else if (this.encoding === "etf" && buffer instanceof Buffer) {
|
||||
try {
|
||||
data = erlpack.unpack(buffer);
|
||||
} catch {
|
||||
return this.close(CLOSECODES.Decode_error);
|
||||
}
|
||||
} else return this.close(CLOSECODES.Decode_error);
|
||||
|
||||
if (process.env.WS_VERBOSE) console.log(`[Websocket] Incomming message: ${JSON.stringify(data)}`);
|
||||
if (process.env.WS_VERBOSE)
|
||||
console.log(`[Websocket] Incomming message: ${JSON.stringify(data)}`);
|
||||
|
||||
check.call(this, PayloadSchema, data);
|
||||
|
||||
@ -46,14 +52,17 @@ export async function Message(this: WebSocket, buffer: WS.Data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const transaction = data.op != 1 ? Sentry.startTransaction({
|
||||
op: OPCODES[data.op],
|
||||
name: `GATEWAY ${OPCODES[data.op]}`,
|
||||
data: {
|
||||
...data.d,
|
||||
token: data?.d?.token ? "[Redacted]" : undefined,
|
||||
},
|
||||
}) : undefined;
|
||||
const transaction =
|
||||
data.op != 1
|
||||
? Sentry.startTransaction({
|
||||
op: OPCODES[data.op],
|
||||
name: `GATEWAY ${OPCODES[data.op]}`,
|
||||
data: {
|
||||
...data.d,
|
||||
token: data?.d?.token ? "[Redacted]" : undefined,
|
||||
},
|
||||
})
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
var ret = await OPCodeHandler.call(this, data);
|
||||
|
@ -259,7 +259,10 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
||||
|
||||
const d: ReadyEventData = {
|
||||
v: 9,
|
||||
application: { id: application?.id ?? '', flags: application?.flags ?? 0 }, //TODO: check this code!
|
||||
application: {
|
||||
id: application?.id ?? "",
|
||||
flags: application?.flags ?? 0,
|
||||
}, //TODO: check this code!
|
||||
user: privateUser,
|
||||
user_settings: user.settings,
|
||||
// @ts-ignore
|
||||
@ -267,7 +270,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
||||
return {
|
||||
...new ReadyGuildDTO(x as Guild & { joined_at: Date }).toJSON(),
|
||||
guild_hashes: {},
|
||||
joined_at: x.joined_at
|
||||
joined_at: x.joined_at,
|
||||
};
|
||||
}),
|
||||
guild_experiments: [], // TODO
|
||||
|
@ -159,7 +159,11 @@ async function getMembers(guild_id: string, range: [number, number]) {
|
||||
groups,
|
||||
range,
|
||||
members: items
|
||||
.map((x) => ("member" in x ? { ...x.member, settings: undefined } : undefined))
|
||||
.map((x) =>
|
||||
"member" in x
|
||||
? { ...x.member, settings: undefined }
|
||||
: undefined,
|
||||
)
|
||||
.filter((x) => !!x),
|
||||
};
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
|
||||
}
|
||||
|
||||
// 'Fix' for this one voice state error. TODO: Find out why this is sent
|
||||
// It seems to be sent on client load,
|
||||
// It seems to be sent on client load,
|
||||
// so maybe its trying to find which server you were connected to before disconnecting, if any?
|
||||
if (body.guild_id == null) {
|
||||
return;
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
RegisterConfiguration,
|
||||
SecurityConfiguration,
|
||||
SentryConfiguration,
|
||||
TemplateConfiguration
|
||||
TemplateConfiguration,
|
||||
} from "../config";
|
||||
|
||||
export class ConfigValue {
|
||||
@ -40,4 +40,4 @@ export class ConfigValue {
|
||||
sentry: SentryConfiguration = new SentryConfiguration();
|
||||
defaults: DefaultsConfiguration = new DefaultsConfiguration();
|
||||
external: ExternalTokensConfiguration = new ExternalTokensConfiguration();
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,4 @@ export class ApiConfiguration {
|
||||
defaultVersion: string = "9";
|
||||
activeVersions: string[] = ["6", "7", "8", "9"];
|
||||
useFosscordEnhancements: boolean = true;
|
||||
}
|
||||
}
|
||||
|
@ -7,4 +7,4 @@ export class CdnConfiguration extends EndpointConfiguration {
|
||||
|
||||
endpointPublic: string | null = "http://localhost:3001";
|
||||
endpointPrivate: string | null = "http://localhost:3001";
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ClientReleaseConfiguration } from ".";
|
||||
|
||||
export class ClientConfiguration {
|
||||
releases: ClientReleaseConfiguration = new ClientReleaseConfiguration();
|
||||
useTestClient: boolean = false;
|
||||
}
|
||||
releases: ClientReleaseConfiguration = new ClientReleaseConfiguration();
|
||||
useTestClient: boolean = false;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { GuildDefaults, UserDefaults } from ".";
|
||||
|
||||
export class DefaultsConfiguration {
|
||||
guild: GuildDefaults = new GuildDefaults();
|
||||
user: UserDefaults = new UserDefaults();
|
||||
}
|
||||
guild: GuildDefaults = new GuildDefaults();
|
||||
user: UserDefaults = new UserDefaults();
|
||||
}
|
||||
|
@ -2,4 +2,4 @@ export class EndpointConfiguration {
|
||||
endpointClient: string | null = null;
|
||||
endpointPrivate: string | null = null;
|
||||
endpointPublic: string | null = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
export class ExternalTokensConfiguration {
|
||||
twitter: string | null = null;
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ import { Snowflake } from "@fosscord/util";
|
||||
|
||||
export class GeneralConfiguration {
|
||||
instanceName: string = "Fosscord Instance";
|
||||
instanceDescription: string | null = "This is a Fosscord instance made in the pre-release days";
|
||||
instanceDescription: string | null =
|
||||
"This is a Fosscord instance made in the pre-release days";
|
||||
frontPage: string | null = null;
|
||||
tosPage: string | null = null;
|
||||
correspondenceEmail: string | null = null;
|
||||
correspondenceUserID: string | null = null;
|
||||
image: string | null = null;
|
||||
instanceId: string = Snowflake.generate();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export class GifConfiguration {
|
||||
enabled: boolean = true;
|
||||
provider: "tenor" = "tenor"; // more coming soon
|
||||
apiKey?: string = "LIVDSRZULELA";
|
||||
}
|
||||
enabled: boolean = true;
|
||||
provider: "tenor" = "tenor"; // more coming soon
|
||||
apiKey?: string = "LIVDSRZULELA";
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DiscoveryConfiguration, AutoJoinConfiguration } from ".";
|
||||
|
||||
export class GuildConfiguration {
|
||||
discovery: DiscoveryConfiguration = new DiscoveryConfiguration();
|
||||
autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration();
|
||||
discovery: DiscoveryConfiguration = new DiscoveryConfiguration();
|
||||
autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration();
|
||||
defaultFeatures: string[] = [];
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { KafkaBroker } from ".";
|
||||
|
||||
export class KafkaConfiguration {
|
||||
brokers: KafkaBroker[] | null = null;
|
||||
}
|
||||
brokers: KafkaBroker[] | null = null;
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { ChannelLimits, GlobalRateLimits, GuildLimits, MessageLimits, RateLimits, UserLimits } from ".";
|
||||
import {
|
||||
ChannelLimits,
|
||||
GlobalRateLimits,
|
||||
GuildLimits,
|
||||
MessageLimits,
|
||||
RateLimits,
|
||||
UserLimits,
|
||||
} from ".";
|
||||
|
||||
export class LimitsConfiguration {
|
||||
user: UserLimits = new UserLimits();
|
||||
|
@ -1,3 +1,3 @@
|
||||
export class LoginConfiguration {
|
||||
requireCaptcha: boolean = false;
|
||||
}
|
||||
requireCaptcha: boolean = false;
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
export class MetricsConfiguration {
|
||||
timeout: number = 30000;
|
||||
}
|
||||
timeout: number = 30000;
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
export class RabbitMQConfiguration {
|
||||
host: string | null = null;
|
||||
}
|
||||
host: string | null = null;
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { Region } from ".";
|
||||
|
||||
export class RegionConfiguration {
|
||||
default: string = "fosscord";
|
||||
useDefaultAsOptimal: boolean = true;
|
||||
available: Region[] = [
|
||||
{
|
||||
id: "fosscord",
|
||||
name: "Fosscord",
|
||||
endpoint: "127.0.0.1:3004",
|
||||
vip: false,
|
||||
custom: false,
|
||||
deprecated: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
default: string = "fosscord";
|
||||
useDefaultAsOptimal: boolean = true;
|
||||
available: Region[] = [
|
||||
{
|
||||
id: "fosscord",
|
||||
name: "Fosscord",
|
||||
endpoint: "127.0.0.1:3004",
|
||||
vip: false,
|
||||
custom: false,
|
||||
deprecated: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -1,16 +1,20 @@
|
||||
import { DateOfBirthConfiguration, EmailConfiguration, PasswordConfiguration } from ".";
|
||||
import {
|
||||
DateOfBirthConfiguration,
|
||||
EmailConfiguration,
|
||||
PasswordConfiguration,
|
||||
} from ".";
|
||||
|
||||
export class RegisterConfiguration {
|
||||
email: EmailConfiguration = new EmailConfiguration();
|
||||
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
|
||||
password: PasswordConfiguration = new PasswordConfiguration();
|
||||
disabled: boolean = false;
|
||||
requireCaptcha: boolean = true;
|
||||
requireInvite: boolean = false;
|
||||
guestsRequireInvite: boolean = true;
|
||||
allowNewRegistration: boolean = true;
|
||||
allowMultipleAccounts: boolean = true;
|
||||
blockProxies: boolean = true;
|
||||
incrementingDiscriminators: boolean = false; // random otherwise
|
||||
defaultRights: string = "30644591655940"; // See `npm run generate:rights`
|
||||
email: EmailConfiguration = new EmailConfiguration();
|
||||
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
|
||||
password: PasswordConfiguration = new PasswordConfiguration();
|
||||
disabled: boolean = false;
|
||||
requireCaptcha: boolean = true;
|
||||
requireInvite: boolean = false;
|
||||
guestsRequireInvite: boolean = true;
|
||||
allowNewRegistration: boolean = true;
|
||||
allowMultipleAccounts: boolean = true;
|
||||
blockProxies: boolean = true;
|
||||
incrementingDiscriminators: boolean = false; // random otherwise
|
||||
defaultRights: string = "30644591655940"; // See `npm run generate:rights`
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ export class SecurityConfiguration {
|
||||
// X-Forwarded-For for nginx/reverse proxies
|
||||
// CF-Connecting-IP for cloudflare
|
||||
forwadedFor: string | null = null;
|
||||
ipdataApiKey: string | null = "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9";
|
||||
ipdataApiKey: string | null =
|
||||
"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9";
|
||||
mfaBackupCodeCount: number = 10;
|
||||
statsWorldReadable: boolean = true;
|
||||
defaultRegistrationTokenExpiration: number = 1000 * 60 * 60 * 24 * 7; //1 week
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { hostname } from "os";
|
||||
|
||||
export class SentryConfiguration {
|
||||
enabled: boolean = false;
|
||||
endpoint: string = "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6";
|
||||
traceSampleRate: number = 1.0;
|
||||
environment: string = hostname();
|
||||
}
|
||||
enabled: boolean = false;
|
||||
endpoint: string =
|
||||
"https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6";
|
||||
traceSampleRate: number = 1.0;
|
||||
environment: string = hostname();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
export class TemplateConfiguration {
|
||||
enabled: boolean = true;
|
||||
allowTemplateCreation: boolean = true;
|
||||
allowDiscordTemplates: boolean = true;
|
||||
allowRaws: boolean = true;
|
||||
}
|
||||
enabled: boolean = true;
|
||||
allowTemplateCreation: boolean = true;
|
||||
allowDiscordTemplates: boolean = true;
|
||||
allowRaws: boolean = true;
|
||||
}
|
||||
|
@ -17,4 +17,4 @@ export * from "./RegisterConfiguration";
|
||||
export * from "./SecurityConfiguration";
|
||||
export * from "./SentryConfiguration";
|
||||
export * from "./TemplateConfiguration";
|
||||
export * from "./subconfigurations";
|
||||
export * from "./subconfigurations";
|
||||
|
@ -1,4 +1,4 @@
|
||||
export class ClientReleaseConfiguration {
|
||||
useLocalRelease: boolean = true; //TODO
|
||||
upstreamVersion: string = "0.0.264";
|
||||
}
|
||||
useLocalRelease: boolean = true; //TODO
|
||||
upstreamVersion: string = "0.0.264";
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
export class GuildDefaults {
|
||||
maxPresences: number = 250000;
|
||||
maxVideoChannelUsers: number = 200;
|
||||
afkTimeout: number = 300;
|
||||
defaultMessageNotifications: number = 1;
|
||||
explicitContentFilter: number = 0;
|
||||
}
|
||||
maxPresences: number = 250000;
|
||||
maxVideoChannelUsers: number = 200;
|
||||
afkTimeout: number = 300;
|
||||
defaultMessageNotifications: number = 1;
|
||||
explicitContentFilter: number = 0;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export class UserDefaults {
|
||||
premium: boolean = true;
|
||||
premiumType: number = 2;
|
||||
verified: boolean = true;
|
||||
}
|
||||
premium: boolean = true;
|
||||
premiumType: number = 2;
|
||||
verified: boolean = true;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export class AutoJoinConfiguration {
|
||||
enabled: boolean = true;
|
||||
guilds: string[] = [];
|
||||
canLeave: boolean = true;
|
||||
}
|
||||
enabled: boolean = true;
|
||||
guilds: string[] = [];
|
||||
canLeave: boolean = true;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
export class DiscoveryConfiguration {
|
||||
showAllGuilds: boolean = false;
|
||||
useRecommendation: boolean = false; // TODO: Recommendation, privacy concern?
|
||||
offset: number = 0;
|
||||
limit: number = 24;
|
||||
}
|
||||
showAllGuilds: boolean = false;
|
||||
useRecommendation: boolean = false; // TODO: Recommendation, privacy concern?
|
||||
offset: number = 0;
|
||||
limit: number = 24;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export interface KafkaBroker {
|
||||
ip: string;
|
||||
port: number;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export class ChannelLimits {
|
||||
maxPins: number = 500;
|
||||
maxTopic: number = 1024;
|
||||
maxWebhooks: number = 100;
|
||||
}
|
||||
maxPins: number = 500;
|
||||
maxTopic: number = 1024;
|
||||
maxWebhooks: number = 100;
|
||||
}
|
||||
|
@ -1,6 +1,14 @@
|
||||
export class GlobalRateLimits {
|
||||
register: GlobalRateLimit = { limit: 25, window: 60 * 60 * 1000, enabled: true };
|
||||
sendMessage: GlobalRateLimit = { limit: 200, window: 60 * 1000, enabled: true };
|
||||
register: GlobalRateLimit = {
|
||||
limit: 25,
|
||||
window: 60 * 60 * 1000,
|
||||
enabled: true,
|
||||
};
|
||||
sendMessage: GlobalRateLimit = {
|
||||
limit: 200,
|
||||
window: 60 * 1000,
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
export class GlobalRateLimit {
|
||||
|
@ -1,7 +1,7 @@
|
||||
export class GuildLimits {
|
||||
maxRoles: number = 1000;
|
||||
maxEmojis: number = 2000;
|
||||
maxMembers: number = 25000000;
|
||||
maxChannels: number = 65535;
|
||||
maxChannelsInCategory: number = 65535;
|
||||
}
|
||||
maxRoles: number = 1000;
|
||||
maxEmojis: number = 2000;
|
||||
maxMembers: number = 25000000;
|
||||
maxChannels: number = 65535;
|
||||
maxChannelsInCategory: number = 65535;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
export class MessageLimits {
|
||||
maxCharacters: number = 1048576;
|
||||
maxTTSCharacters: number = 160;
|
||||
maxReactions: number = 2048;
|
||||
maxAttachmentSize: number = 1024 * 1024 * 1024;
|
||||
maxBulkDelete: number = 1000;
|
||||
maxEmbedDownloadSize: number = 1024 * 1024 * 5;
|
||||
}
|
||||
maxCharacters: number = 1048576;
|
||||
maxTTSCharacters: number = 160;
|
||||
maxReactions: number = 2048;
|
||||
maxAttachmentSize: number = 1024 * 1024 * 1024;
|
||||
maxBulkDelete: number = 1000;
|
||||
maxEmbedDownloadSize: number = 1024 * 1024 * 5;
|
||||
}
|
||||
|
@ -4,15 +4,15 @@ export class RateLimits {
|
||||
enabled: boolean = false;
|
||||
ip: Omit<RateLimitOptions, "bot_count"> = {
|
||||
count: 500,
|
||||
window: 5
|
||||
window: 5,
|
||||
};
|
||||
global: RateLimitOptions = {
|
||||
count: 250,
|
||||
window: 5
|
||||
window: 5,
|
||||
};
|
||||
error: RateLimitOptions = {
|
||||
count: 10,
|
||||
window: 5
|
||||
window: 5,
|
||||
};
|
||||
routes: RouteRateLimit = new RouteRateLimit();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export class UserLimits {
|
||||
maxGuilds: number = 1048576;
|
||||
maxUsername: number = 127;
|
||||
maxFriends: number = 5000;
|
||||
}
|
||||
maxGuilds: number = 1048576;
|
||||
maxUsername: number = 127;
|
||||
maxFriends: number = 5000;
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { RateLimitOptions } from "./RateLimitOptions";
|
||||
|
||||
export class AuthRateLimit {
|
||||
login: RateLimitOptions = {
|
||||
count: 5,
|
||||
window: 60
|
||||
};
|
||||
register: RateLimitOptions = {
|
||||
count: 2,
|
||||
window: 60 * 60 * 12
|
||||
};
|
||||
}
|
||||
login: RateLimitOptions = {
|
||||
count: 5,
|
||||
window: 60,
|
||||
};
|
||||
register: RateLimitOptions = {
|
||||
count: 2,
|
||||
window: 60 * 60 * 12,
|
||||
};
|
||||
}
|
||||
|
@ -3,4 +3,4 @@ export interface RateLimitOptions {
|
||||
count: number;
|
||||
window: number;
|
||||
onyIp?: boolean;
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,15 @@ import { RateLimitOptions } from "./RateLimitOptions";
|
||||
export class RouteRateLimit {
|
||||
guild: RateLimitOptions = {
|
||||
count: 5,
|
||||
window: 5
|
||||
window: 5,
|
||||
};
|
||||
webhook: RateLimitOptions = {
|
||||
count: 10,
|
||||
window: 5
|
||||
window: 5,
|
||||
};
|
||||
channel: RateLimitOptions = {
|
||||
count: 10,
|
||||
window: 5
|
||||
window: 5,
|
||||
};
|
||||
auth: AuthRateLimit = new AuthRateLimit();
|
||||
// TODO: rate limit configuration for all routes
|
||||
|
@ -9,4 +9,4 @@ export interface Region {
|
||||
vip: boolean;
|
||||
custom: boolean;
|
||||
deprecated: boolean;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export class DateOfBirthConfiguration {
|
||||
required: boolean = true;
|
||||
minimum: number = 13; // in years
|
||||
}
|
||||
required: boolean = true;
|
||||
minimum: number = 13; // in years
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
export class EmailConfiguration {
|
||||
required: boolean = false;
|
||||
allowlist: boolean = false;
|
||||
blocklist: boolean = true;
|
||||
domains: string[] = [];// TODO: efficiently save domain blocklist in database
|
||||
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
|
||||
}
|
||||
required: boolean = false;
|
||||
allowlist: boolean = false;
|
||||
blocklist: boolean = true;
|
||||
domains: string[] = []; // TODO: efficiently save domain blocklist in database
|
||||
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
export class PasswordConfiguration {
|
||||
required: boolean = false;
|
||||
minLength: number = 8;
|
||||
minNumbers: number = 2;
|
||||
minUpperCase: number =2;
|
||||
minSymbols: number = 0;
|
||||
}
|
||||
required: boolean = false;
|
||||
minLength: number = 8;
|
||||
minNumbers: number = 2;
|
||||
minUpperCase: number = 2;
|
||||
minSymbols: number = 0;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
export class CaptchaConfiguration {
|
||||
enabled: boolean = false;
|
||||
service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom
|
||||
sitekey: string | null = null;
|
||||
secret: string | null = null;
|
||||
}
|
||||
enabled: boolean = false;
|
||||
service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom
|
||||
sitekey: string | null = null;
|
||||
secret: string | null = null;
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
export class TwoFactorConfiguration {
|
||||
generateBackupCodes: boolean = true;
|
||||
}
|
||||
generateBackupCodes: boolean = true;
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, RelationId } from "typeorm";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
RelationId,
|
||||
} from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { Team } from "./Team";
|
||||
@ -8,78 +15,78 @@ import { User } from "./User";
|
||||
export class Application extends BaseClass {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
description: string;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
summary: string = "";
|
||||
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
type?: any;
|
||||
|
||||
|
||||
@Column()
|
||||
hook: boolean = true;
|
||||
|
||||
|
||||
@Column()
|
||||
bot_public?: boolean = true;
|
||||
|
||||
|
||||
@Column()
|
||||
bot_require_code_grant?: boolean = false;
|
||||
|
||||
|
||||
@Column()
|
||||
verify_key: string;
|
||||
|
||||
|
||||
@JoinColumn({ name: "owner_id" })
|
||||
@ManyToOne(() => User)
|
||||
owner: User;
|
||||
|
||||
|
||||
// TODO: enum this? https://discord.com/developers/docs/resources/application#application-object-application-flags
|
||||
@Column()
|
||||
flags: number = 0;
|
||||
|
||||
|
||||
@Column({ type: "simple-array", nullable: true })
|
||||
redirect_uris: string[] = [];
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
rpc_application_state: number = 0;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
store_application_state: number = 1;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
verification_state: number = 1;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
interactions_endpoint_url?: string;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
integration_public: boolean = true;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
integration_require_code_grant: boolean = false;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
discoverability_state: number = 1;
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
discovery_eligibility_flags: number = 2240;
|
||||
|
||||
|
||||
@JoinColumn({ name: "bot_user_id" })
|
||||
@OneToOne(() => User)
|
||||
bot?: User;
|
||||
|
||||
|
||||
@Column({ type: "simple-array", nullable: true })
|
||||
tags?: string[];
|
||||
|
||||
|
||||
@Column({ nullable: true })
|
||||
cover_image?: string; // the application's default rich presence invite cover image hash
|
||||
|
||||
|
||||
@Column({ type: "simple-json", nullable: true })
|
||||
install_params?: {scopes: string[], permissions: string};
|
||||
install_params?: { scopes: string[]; permissions: string };
|
||||
|
||||
@Column({ nullable: true })
|
||||
terms_of_service_url?: string;
|
||||
@ -91,7 +98,7 @@ export class Application extends BaseClass {
|
||||
|
||||
//@Column({ type: "simple-array", nullable: true })
|
||||
//rpc_origins?: string[];
|
||||
|
||||
|
||||
//@JoinColumn({ name: "guild_id" })
|
||||
//@ManyToOne(() => Guild)
|
||||
//guild?: Guild; // if this application is a game sold, this field will be the guild to which it has been linked
|
||||
@ -105,11 +112,10 @@ export class Application extends BaseClass {
|
||||
@JoinColumn({ name: "team_id" })
|
||||
@ManyToOne(() => Team, {
|
||||
onDelete: "CASCADE",
|
||||
nullable: true
|
||||
nullable: true,
|
||||
})
|
||||
team?: Team;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export interface ApplicationCommand {
|
||||
id: string;
|
||||
|
@ -209,7 +209,10 @@ export class Channel extends BaseClass {
|
||||
);
|
||||
|
||||
// Categories skip these checks on discord.com
|
||||
if (channel.type !== ChannelType.GUILD_CATEGORY || guild.features.includes("IRC_LIKE_CATEGORY_NAMES")) {
|
||||
if (
|
||||
channel.type !== ChannelType.GUILD_CATEGORY ||
|
||||
guild.features.includes("IRC_LIKE_CATEGORY_NAMES")
|
||||
) {
|
||||
if (channel.name.includes(" "))
|
||||
throw new HTTPError(
|
||||
"Channel name cannot include invalid characters",
|
||||
@ -286,10 +289,10 @@ export class Channel extends BaseClass {
|
||||
Channel.create(channel).save(),
|
||||
!opts?.skipEventEmit
|
||||
? emitEvent({
|
||||
event: "CHANNEL_CREATE",
|
||||
data: channel,
|
||||
guild_id: channel.guild_id,
|
||||
} as ChannelCreateEvent)
|
||||
event: "CHANNEL_CREATE",
|
||||
data: channel,
|
||||
guild_id: channel.guild_id,
|
||||
} as ChannelCreateEvent)
|
||||
: Promise.resolve(),
|
||||
]);
|
||||
|
||||
|
@ -9,4 +9,4 @@ export class EmbedCache extends BaseClass {
|
||||
|
||||
@Column({ type: "simple-json" })
|
||||
embed: Embed;
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,8 @@ export class Guild extends BaseClass {
|
||||
banner?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
default_message_notifications?: number = Config.get().defaults.guild.defaultMessageNotifications;
|
||||
default_message_notifications?: number =
|
||||
Config.get().defaults.guild.defaultMessageNotifications;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description?: string;
|
||||
@ -88,7 +89,8 @@ export class Guild extends BaseClass {
|
||||
discovery_splash?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
explicit_content_filter?: number = Config.get().defaults.guild.explicitContentFilter;
|
||||
explicit_content_filter?: number =
|
||||
Config.get().defaults.guild.explicitContentFilter;
|
||||
|
||||
@Column({ type: "simple-array" })
|
||||
features: string[] = Config.get().guild.defaultFeatures || []; //TODO use enum
|
||||
@ -110,7 +112,8 @@ export class Guild extends BaseClass {
|
||||
max_presences?: number = Config.get().defaults.guild.maxPresences;
|
||||
|
||||
@Column({ nullable: true })
|
||||
max_video_channel_users?: number = Config.get().defaults.guild.maxVideoChannelUsers;
|
||||
max_video_channel_users?: number =
|
||||
Config.get().defaults.guild.maxVideoChannelUsers;
|
||||
|
||||
@Column({ nullable: true })
|
||||
member_count?: number;
|
||||
|
@ -1,10 +1,4 @@
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
RelationId,
|
||||
} from "typeorm";
|
||||
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||
import { Member } from "./Member";
|
||||
import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
@ -62,7 +56,7 @@ export class Invite extends BaseClassWithoutId {
|
||||
|
||||
@JoinColumn({ name: "inviter_id" })
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE"
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
inviter: User;
|
||||
|
||||
|
@ -125,9 +125,9 @@ export class Member extends BaseClassWithoutId {
|
||||
|
||||
@Column()
|
||||
bio: string;
|
||||
|
||||
|
||||
@Column({ nullable: true, type: "simple-array" })
|
||||
theme_colors?: number[]; // TODO: Separate `User` and `UserProfile` models
|
||||
theme_colors?: number[]; // TODO: Separate `User` and `UserProfile` models
|
||||
|
||||
@Column({ nullable: true })
|
||||
pronouns?: string;
|
||||
@ -309,9 +309,9 @@ export class Member extends BaseClassWithoutId {
|
||||
guild_id,
|
||||
user: {
|
||||
sessions: {
|
||||
status: Not("invisible" as "invisible") // lol typescript?
|
||||
}
|
||||
}
|
||||
status: Not("invisible" as "invisible"), // lol typescript?
|
||||
},
|
||||
},
|
||||
},
|
||||
take: 10,
|
||||
});
|
||||
@ -380,7 +380,7 @@ export class Member extends BaseClassWithoutId {
|
||||
stage_instances: [],
|
||||
threads: [],
|
||||
embedded_activities: [],
|
||||
voice_states: guild.voice_states
|
||||
voice_states: guild.voice_states,
|
||||
},
|
||||
user_id,
|
||||
} as GuildCreateEvent),
|
||||
|
@ -15,13 +15,7 @@ import { ConnectedAccount } from "./ConnectedAccount";
|
||||
import { Member } from "./Member";
|
||||
import { UserSettings } from "./UserSettings";
|
||||
import { Session } from "./Session";
|
||||
import {
|
||||
Config,
|
||||
FieldErrors,
|
||||
Snowflake,
|
||||
trimSpecial,
|
||||
adjustEmail,
|
||||
} from "..";
|
||||
import { Config, FieldErrors, Snowflake, trimSpecial, adjustEmail } from "..";
|
||||
|
||||
export enum PublicUserEnum {
|
||||
username,
|
||||
@ -68,7 +62,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;
|
||||
@ -92,7 +86,7 @@ export class User extends BaseClass {
|
||||
banner?: string; // hash of the user banner
|
||||
|
||||
@Column({ nullable: true, type: "simple-array" })
|
||||
theme_colors?: number[]; // TODO: Separate `User` and `UserProfile` models
|
||||
theme_colors?: number[]; // TODO: Separate `User` and `UserProfile` models
|
||||
|
||||
@Column({ nullable: true })
|
||||
pronouns?: string;
|
||||
@ -140,7 +134,7 @@ export class User extends BaseClass {
|
||||
premium_since: Date = new Date(); // premium date
|
||||
|
||||
@Column({ select: false })
|
||||
verified: boolean = true; // email is verified
|
||||
verified: boolean = true; // email is verified
|
||||
|
||||
@Column()
|
||||
disabled: boolean = false; // if the account is disabled
|
||||
@ -203,7 +197,7 @@ export class User extends BaseClass {
|
||||
@OneToOne(() => UserSettings, {
|
||||
cascade: true,
|
||||
orphanedRowAction: "delete",
|
||||
eager: false
|
||||
eager: false,
|
||||
})
|
||||
@JoinColumn()
|
||||
settings: UserSettings;
|
||||
@ -270,7 +264,9 @@ export class User extends BaseClass {
|
||||
});
|
||||
}
|
||||
|
||||
public static async generateDiscriminator(username: string): Promise<string | undefined> {
|
||||
public static async generateDiscriminator(
|
||||
username: string,
|
||||
): Promise<string | undefined> {
|
||||
if (Config.get().register.incrementingDiscriminators) {
|
||||
// discriminator will be incrementally generated
|
||||
|
||||
@ -322,7 +318,7 @@ export class User extends BaseClass {
|
||||
password?: string;
|
||||
email?: string;
|
||||
date_of_birth?: Date; // "2000-04-03"
|
||||
id?: string,
|
||||
id?: string;
|
||||
req?: any;
|
||||
}) {
|
||||
// trim special uf8 control characters -> Backspace, Newline, ...
|
||||
@ -347,7 +343,7 @@ export class User extends BaseClass {
|
||||
|
||||
const settings = UserSettings.create({
|
||||
locale: language,
|
||||
})
|
||||
});
|
||||
|
||||
const user = User.create({
|
||||
username: username,
|
||||
@ -367,15 +363,12 @@ export class User extends BaseClass {
|
||||
});
|
||||
|
||||
user.validate();
|
||||
await Promise.all([
|
||||
user.save(),
|
||||
settings.save(),
|
||||
])
|
||||
await Promise.all([user.save(), settings.save()]);
|
||||
|
||||
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) => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -3,117 +3,117 @@ import { BaseClassWithoutId } from "./BaseClass";
|
||||
|
||||
@Entity("user_settings")
|
||||
export class UserSettings extends BaseClassWithoutId {
|
||||
@PrimaryGeneratedColumn()
|
||||
@PrimaryGeneratedColumn()
|
||||
index: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
afk_timeout: number = 3600;
|
||||
afk_timeout: number = 3600;
|
||||
|
||||
@Column({ nullable: true })
|
||||
allow_accessibility_detection: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
animate_emoji: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
animate_stickers: number = 0;
|
||||
|
||||
@Column({ nullable: true })
|
||||
contact_sync_enabled: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
convert_emoticons: boolean = false;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
custom_status: CustomStatus | null = null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
default_guilds_restricted: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
detect_platform_accounts: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
developer_mode: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
disable_games_tab: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
enable_tts_command: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
explicit_content_filter: number = 0;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
friend_source_flags: FriendSourceFlags = { all: true };
|
||||
|
||||
@Column({ nullable: true })
|
||||
gateway_connected: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
gif_auto_play: boolean = false;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
guild_folders: GuildFolder[] = []; // every top guild is displayed as a "folder"
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
guild_positions: string[] = []; // guild ids ordered by position
|
||||
|
||||
@Column({ nullable: true })
|
||||
inline_attachment_media: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
inline_embed_media: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
locale: string = "en-US"; // en_US
|
||||
|
||||
@Column({ nullable: true })
|
||||
message_display_compact: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
native_phone_integration_enabled: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
render_embeds: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
render_reactions: boolean = true;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
restricted_guilds: string[] = [];
|
||||
|
||||
@Column({ nullable: true })
|
||||
show_current_game: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
status: "online" | "offline" | "dnd" | "idle" | "invisible" = "online";
|
||||
|
||||
@Column({ nullable: true })
|
||||
stream_notifications_enabled: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
theme: "dark" | "light" = "dark"; // dark
|
||||
|
||||
@Column({ nullable: true })
|
||||
timezone_offset: number = 0; // e.g -60
|
||||
allow_accessibility_detection: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
animate_emoji: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
animate_stickers: number = 0;
|
||||
|
||||
@Column({ nullable: true })
|
||||
contact_sync_enabled: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
convert_emoticons: boolean = false;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
custom_status: CustomStatus | null = null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
default_guilds_restricted: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
detect_platform_accounts: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
developer_mode: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
disable_games_tab: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
enable_tts_command: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
explicit_content_filter: number = 0;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
friend_source_flags: FriendSourceFlags = { all: true };
|
||||
|
||||
@Column({ nullable: true })
|
||||
gateway_connected: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
gif_auto_play: boolean = false;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
guild_folders: GuildFolder[] = []; // every top guild is displayed as a "folder"
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
guild_positions: string[] = []; // guild ids ordered by position
|
||||
|
||||
@Column({ nullable: true })
|
||||
inline_attachment_media: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
inline_embed_media: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
locale: string = "en-US"; // en_US
|
||||
|
||||
@Column({ nullable: true })
|
||||
message_display_compact: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
native_phone_integration_enabled: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
render_embeds: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
render_reactions: boolean = true;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
restricted_guilds: string[] = [];
|
||||
|
||||
@Column({ nullable: true })
|
||||
show_current_game: boolean = true;
|
||||
|
||||
@Column({ nullable: true })
|
||||
status: "online" | "offline" | "dnd" | "idle" | "invisible" = "online";
|
||||
|
||||
@Column({ nullable: true })
|
||||
stream_notifications_enabled: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
theme: "dark" | "light" = "dark"; // dark
|
||||
|
||||
@Column({ nullable: true })
|
||||
timezone_offset: number = 0; // e.g -60
|
||||
}
|
||||
|
||||
interface CustomStatus {
|
||||
emoji_id?: string;
|
||||
emoji_name?: string;
|
||||
expires_at?: number;
|
||||
text?: string;
|
||||
emoji_id?: string;
|
||||
emoji_name?: string;
|
||||
expires_at?: number;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
interface GuildFolder {
|
||||
color: number;
|
||||
guild_ids: string[];
|
||||
id: number;
|
||||
name: string;
|
||||
color: number;
|
||||
guild_ids: string[];
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface FriendSourceFlags {
|
||||
all: boolean
|
||||
}
|
||||
interface FriendSourceFlags {
|
||||
all: boolean;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user