mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-25 19:52:36 +01:00
Prettier
This commit is contained in:
parent
ea903baa15
commit
a3f2f997a3
@ -26,13 +26,13 @@ const CHANGELOG_SCRIPT = "4ec0b5948572d31df88b.js";
|
|||||||
.toString()
|
.toString()
|
||||||
.replaceAll("\r", "")
|
.replaceAll("\r", "")
|
||||||
.replaceAll("\n", "\\n")
|
.replaceAll("\n", "\\n")
|
||||||
.replaceAll("\'", "\\'");
|
.replaceAll("'", "\\'");
|
||||||
|
|
||||||
const index = text.indexOf("t.exports='---changelog---") + 11;
|
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(
|
await fs.writeFile(
|
||||||
path.join(CACHE_PATH, CHANGELOG_SCRIPT),
|
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 path = require("path");
|
||||||
const fetch = require("node-fetch");
|
const fetch = require("node-fetch");
|
||||||
const http = require('http');
|
const http = require("http");
|
||||||
const https = require('https');
|
const https = require("https");
|
||||||
const fs = require("fs/promises");
|
const fs = require("fs/promises");
|
||||||
const { existsSync } = require("fs");
|
const { existsSync } = require("fs");
|
||||||
|
|
||||||
// https://stackoverflow.com/a/62500224
|
// https://stackoverflow.com/a/62500224
|
||||||
const httpAgent = new http.Agent({ keepAlive: true });
|
const httpAgent = new http.Agent({ keepAlive: true });
|
||||||
const httpsAgent = new https.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 CACHE_PATH = path.join(__dirname, "..", "assets", "cache");
|
||||||
const BASE_URL = "https://discord.com";
|
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 /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 Nitro/g, `${INSTANCE_NAME} Premium`);
|
||||||
content = content.replaceAll(/Discord's/g, `${INSTANCE_NAME}'s`);
|
content = content.replaceAll(/Discord's/g, `${INSTANCE_NAME}'s`);
|
||||||
//content = content.replaceAll(/DiscordTag/g, "FosscordTag");
|
//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."'],
|
[' 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,"'],
|
[' 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) =>
|
||||||
serverVariations.forEach(x => content = content.replaceAll(x[0], x[1]));
|
serverVariations.push([x[0].toLowerCase(), x[1].toLowerCase()]),
|
||||||
|
);
|
||||||
|
serverVariations.forEach((x) => (content = content.replaceAll(x[0], x[1])));
|
||||||
|
|
||||||
// sentry
|
// 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
|
//logos
|
||||||
content = content.replaceAll(
|
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",
|
"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
|
// app download links
|
||||||
// content = content.replaceAll(
|
// content = content.replaceAll(
|
||||||
@ -126,28 +138,34 @@ const doPatch = (content) => {
|
|||||||
// Stop client from deleting `localStorage` global. Makes `plugins` and `preload-plugins` less annoying.
|
// Stop client from deleting `localStorage` global. Makes `plugins` and `preload-plugins` less annoying.
|
||||||
content = content.replaceAll(
|
content = content.replaceAll(
|
||||||
"delete window.localStorage",
|
"delete window.localStorage",
|
||||||
"console.log('Prevented deletion of localStorage')"
|
"console.log('Prevented deletion of localStorage')",
|
||||||
);
|
);
|
||||||
|
|
||||||
// fast identify
|
// fast identify
|
||||||
content = content.replaceAll(
|
content = content.replaceAll(
|
||||||
"e.isFastConnect=t;t?e._doFastConnectIdentify():e._doResumeOrIdentify()",
|
"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
|
// 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;
|
return content;
|
||||||
};
|
};
|
||||||
|
|
||||||
const processFile = async (name) => {
|
const processFile = async (name) => {
|
||||||
const res = await fetch(`${BASE_URL}/assets/${name}${name.includes(".") ? "" : ".js"}`, {
|
const res = await fetch(
|
||||||
agent,
|
`${BASE_URL}/assets/${name}${name.includes(".") ? "" : ".js"}`,
|
||||||
});
|
{
|
||||||
|
agent,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
return [];
|
return [];
|
||||||
};
|
}
|
||||||
|
|
||||||
if (name.includes(".") && !name.includes(".js") && !name.includes(".css")) {
|
if (name.includes(".") && !name.includes(".js") && !name.includes(".css")) {
|
||||||
await fs.writeFile(path.join(CACHE_PATH, name), await res.buffer());
|
await fs.writeFile(path.join(CACHE_PATH, name), await res.buffer());
|
||||||
@ -158,9 +176,14 @@ const processFile = async (name) => {
|
|||||||
|
|
||||||
text = doPatch(text);
|
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 () => {
|
(async () => {
|
||||||
@ -187,7 +210,13 @@ const processFile = async (name) => {
|
|||||||
|
|
||||||
process.stdout.moveCursor(0, 1);
|
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) {
|
while (CACHE_MISSES.length > 0) {
|
||||||
const asset = CACHE_MISSES.shift();
|
const asset = CACHE_MISSES.shift();
|
||||||
process.stdout.clearLine(0);
|
process.stdout.clearLine(0);
|
||||||
@ -216,11 +245,15 @@ const processFile = async (name) => {
|
|||||||
`Patching existing ${file}. Remaining: ${existing.length}.`,
|
`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")) {
|
if (file.includes(".js") || file.includes(".css")) {
|
||||||
text = doPatch(text.toString());
|
text = doPatch(text.toString());
|
||||||
await fs.writeFile(path.join(CACHE_PATH, file), 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.cursorTo(0);
|
||||||
process.stdout.write(
|
process.stdout.write(
|
||||||
`Caching asset ${asset}. ` +
|
`Caching asset ${asset}. ` +
|
||||||
`${i}/${assets.length - 1} = ${Math.floor(
|
`${i}/${assets.length - 1} = ${Math.floor(
|
||||||
(i / (assets.length - 1)) * 100,
|
(i / (assets.length - 1)) * 100,
|
||||||
)}% `
|
)}% `,
|
||||||
// + `Finish at: ${new Date(
|
// + `Finish at: ${new Date(
|
||||||
// Date.now() + finishTime,
|
// Date.now() + finishTime,
|
||||||
// ).toLocaleTimeString()}`,
|
// ).toLocaleTimeString()}`,
|
||||||
|
@ -51,7 +51,7 @@ function modify(obj) {
|
|||||||
function main() {
|
function main() {
|
||||||
const program = TJS.programFromConfig(
|
const program = TJS.programFromConfig(
|
||||||
path.join(__dirname, "..", "tsconfig.json"),
|
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);
|
const generator = TJS.buildGenerator(program, settings);
|
||||||
if (!generator || !program) return;
|
if (!generator || !program) return;
|
||||||
|
@ -4,7 +4,15 @@ const path = require("path");
|
|||||||
(async () => {
|
(async () => {
|
||||||
DataSourceOptions.setOptions({
|
DataSourceOptions.setOptions({
|
||||||
logging: true,
|
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();
|
const dbConnection = await DataSourceOptions.initialize();
|
||||||
|
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");
|
const { MigrationInterface, QueryRunner } = require("typeorm");
|
||||||
|
|
||||||
module.exports = class staging1672815835837 {
|
module.exports = class staging1672815835837 {
|
||||||
name = 'staging1672815835837'
|
name = "staging1672815835837";
|
||||||
|
|
||||||
async up(queryRunner) {
|
async up(queryRunner) {
|
||||||
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "FK_76ba283779c8441fd5ff819c8cf"`);
|
await queryRunner.query(
|
||||||
await queryRunner.query(`ALTER TABLE "user_settings" RENAME COLUMN "id" TO "index"`);
|
`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "FK_76ba283779c8441fd5ff819c8cf"`,
|
||||||
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(
|
||||||
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"))`);
|
`ALTER TABLE "user_settings" RENAME COLUMN "id" TO "index"`,
|
||||||
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(
|
||||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "win_url"`);
|
`ALTER TABLE "user_settings" RENAME CONSTRAINT "PK_00f004f5922a0744d174530d639" TO "PK_e81f8bb92802737337d35c00981"`,
|
||||||
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(
|
||||||
await queryRunner.query(`ALTER TABLE "client_release" ADD "platform" character varying NOT NULL`);
|
`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(`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(
|
||||||
await queryRunner.query(`ALTER TABLE "users" ADD "premium_usage_flags" integer NOT NULl DEFAULT 0`);
|
`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 "users" ADD "settingsIndex" integer`);
|
);
|
||||||
await queryRunner.query(`ALTER TABLE "users" ADD CONSTRAINT "UQ_0c14beb78d8c5ccba66072adbc7" UNIQUE ("settingsIndex")`);
|
await queryRunner.query(
|
||||||
await queryRunner.query(`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "pub_date"`);
|
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "deb_url"`,
|
||||||
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(
|
||||||
await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "nsfw" SET NOT NULL`);
|
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "osx_url"`,
|
||||||
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(
|
||||||
await queryRunner.query(`UPDATE channels SET default_thread_rate_limit_per_user = 0 WHERE default_thread_rate_limit_per_user IS NULL`);
|
`ALTER TABLE "client_release" DROP COLUMN IF EXISTS "win_url"`,
|
||||||
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(
|
||||||
await queryRunner.query(`ALTER TABLE "user_settings" DROP COLUMN IF EXISTS "index"`);
|
`ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "REL_76ba283779c8441fd5ff819c8c"`,
|
||||||
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(
|
||||||
await queryRunner.query(`ALTER TABLE "guilds" DROP COLUMN IF EXISTS "primary_category_id"`);
|
`ALTER TABLE "users" DROP COLUMN IF EXISTS "settingsId"`,
|
||||||
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(
|
||||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "large" SET NOT NULL`);
|
`ALTER TABLE "client_release" ADD "platform" character varying 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(
|
||||||
await queryRunner.query(`UPDATE guilds SET unavailable = false WHERE unavailable IS NULL`);
|
`ALTER TABLE "client_release" ADD "enabled" boolean NOT 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(
|
||||||
await queryRunner.query(`ALTER TABLE "guilds" ALTER COLUMN "widget_enabled" SET NOT NULL`);
|
`ALTER TABLE "users" ADD "purchased_flags" integer NOT NULL DEFAULT 0`,
|
||||||
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(
|
||||||
await queryRunner.query(`ALTER TABLE "members" DROP COLUMN IF EXISTS "premium_since"`);
|
`ALTER TABLE "users" ADD "premium_usage_flags" integer NOT NULl DEFAULT 0`,
|
||||||
await queryRunner.query(`ALTER TABLE "members" ADD "premium_since" bigint`);
|
);
|
||||||
|
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(`UPDATE users SET bio = '' WHERE bio IS NULL`);
|
||||||
await queryRunner.query(`ALTER TABLE users ALTER COLUMN bio SET NOT NULL`);
|
await queryRunner.query(
|
||||||
await queryRunner.query(`UPDATE users SET mfa_enabled = false WHERE mfa_enabled IS NULL`);
|
`ALTER TABLE users ALTER COLUMN bio SET NOT 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(
|
||||||
}
|
`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) {}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
@ -14,7 +14,7 @@ import { initInstance } from "./util/handlers/Instance";
|
|||||||
import { registerRoutes } from "@fosscord/util";
|
import { registerRoutes } from "@fosscord/util";
|
||||||
import { red } from "picocolors";
|
import { red } from "picocolors";
|
||||||
|
|
||||||
export interface FosscordServerOptions extends ServerOptions { }
|
export interface FosscordServerOptions extends ServerOptions {}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
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
|
// 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
|
// 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.
|
// and since its not an error middleware, our error handler below still works.
|
||||||
api.use(
|
api.use("*", (req: Request, res: Response, next: NextFunction) => {
|
||||||
"*",
|
res.status(404).json({
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
message: "404 endpoint not found",
|
||||||
res.status(404).json({
|
code: 0,
|
||||||
message: "404 endpoint not found",
|
});
|
||||||
code: 0,
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
|
||||||
|
@ -22,16 +22,28 @@ export default function TestClient(app: Application) {
|
|||||||
|
|
||||||
const agent = new ProxyAgent();
|
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
|
html = applyPlugins(html); // inject our plugins
|
||||||
app.use("/assets/plugins", express.static(path.join(ASSET_FOLDER_PATH, "plugins")));
|
app.use(
|
||||||
app.use("/assets/inline-plugins", express.static(path.join(ASSET_FOLDER_PATH, "inline-plugins")));
|
"/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
|
// 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) => {
|
app.get("/assets/:file", async (req, res) => {
|
||||||
@ -43,13 +55,15 @@ export default function TestClient(app: Application) {
|
|||||||
let buffer: Buffer;
|
let buffer: Buffer;
|
||||||
const cache = assetCache.get(req.params.file);
|
const cache = assetCache.get(req.params.file);
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
response = await fetch(`https://discord.com/assets/${req.params.file}`, {
|
response = await fetch(
|
||||||
agent,
|
`https://discord.com/assets/${req.params.file}`,
|
||||||
headers: { ...req.headers as { [key: string]: string; } },
|
{
|
||||||
});
|
agent,
|
||||||
|
headers: { ...(req.headers as { [key: string]: string }) },
|
||||||
|
},
|
||||||
|
);
|
||||||
buffer = await response.buffer();
|
buffer = await response.buffer();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
response = cache.response;
|
response = cache.response;
|
||||||
buffer = cache.buffer;
|
buffer = cache.buffer;
|
||||||
}
|
}
|
||||||
@ -62,8 +76,8 @@ export default function TestClient(app: Application) {
|
|||||||
"transfer-encoding",
|
"transfer-encoding",
|
||||||
"expect-ct",
|
"expect-ct",
|
||||||
"access-control-allow-origin",
|
"access-control-allow-origin",
|
||||||
"content-encoding"
|
"content-encoding",
|
||||||
].forEach(headerName => {
|
].forEach((headerName) => {
|
||||||
response.headers.delete(headerName);
|
response.headers.delete(headerName);
|
||||||
});
|
});
|
||||||
response.headers.forEach((value, name) => res.set(name, value));
|
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.
|
// TODO: I don't like this. Figure out a way to get client cacher to download *all* assets.
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
console.warn(`[TestClient] Cache miss for file ${req.params.file}! Use 'npm run generate:client' to cache and patch.`);
|
console.warn(
|
||||||
await fs.promises.appendFile(path.join(ASSET_FOLDER_PATH, "cacheMisses"), req.params.file + "\n");
|
`[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);
|
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
|
// Instead of our generated html, send developers.html for developers endpoint
|
||||||
app.get("/developers*", (req, res) => {
|
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.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.
|
// Send our generated index.html for all routes.
|
||||||
app.get("*", (req, res) => {
|
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");
|
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);
|
return res.send(html);
|
||||||
});
|
});
|
||||||
@ -101,16 +126,26 @@ export default function TestClient(app: Application) {
|
|||||||
const applyEnv = (html: string): string => {
|
const applyEnv = (html: string): string => {
|
||||||
const config = Config.get();
|
const config = Config.get();
|
||||||
|
|
||||||
const cdn = (config.cdn.endpointClient || config.cdn.endpointPublic || process.env.CDN || "")
|
const cdn = (
|
||||||
.replace(/(https?)?(:\/\/?)/g, "");
|
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)
|
if (cdn) html = html.replace(/CDN_HOST: .+/, `CDN_HOST: \`${cdn}\`,`);
|
||||||
html = html.replace(/CDN_HOST: .+/, `CDN_HOST: \`${cdn}\`,`);
|
|
||||||
|
|
||||||
if (gateway)
|
if (gateway)
|
||||||
html = html.replace(/GATEWAY_ENDPOINT: .+/, `GATEWAY_ENDPOINT: \`${gateway}\`,`);
|
html = html.replace(
|
||||||
|
/GATEWAY_ENDPOINT: .+/,
|
||||||
|
`GATEWAY_ENDPOINT: \`${gateway}\`,`,
|
||||||
|
);
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
@ -118,26 +153,35 @@ const applyEnv = (html: string): string => {
|
|||||||
// Injects inline, preload, and standard plugins into index.html.
|
// Injects inline, preload, and standard plugins into index.html.
|
||||||
const applyPlugins = (html: string): string => {
|
const applyPlugins = (html: string): string => {
|
||||||
// Inline plugins. Injected as <script src="/assets/inline-plugins/name.js"> into head.
|
// 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
|
const inline = inlineFiles
|
||||||
.filter(x => x.endsWith(".js"))
|
.filter((x) => x.endsWith(".js"))
|
||||||
.map(x => `<script src="/assets/inline-plugins/${x}"></script>`)
|
.map((x) => `<script src="/assets/inline-plugins/${x}"></script>`)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
html = html.replace("<!-- inline plugin marker -->", inline);
|
html = html.replace("<!-- inline plugin marker -->", inline);
|
||||||
|
|
||||||
// Preload plugins. Text content of each plugin is injected into head.
|
// 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
|
const preload = preloadFiles
|
||||||
.filter(x => x.endsWith(".js"))
|
.filter((x) => x.endsWith(".js"))
|
||||||
.map(x => `<script>${fs.readFileSync(path.join(ASSET_FOLDER_PATH, "preload-plugins", x))}</script>`)
|
.map(
|
||||||
|
(x) =>
|
||||||
|
`<script>${fs.readFileSync(
|
||||||
|
path.join(ASSET_FOLDER_PATH, "preload-plugins", x),
|
||||||
|
)}</script>`,
|
||||||
|
)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
html = html.replace("<!-- preload plugin marker -->", preload);
|
html = html.replace("<!-- preload plugin marker -->", preload);
|
||||||
|
|
||||||
// Normal plugins. Injected as <script src="/assets/plugins/name.js"> into body.
|
// Normal plugins. Injected as <script src="/assets/plugins/name.js"> into body.
|
||||||
const pluginFiles = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "plugins"));
|
const pluginFiles = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "plugins"));
|
||||||
const plugins = pluginFiles
|
const plugins = pluginFiles
|
||||||
.filter(x => x.endsWith(".js"))
|
.filter((x) => x.endsWith(".js"))
|
||||||
.map(x => `<script src="/assets/plugins/${x}"></script>`)
|
.map((x) => `<script src="/assets/plugins/${x}"></script>`)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
html = html.replace("<!-- plugin marker -->", plugins);
|
html = html.replace("<!-- plugin marker -->", plugins);
|
||||||
|
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route } from "@fosscord/api";
|
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 { HTTPError } from "lambert-server";
|
||||||
import { verifyToken } from "node-2fa";
|
import { verifyToken } from "node-2fa";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.post("/", route({}), async (req: Request, res: Response) => {
|
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)
|
if (app.owner.id != req.user_id)
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||||
@ -31,7 +41,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
|
|||||||
await app.save();
|
await app.save();
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
token: await generateToken(user.id)
|
token: await generateToken(user.id),
|
||||||
}).status(204);
|
}).status(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -42,7 +52,10 @@ router.post("/reset", route({}), async (req: Request, res: Response) => {
|
|||||||
if (owner.id != req.user_id)
|
if (owner.id != req.user_id)
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
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);
|
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||||
|
|
||||||
bot.data = { hash: undefined, valid_tokens_since: new Date() };
|
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);
|
res.json({ token }).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.patch("/", route({ body: "BotModifySchema" }), async (req: Request, res: Response) => {
|
router.patch(
|
||||||
const body = req.body as BotModifySchema;
|
"/",
|
||||||
if (!body.avatar?.trim()) delete body.avatar;
|
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)
|
if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
|
||||||
throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
|
|
||||||
|
|
||||||
if (app.owner.id != req.user_id)
|
if (app.owner.id != req.user_id)
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||||
|
|
||||||
if (body.avatar)
|
if (body.avatar)
|
||||||
body.avatar = await handleFile(
|
body.avatar = await handleFile(
|
||||||
`/avatars/${app.id}`,
|
`/avatars/${app.id}`,
|
||||||
body.avatar as string,
|
body.avatar as string,
|
||||||
);
|
);
|
||||||
|
|
||||||
app.bot.assign(body);
|
app.bot.assign(body);
|
||||||
|
|
||||||
app.bot.save();
|
app.bot.save();
|
||||||
|
|
||||||
await app.save();
|
await app.save();
|
||||||
res.json(app).status(200);
|
res.json(app).status(200);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@ -1,57 +1,81 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route } from "@fosscord/api";
|
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 { verifyToken } from "node-2fa";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
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)
|
if (app.owner.id != req.user_id)
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||||
|
|
||||||
return res.json(app);
|
return res.json(app);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.patch("/", route({ body: "ApplicationModifySchema" }), async (req: Request, res: Response) => {
|
router.patch(
|
||||||
const body = req.body as ApplicationModifySchema;
|
"/",
|
||||||
|
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)
|
if (app.owner.id != req.user_id)
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
||||||
|
|
||||||
if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code)))
|
if (
|
||||||
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
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) {
|
if (app.bot) {
|
||||||
app.bot.assign({ bio: body.description });
|
app.bot.assign({ bio: body.description });
|
||||||
await app.bot.save();
|
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) => {
|
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)
|
if (app.owner.id != req.user_id)
|
||||||
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
|
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);
|
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
|
||||||
|
|
||||||
if (app.bot)
|
if (app.bot) await User.delete({ id: app.bot.id });
|
||||||
await User.delete({ id: app.bot.id });
|
|
||||||
|
|
||||||
await Application.delete({ id: app.id });
|
await Application.delete({ id: app.id });
|
||||||
|
|
||||||
res.send().status(200);
|
res.send().status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@ -1,30 +1,42 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { route } from "@fosscord/api";
|
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();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
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);
|
res.json(results).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/", route({ body: "ApplicationCreateSchema" }), async (req: Request, res: Response) => {
|
router.post(
|
||||||
const body = req.body as ApplicationCreateSchema;
|
"/",
|
||||||
const user = await User.findOneOrFail({ where: { id: req.user_id } });
|
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({
|
const app = Application.create({
|
||||||
name: trimSpecial(body.name),
|
name: trimSpecial(body.name),
|
||||||
description: "",
|
description: "",
|
||||||
bot_public: true,
|
bot_public: true,
|
||||||
owner: user,
|
owner: user,
|
||||||
verify_key: "IMPLEMENTME",
|
verify_key: "IMPLEMENTME",
|
||||||
flags: 0,
|
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();
|
const router: Router = Router();
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
router.get("/", route({ right: "OPERATOR" }), async (req: Request, res: Response) => {
|
router.get(
|
||||||
const count = req.query.count ? parseInt(req.query.count as string) : 1;
|
"/",
|
||||||
const length = req.query.length ? parseInt(req.query.length as string) : 255;
|
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++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const token = ValidRegistrationToken.create({
|
const token = ValidRegistrationToken.create({
|
||||||
token: random(length),
|
token: random(length),
|
||||||
expires_at: Date.now() + Config.get().security.defaultRegistrationTokenExpiration
|
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?
|
if (req.query.plain)
|
||||||
await ValidRegistrationToken.save(tokens, { chunk: 1000, reload: false, transaction: false });
|
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
|
// Reg tokens
|
||||||
// They're a one time use token that bypasses registration limits ( rates, disabled reg, etc )
|
// They're a one time use token that bypasses registration limits ( rates, disabled reg, etc )
|
||||||
let regTokenUsed = false;
|
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];
|
const token = req.get("Referrer")!.split("token=")[1].split("&")[0];
|
||||||
if (token) {
|
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 });
|
await ValidRegistrationToken.delete({ token });
|
||||||
regTokenUsed = true;
|
regTokenUsed = true;
|
||||||
console.log(`[REGISTER] Registration token ${token} used for registration!`);
|
console.log(
|
||||||
}
|
`[REGISTER] Registration token ${token} used for registration!`,
|
||||||
else {
|
);
|
||||||
console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`);
|
} 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;
|
const { sitekey, service } = security.captcha;
|
||||||
if (!body.captcha_key) {
|
if (!body.captcha_key) {
|
||||||
return res?.status(400).json({
|
return res?.status(400).json({
|
||||||
@ -220,14 +230,26 @@ router.post(
|
|||||||
if (
|
if (
|
||||||
!regTokenUsed &&
|
!regTokenUsed &&
|
||||||
limits.absoluteRate.register.enabled &&
|
limits.absoluteRate.register.enabled &&
|
||||||
(await User.count({ where: { created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)) } }))
|
(await User.count({
|
||||||
>= limits.absoluteRate.register.limit
|
where: {
|
||||||
|
created_at: MoreThan(
|
||||||
|
new Date(
|
||||||
|
Date.now() - limits.absoluteRate.register.window,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})) >= limits.absoluteRate.register.limit
|
||||||
) {
|
) {
|
||||||
console.log(
|
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({
|
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(),
|
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);
|
return res.json(message);
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,12 @@ import {
|
|||||||
Rights,
|
Rights,
|
||||||
} from "@fosscord/util";
|
} from "@fosscord/util";
|
||||||
import { HTTPError } from "lambert-server";
|
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 multer from "multer";
|
||||||
import { yellow } from "picocolors";
|
import { yellow } from "picocolors";
|
||||||
import { FindManyOptions, LessThan, MoreThan } from "typeorm";
|
import { FindManyOptions, LessThan, MoreThan } from "typeorm";
|
||||||
@ -80,7 +85,7 @@ router.get("/", async (req: Request, res: Response) => {
|
|||||||
permissions.hasThrow("VIEW_CHANNEL");
|
permissions.hasThrow("VIEW_CHANNEL");
|
||||||
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
|
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" },
|
order: { timestamp: "DESC" },
|
||||||
take: limit,
|
take: limit,
|
||||||
where: { channel_id },
|
where: { channel_id },
|
||||||
@ -138,8 +143,9 @@ router.get("/", async (req: Request, res: Response) => {
|
|||||||
const uri = y.proxy_url.startsWith("http")
|
const uri = y.proxy_url.startsWith("http")
|
||||||
? y.proxy_url
|
? y.proxy_url
|
||||||
: `https://example.org${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: {
|
where: {
|
||||||
nonce: body.nonce,
|
nonce: body.nonce,
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
author_id: req.user_id
|
author_id: req.user_id,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return res.json(existing);
|
return res.json(existing);
|
||||||
@ -225,13 +231,21 @@ router.post(
|
|||||||
const count = await Message.count({
|
const count = await Message.count({
|
||||||
where: {
|
where: {
|
||||||
channel_id,
|
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)
|
if (count >= limits.absoluteRate.sendMessage.limit)
|
||||||
throw FieldErrors({
|
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 }),
|
Attachment.create({ ...file, proxy_url: file.url }),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} 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) {
|
if (!message.member) {
|
||||||
message.member = await Member.findOneOrFail({
|
message.member = await Member.findOneOrFail({
|
||||||
where: { id: req.user_id, guild_id: message.guild_id },
|
where: { id: req.user_id, guild_id: message.guild_id },
|
||||||
relations: ["roles"]
|
relations: ["roles"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
message.member.roles =
|
message.member.roles = message.member.roles
|
||||||
message.member.roles.
|
.filter((x) => x.id != x.guild_id)
|
||||||
filter(x => x.id != x.guild_id)
|
.map((x) => x.id);
|
||||||
.map(x => x.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let read_state = await ReadState.findOne({
|
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)
|
if (!read_state)
|
||||||
read_state = ReadState.create({ user_id: req.user_id, channel_id });
|
read_state = ReadState.create({ user_id: req.user_id, channel_id });
|
||||||
@ -324,14 +337,14 @@ router.post(
|
|||||||
} as MessageCreateEvent),
|
} as MessageCreateEvent),
|
||||||
message.guild_id
|
message.guild_id
|
||||||
? Member.update(
|
? Member.update(
|
||||||
{ id: req.user_id, guild_id: message.guild_id },
|
{ id: req.user_id, guild_id: message.guild_id },
|
||||||
{ last_message_id: message.id },
|
{ last_message_id: message.id },
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
channel.save(),
|
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);
|
return res.json(message);
|
||||||
},
|
},
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
emitEvent,
|
emitEvent,
|
||||||
Member,
|
Member,
|
||||||
Role,
|
Role,
|
||||||
ChannelPermissionOverwriteSchema
|
ChannelPermissionOverwriteSchema,
|
||||||
} from "@fosscord/util";
|
} from "@fosscord/util";
|
||||||
import { Router, Response, Request } from "express";
|
import { Router, Response, Request } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
import { Router, Response, Request } from "express";
|
import { Router, Response, Request } from "express";
|
||||||
import { route } from "@fosscord/api";
|
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 { HTTPError } from "lambert-server";
|
||||||
import { isTextChannel } from "./messages/index";
|
import { isTextChannel } from "./messages/index";
|
||||||
import { DiscordApiErrors } from "@fosscord/util";
|
import { DiscordApiErrors } from "@fosscord/util";
|
||||||
@ -38,8 +47,7 @@ router.post(
|
|||||||
if (name === "clyde") throw new HTTPError("Invalid name", 400);
|
if (name === "clyde") throw new HTTPError("Invalid name", 400);
|
||||||
if (name === "Fosscord Ghost") throw new HTTPError("Invalid name", 400);
|
if (name === "Fosscord Ghost") throw new HTTPError("Invalid name", 400);
|
||||||
|
|
||||||
if (avatar)
|
if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar);
|
||||||
avatar = await handleFile(`/avatars/${channel_id}`, avatar);
|
|
||||||
|
|
||||||
const hook = Webhook.create({
|
const hook = Webhook.create({
|
||||||
type: WebhookType.Incoming,
|
type: WebhookType.Incoming,
|
||||||
|
@ -12,19 +12,20 @@ const router = Router();
|
|||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
const { platform } = req.query;
|
const { platform } = req.query;
|
||||||
|
|
||||||
if (!platform) throw FieldErrors({
|
if (!platform)
|
||||||
platform: {
|
throw FieldErrors({
|
||||||
code: "BASE_TYPE_REQUIRED",
|
platform: {
|
||||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
code: "BASE_TYPE_REQUIRED",
|
||||||
}
|
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const release = await Release.findOneOrFail({
|
const release = await Release.findOneOrFail({
|
||||||
where: {
|
where: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
platform: platform as string,
|
platform: platform as string,
|
||||||
},
|
},
|
||||||
order: { pub_date: "DESC" }
|
order: { pub_date: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
||||||
res.redirect(release.url);
|
res.redirect(release.url);
|
||||||
|
@ -69,15 +69,21 @@ router.patch(
|
|||||||
body.splash,
|
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(
|
body.discovery_splash = await handleFile(
|
||||||
`/discovery-splashes/${guild_id}`,
|
`/discovery-splashes/${guild_id}`,
|
||||||
body.discovery_splash,
|
body.discovery_splash,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (body.features) {
|
if (body.features) {
|
||||||
const diff = guild.features.filter(x => !body.features?.includes(x))
|
const diff = guild.features
|
||||||
.concat(body.features.filter(x => !guild.features.includes(x)));
|
.filter((x) => !body.features?.includes(x))
|
||||||
|
.concat(
|
||||||
|
body.features.filter((x) => !guild.features.includes(x)),
|
||||||
|
);
|
||||||
|
|
||||||
// TODO move these
|
// TODO move these
|
||||||
const MUTABLE_FEATURES = [
|
const MUTABLE_FEATURES = [
|
||||||
@ -89,7 +95,9 @@ router.patch(
|
|||||||
for (var feature of diff) {
|
for (var feature of diff) {
|
||||||
if (MUTABLE_FEATURES.includes(feature)) continue;
|
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.
|
// 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);
|
return res.json(member);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => {
|
router.patch(
|
||||||
let { guild_id, member_id } = req.params;
|
"/",
|
||||||
if (member_id === "@me") member_id = req.user_id;
|
route({ body: "MemberChangeSchema" }),
|
||||||
const body = req.body as 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"] });
|
let member = await Member.findOneOrFail({
|
||||||
const permission = await getPermission(req.user_id, guild_id);
|
where: { id: member_id, guild_id },
|
||||||
const everyone = await Role.findOneOrFail({ where: { guild_id: guild_id, name: "@everyone", position: 0 } });
|
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) {
|
if ("roles" in body) {
|
||||||
permission.hasThrow("MANAGE_ROLES");
|
permission.hasThrow("MANAGE_ROLES");
|
||||||
|
|
||||||
body.roles = body.roles || [];
|
body.roles = body.roles || [];
|
||||||
body.roles.filter(x => !!x);
|
body.roles.filter((x) => !!x);
|
||||||
|
|
||||||
if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id);
|
if (body.roles.indexOf(everyone.id) === -1)
|
||||||
member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist
|
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
|
// do not use promise.all as we have to first write to db before emitting the event to catch errors
|
||||||
await emitEvent({
|
await emitEvent({
|
||||||
event: "GUILD_MEMBER_UPDATE",
|
event: "GUILD_MEMBER_UPDATE",
|
||||||
guild_id,
|
guild_id,
|
||||||
data: { ...member, roles: member.roles.map((x) => x.id) }
|
data: { ...member, roles: member.roles.map((x) => x.id) },
|
||||||
} as GuildMemberUpdateEvent);
|
} as GuildMemberUpdateEvent);
|
||||||
|
|
||||||
res.json(member);
|
res.json(member);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.put("/", route({}), async (req: Request, res: Response) => {
|
router.put("/", route({}), async (req: Request, res: Response) => {
|
||||||
// TODO: Lurker mode
|
// TODO: Lurker mode
|
||||||
|
@ -72,12 +72,20 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
|||||||
if (channel_id) query.where!.channel = { id: channel_id };
|
if (channel_id) query.where!.channel = { id: channel_id };
|
||||||
else {
|
else {
|
||||||
// get all channel IDs that this user can access
|
// 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 = [];
|
const ids = [];
|
||||||
|
|
||||||
for (var channel of channels) {
|
for (var channel of channels) {
|
||||||
const perm = await getPermission(req.user_id, req.params.guild_id, channel.id);
|
const perm = await getPermission(
|
||||||
if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue;
|
req.user_id,
|
||||||
|
req.params.guild_id,
|
||||||
|
channel.id,
|
||||||
|
);
|
||||||
|
if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY"))
|
||||||
|
continue;
|
||||||
ids.push(channel.id);
|
ids.push(channel.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,30 +1,48 @@
|
|||||||
import { route } from "@fosscord/api";
|
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";
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.patch("/:member_id", route({ body: "MemberChangeProfileSchema" }), async (req: Request, res: Response) => {
|
router.patch(
|
||||||
let { guild_id, member_id } = req.params;
|
"/:member_id",
|
||||||
if (member_id === "@me") member_id = req.user_id;
|
route({ body: "MemberChangeProfileSchema" }),
|
||||||
const body = req.body as 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
|
// do not use promise.all as we have to first write to db before emitting the event to catch errors
|
||||||
await emitEvent({
|
await emitEvent({
|
||||||
event: "GUILD_MEMBER_UPDATE",
|
event: "GUILD_MEMBER_UPDATE",
|
||||||
guild_id,
|
guild_id,
|
||||||
data: { ...member, roles: member.roles.map((x) => x.id) }
|
data: { ...member, roles: member.roles.map((x) => x.id) },
|
||||||
} as GuildMemberUpdateEvent);
|
} as GuildMemberUpdateEvent);
|
||||||
|
|
||||||
res.json(member);
|
res.json(member);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -63,12 +63,14 @@ router.patch(
|
|||||||
);
|
);
|
||||||
else body.icon = undefined;
|
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({
|
role.assign({
|
||||||
...body,
|
...body,
|
||||||
permissions: String(
|
permissions: String(
|
||||||
req.permission!.bitfield & BigInt(body.permissions || "0")
|
req.permission!.bitfield & BigInt(body.permissions || "0"),
|
||||||
)
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -61,9 +61,13 @@ router.post(
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
role.save(),
|
role.save(),
|
||||||
// Move all existing roles up one position, to accommodate the new role
|
// Move all existing roles up one position, to accommodate the new role
|
||||||
Role.createQueryBuilder('roles')
|
Role.createQueryBuilder("roles")
|
||||||
.where({ guild: { id: guild_id }, name: Not("@everyone"), id: Not(role.id) })
|
.where({
|
||||||
.update({ position: () => 'position + 1' })
|
guild: { id: guild_id },
|
||||||
|
name: Not("@everyone"),
|
||||||
|
id: Not(role.id),
|
||||||
|
})
|
||||||
|
.update({ position: () => "position + 1" })
|
||||||
.execute(),
|
.execute(),
|
||||||
emitEvent({
|
emitEvent({
|
||||||
event: "GUILD_ROLE_CREATE",
|
event: "GUILD_ROLE_CREATE",
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import { route } from "@fosscord/api";
|
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();
|
const router = Router();
|
||||||
|
|
||||||
// TODO: scopes, other oauth types
|
// TODO: scopes, other oauth types
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
const {
|
const { client_id, scope, response_type, redirect_url } = req.query;
|
||||||
client_id,
|
|
||||||
scope,
|
|
||||||
response_type,
|
|
||||||
redirect_url,
|
|
||||||
} = req.query;
|
|
||||||
|
|
||||||
const app = await Application.findOne({
|
const app = await Application.findOne({
|
||||||
where: {
|
where: {
|
||||||
@ -33,7 +40,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
|||||||
id: req.user_id,
|
id: req.user_id,
|
||||||
bot: false,
|
bot: false,
|
||||||
},
|
},
|
||||||
select: ["id", "username", "avatar", "discriminator", "public_flags"]
|
select: ["id", "username", "avatar", "discriminator", "public_flags"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const guilds = await Member.find({
|
const guilds = await Member.find({
|
||||||
@ -44,21 +51,23 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
|||||||
},
|
},
|
||||||
relations: ["guild", "roles"],
|
relations: ["guild", "roles"],
|
||||||
//@ts-ignore
|
//@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 guildsWithPermissions = guilds.map((x) => {
|
||||||
const perms = x.guild.owner_id === user.id
|
const perms =
|
||||||
? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
|
x.guild.owner_id === user.id
|
||||||
: Permissions.finalPermission({
|
? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
|
||||||
user: {
|
: Permissions.finalPermission({
|
||||||
id: user.id,
|
user: {
|
||||||
roles: x.roles?.map(x => x.id) || [],
|
id: user.id,
|
||||||
},
|
roles: x.roles?.map((x) => x.id) || [],
|
||||||
guild: {
|
},
|
||||||
roles: x?.roles || [],
|
guild: {
|
||||||
}
|
roles: x?.roles || [],
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: x.guild.id,
|
id: x.guild.id,
|
||||||
@ -75,7 +84,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
avatar: user.avatar,
|
avatar: user.avatar,
|
||||||
avatar_decoration: null, // TODO
|
avatar_decoration: null, // TODO
|
||||||
discriminator: user.discriminator,
|
discriminator: user.discriminator,
|
||||||
public_flags: user.public_flags,
|
public_flags: user.public_flags,
|
||||||
},
|
},
|
||||||
@ -87,7 +96,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
|||||||
summary: app.summary,
|
summary: app.summary,
|
||||||
type: app.type,
|
type: app.type,
|
||||||
hook: app.hook,
|
hook: app.hook,
|
||||||
guild_id: null, // TODO support guilds
|
guild_id: null, // TODO support guilds
|
||||||
bot_public: app.bot_public,
|
bot_public: app.bot_public,
|
||||||
bot_require_code_grant: app.bot_require_code_grant,
|
bot_require_code_grant: app.bot_require_code_grant,
|
||||||
verify_key: app.verify_key,
|
verify_key: app.verify_key,
|
||||||
@ -97,50 +106,63 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
|||||||
id: bot.id,
|
id: bot.id,
|
||||||
username: bot.username,
|
username: bot.username,
|
||||||
avatar: bot.avatar,
|
avatar: bot.avatar,
|
||||||
avatar_decoration: null, // TODO
|
avatar_decoration: null, // TODO
|
||||||
discriminator: bot.discriminator,
|
discriminator: bot.discriminator,
|
||||||
public_flags: bot.public_flags,
|
public_flags: bot.public_flags,
|
||||||
bot: true,
|
bot: true,
|
||||||
approximated_guild_count: 0, // TODO
|
approximated_guild_count: 0, // TODO
|
||||||
},
|
},
|
||||||
authorized: false,
|
authorized: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/", route({ body: "ApplicationAuthorizeSchema" }), async (req: Request, res: Response) => {
|
router.post(
|
||||||
const body = req.body as ApplicationAuthorizeSchema;
|
"/",
|
||||||
const {
|
route({ body: "ApplicationAuthorizeSchema" }),
|
||||||
client_id,
|
async (req: Request, res: Response) => {
|
||||||
scope,
|
const body = req.body as ApplicationAuthorizeSchema;
|
||||||
response_type,
|
const { client_id, scope, response_type, redirect_url } = req.query;
|
||||||
redirect_url
|
|
||||||
} = req.query;
|
|
||||||
|
|
||||||
// TODO: captcha verification
|
// TODO: captcha verification
|
||||||
// TODO: MFA verification
|
// TODO: MFA verification
|
||||||
|
|
||||||
const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
|
const perms = await getPermission(
|
||||||
// getPermission cache won't exist if we're owner
|
req.user_id,
|
||||||
if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member!.user.bot) throw DiscordApiErrors.UNAUTHORIZED;
|
body.guild_id,
|
||||||
perms.hasThrow("MANAGE_GUILD");
|
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({
|
const app = await Application.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: client_id as string,
|
id: client_id as string,
|
||||||
},
|
},
|
||||||
relations: ["bot"],
|
relations: ["bot"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: use DiscordApiErrors
|
// TODO: use DiscordApiErrors
|
||||||
// findOneOrFail throws code 404
|
// findOneOrFail throws code 404
|
||||||
if (!app) throw new ApiError("Unknown Application", 10002, 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);
|
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({
|
return res.json({
|
||||||
location: "/oauth2/authorized", // redirect URL
|
location: "/oauth2/authorized", // redirect URL
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { route } from "@fosscord/api";
|
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";
|
import { Request, Response, Router } from "express";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -15,7 +22,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
|||||||
guild: await Guild.count(),
|
guild: await Guild.count(),
|
||||||
message: await Message.count(),
|
message: await Message.count(),
|
||||||
members: await Member.count(),
|
members: await Member.count(),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,10 +3,14 @@ import { route } from "@fosscord/api";
|
|||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.post("/", route({ right: "OPERATOR" }), async (req: Request, res: Response) => {
|
router.post(
|
||||||
console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
|
"/",
|
||||||
res.sendStatus(200);
|
route({ right: "OPERATOR" }),
|
||||||
process.kill(process.pid, "SIGTERM");
|
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",
|
sku_id: "521842865731534868",
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "511651860671627264",
|
id: "511651860671627264",
|
||||||
@ -27,9 +27,9 @@ const skus = new Map([
|
|||||||
sku_id: "521842865731534868",
|
sku_id: "521842865731534868",
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"521846918637420545",
|
"521846918637420545",
|
||||||
@ -43,7 +43,7 @@ const skus = new Map([
|
|||||||
sku_id: "521846918637420545",
|
sku_id: "521846918637420545",
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "511651876987469824",
|
id: "511651876987469824",
|
||||||
@ -54,7 +54,7 @@ const skus = new Map([
|
|||||||
sku_id: "521846918637420545",
|
sku_id: "521846918637420545",
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "978380684370378761",
|
id: "978380684370378761",
|
||||||
@ -65,9 +65,9 @@ const skus = new Map([
|
|||||||
sku_id: "521846918637420545",
|
sku_id: "521846918637420545",
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"521847234246082599",
|
"521847234246082599",
|
||||||
@ -81,7 +81,7 @@ const skus = new Map([
|
|||||||
sku_id: "521847234246082599",
|
sku_id: "521847234246082599",
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "511651880837840896",
|
id: "511651880837840896",
|
||||||
@ -92,7 +92,7 @@ const skus = new Map([
|
|||||||
sku_id: "521847234246082599",
|
sku_id: "521847234246082599",
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "511651885459963904",
|
id: "511651885459963904",
|
||||||
@ -103,9 +103,9 @@ const skus = new Map([
|
|||||||
sku_id: "521847234246082599",
|
sku_id: "521847234246082599",
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"590663762298667008",
|
"590663762298667008",
|
||||||
@ -120,7 +120,7 @@ const skus = new Map([
|
|||||||
discount_price: 0,
|
discount_price: 0,
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "590665538238152709",
|
id: "590665538238152709",
|
||||||
@ -132,9 +132,9 @@ const skus = new Map([
|
|||||||
discount_price: 0,
|
discount_price: 0,
|
||||||
currency: "eur",
|
currency: "eur",
|
||||||
price: 0,
|
price: 0,
|
||||||
price_tier: null
|
price_tier: null,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"978380684370378762",
|
"978380684370378762",
|
||||||
@ -158,33 +158,33 @@ const skus = new Map([
|
|||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
payment_source_prices: {
|
payment_source_prices: {
|
||||||
"775487223059316758": [
|
"775487223059316758": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"736345864146255982": [
|
"736345864146255982": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"683074999590060249": [
|
"683074999590060249": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"3": {
|
"3": {
|
||||||
country_prices: {
|
country_prices: {
|
||||||
@ -193,33 +193,33 @@ const skus = new Map([
|
|||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
payment_source_prices: {
|
payment_source_prices: {
|
||||||
"775487223059316758": [
|
"775487223059316758": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"736345864146255982": [
|
"736345864146255982": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"683074999590060249": [
|
"683074999590060249": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
country_prices: {
|
country_prices: {
|
||||||
@ -228,33 +228,33 @@ const skus = new Map([
|
|||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
payment_source_prices: {
|
payment_source_prices: {
|
||||||
"775487223059316758": [
|
"775487223059316758": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"736345864146255982": [
|
"736345864146255982": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"683074999590060249": [
|
"683074999590060249": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
country_prices: {
|
country_prices: {
|
||||||
@ -263,39 +263,39 @@ const skus = new Map([
|
|||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
payment_source_prices: {
|
payment_source_prices: {
|
||||||
"775487223059316758": [
|
"775487223059316758": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"736345864146255982": [
|
"736345864146255982": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"683074999590060249": [
|
"683074999590060249": [
|
||||||
{
|
{
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
amount: 0,
|
amount: 0,
|
||||||
exponent: 2
|
exponent: 2,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
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 { client } = Config.get();
|
||||||
const platform = req.query.platform;
|
const platform = req.query.platform;
|
||||||
|
|
||||||
if (!platform) throw FieldErrors({
|
if (!platform)
|
||||||
platform: {
|
throw FieldErrors({
|
||||||
code: "BASE_TYPE_REQUIRED",
|
platform: {
|
||||||
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
code: "BASE_TYPE_REQUIRED",
|
||||||
}
|
message: req.t("common:field.BASE_TYPE_REQUIRED"),
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const release = await Release.findOneOrFail({
|
const release = await Release.findOneOrFail({
|
||||||
where: {
|
where: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
platform: platform as string,
|
platform: platform as string,
|
||||||
},
|
},
|
||||||
order: { pub_date: "DESC" }
|
order: { pub_date: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
@ -89,79 +89,94 @@ router.get(
|
|||||||
bot: user.bot,
|
bot: user.bot,
|
||||||
};
|
};
|
||||||
|
|
||||||
const userProfile = {
|
const userProfile = {
|
||||||
bio: req.user_bot ? null : user.bio,
|
bio: req.user_bot ? null : user.bio,
|
||||||
accent_color: user.accent_color,
|
accent_color: user.accent_color,
|
||||||
banner: user.banner,
|
banner: user.banner,
|
||||||
pronouns: user.pronouns,
|
pronouns: user.pronouns,
|
||||||
theme_colors: user.theme_colors,
|
theme_colors: user.theme_colors,
|
||||||
};
|
};
|
||||||
|
|
||||||
const guildMemberDto = guild_member
|
const guildMemberDto = guild_member
|
||||||
? {
|
? {
|
||||||
avatar: guild_member.avatar,
|
avatar: guild_member.avatar,
|
||||||
banner: guild_member.banner,
|
banner: guild_member.banner,
|
||||||
bio: req.user_bot ? null : guild_member.bio,
|
bio: req.user_bot ? null : guild_member.bio,
|
||||||
communication_disabled_until: guild_member.communication_disabled_until,
|
communication_disabled_until:
|
||||||
deaf: guild_member.deaf,
|
guild_member.communication_disabled_until,
|
||||||
flags: user.flags,
|
deaf: guild_member.deaf,
|
||||||
is_pending: guild_member.pending,
|
flags: user.flags,
|
||||||
pending: guild_member.pending, // why is this here twice, discord?
|
is_pending: guild_member.pending,
|
||||||
joined_at: guild_member.joined_at,
|
pending: guild_member.pending, // why is this here twice, discord?
|
||||||
mute: guild_member.mute,
|
joined_at: guild_member.joined_at,
|
||||||
nick: guild_member.nick,
|
mute: guild_member.mute,
|
||||||
premium_since: guild_member.premium_since,
|
nick: guild_member.nick,
|
||||||
roles: guild_member.roles.map((x) => x.id).filter((id) => id != guild_id),
|
premium_since: guild_member.premium_since,
|
||||||
user: userDto
|
roles: guild_member.roles
|
||||||
}
|
.map((x) => x.id)
|
||||||
: undefined;
|
.filter((id) => id != guild_id),
|
||||||
|
user: userDto,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const guildMemberProfile = {
|
const guildMemberProfile = {
|
||||||
accent_color: null,
|
accent_color: null,
|
||||||
banner: guild_member?.banner || null,
|
banner: guild_member?.banner || null,
|
||||||
bio: guild_member?.bio || "",
|
bio: guild_member?.bio || "",
|
||||||
guild_id
|
guild_id,
|
||||||
};
|
};
|
||||||
res.json({
|
res.json({
|
||||||
connected_accounts: user.connected_accounts,
|
connected_accounts: user.connected_accounts,
|
||||||
premium_guild_since: premium_guild_since, // TODO
|
premium_guild_since: premium_guild_since, // TODO
|
||||||
premium_since: user.premium_since, // TODO
|
premium_since: user.premium_since, // TODO
|
||||||
mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
|
mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
|
||||||
user: userDto,
|
user: userDto,
|
||||||
premium_type: user.premium_type,
|
premium_type: user.premium_type,
|
||||||
profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
|
profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
|
||||||
user_profile: userProfile,
|
user_profile: userProfile,
|
||||||
guild_member: guild_id && guildMemberDto,
|
guild_member: guild_id && guildMemberDto,
|
||||||
guild_member_profile: guild_id && guildMemberProfile
|
guild_member_profile: guild_id && guildMemberProfile,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.patch("/", route({ body: "UserProfileModifySchema" }), async (req: Request, res: Response) => {
|
router.patch(
|
||||||
const body = req.body as UserProfileModifySchema;
|
"/",
|
||||||
|
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);
|
if (body.banner)
|
||||||
let user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
|
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);
|
user.assign(body);
|
||||||
await user.save();
|
await user.save();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
delete user.data;
|
delete user.data;
|
||||||
|
|
||||||
// TODO: send update member list event in gateway
|
// TODO: send update member list event in gateway
|
||||||
await emitEvent({
|
await emitEvent({
|
||||||
event: "USER_UPDATE",
|
event: "USER_UPDATE",
|
||||||
user_id: req.user_id,
|
user_id: req.user_id,
|
||||||
data: user
|
data: user,
|
||||||
} as UserUpdateEvent);
|
} as UserUpdateEvent);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
accent_color: user.accent_color,
|
accent_color: user.accent_color,
|
||||||
bio: user.bio,
|
bio: user.bio,
|
||||||
banner: user.banner,
|
banner: user.banner,
|
||||||
theme_colors: user.theme_colors,
|
theme_colors: user.theme_colors,
|
||||||
pronouns: user.pronouns,
|
pronouns: user.pronouns,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -32,8 +32,7 @@ router.patch(
|
|||||||
|
|
||||||
const user = await Member.findOneOrFail({
|
const user = await Member.findOneOrFail({
|
||||||
where: { id: req.user_id, guild_id: req.params.guild_id },
|
where: { id: req.user_id, guild_id: req.params.guild_id },
|
||||||
select: ["settings"]
|
select: ["settings"],
|
||||||
|
|
||||||
});
|
});
|
||||||
OrmUtils.mergeDeep(user.settings || {}, body);
|
OrmUtils.mergeDeep(user.settings || {}, body);
|
||||||
Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user);
|
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.hash = await bcrypt.hash(body.new_password, 12);
|
||||||
user.data.valid_tokens_since = new Date();
|
user.data.valid_tokens_since = new Date();
|
||||||
newToken = await generateToken(user.id) as string;
|
newToken = (await generateToken(user.id)) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.username) {
|
if (body.username) {
|
||||||
|
@ -21,7 +21,7 @@ router.patch(
|
|||||||
|
|
||||||
const user = await User.findOneOrFail({
|
const user = await User.findOneOrFail({
|
||||||
where: { id: req.user_id, bot: false },
|
where: { id: req.user_id, bot: false },
|
||||||
relations: ["settings"]
|
relations: ["settings"],
|
||||||
});
|
});
|
||||||
|
|
||||||
user.settings.assign(body);
|
user.settings.assign(body);
|
||||||
|
@ -53,7 +53,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
|||||||
channel_id: opts.channel_id,
|
channel_id: opts.channel_id,
|
||||||
attachments: opts.attachments || [],
|
attachments: opts.attachments || [],
|
||||||
embeds: opts.embeds || [],
|
embeds: opts.embeds || [],
|
||||||
reactions: /*opts.reactions ||*/[],
|
reactions: /*opts.reactions ||*/ [],
|
||||||
type: opts.type ?? 0,
|
type: opts.type ?? 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -180,7 +180,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
|||||||
|
|
||||||
// TODO: cache link result in db
|
// TODO: cache link result in db
|
||||||
export async function postHandleMessage(message: Message) {
|
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);
|
var links = content?.match(LINK_REGEX);
|
||||||
if (!links) return;
|
if (!links) return;
|
||||||
|
|
||||||
@ -201,8 +201,12 @@ export async function postHandleMessage(message: Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bit gross, but whatever!
|
// bit gross, but whatever!
|
||||||
const endpointPublic = Config.get().cdn.endpointPublic || "http://127.0.0.1"; // lol
|
const endpointPublic =
|
||||||
const handler = url.hostname == new URL(endpointPublic).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"];
|
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 {
|
try {
|
||||||
let res = await handler(url);
|
let res = await handler(url);
|
||||||
@ -218,11 +222,10 @@ export async function postHandleMessage(message: Message) {
|
|||||||
cachePromises.push(cache.save());
|
cachePromises.push(cache.save());
|
||||||
data.embeds.push(embed);
|
data.embeds.push(embed);
|
||||||
}
|
}
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
Sentry.captureException(e, (scope) => {
|
||||||
Sentry.captureException(e, scope => {
|
|
||||||
scope.clear();
|
scope.clear();
|
||||||
scope.setContext("request", { url })
|
scope.setContext("request", { url });
|
||||||
return scope;
|
return scope;
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
@ -257,7 +260,7 @@ export async function sendMessage(opts: MessageOptions) {
|
|||||||
} as MessageCreateEvent),
|
} 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;
|
return message;
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,13 @@ export const DEFAULT_FETCH_OPTIONS: any = {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getProxyUrl = (url: URL, width: number, height: number): string => {
|
export const getProxyUrl = (
|
||||||
const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn;
|
url: URL,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
): string => {
|
||||||
|
const { resizeWidthMax, resizeHeightMax, imagorServerUrl } =
|
||||||
|
Config.get().cdn;
|
||||||
const secret = Config.get().security.requestSignature;
|
const secret = Config.get().security.requestSignature;
|
||||||
width = Math.min(width || 500, resizeWidthMax || width);
|
width = Math.min(width || 500, resizeWidthMax || width);
|
||||||
height = Math.min(height || 500, resizeHeightMax || width);
|
height = Math.min(height || 500, resizeHeightMax || width);
|
||||||
@ -26,16 +31,20 @@ export const getProxyUrl = (url: URL, width: number, height: number): string =>
|
|||||||
if (imagorServerUrl) {
|
if (imagorServerUrl) {
|
||||||
let path = `${width}x${height}/${url.host}${url.pathname}`;
|
let path = `${width}x${height}/${url.host}${url.pathname}`;
|
||||||
|
|
||||||
const hash = crypto.createHmac('sha1', secret)
|
const hash = crypto
|
||||||
|
.createHmac("sha1", secret)
|
||||||
.update(path)
|
.update(path)
|
||||||
.digest('base64')
|
.digest("base64")
|
||||||
.replace(/\+/g, '-').replace(/\//g, '_');
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_");
|
||||||
|
|
||||||
return `${imagorServerUrl}/${hash}/${path}`;
|
return `${imagorServerUrl}/${hash}/${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Imagor documentation
|
// 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 "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,8 +78,7 @@ const doFetch = async (url: URL) => {
|
|||||||
...DEFAULT_FETCH_OPTIONS,
|
...DEFAULT_FETCH_OPTIONS,
|
||||||
size: Config.get().limits.message.maxEmbedDownloadSize,
|
size: Config.get().limits.message.maxEmbedDownloadSize,
|
||||||
});
|
});
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -88,12 +96,10 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
|
|||||||
width = result.width;
|
width = result.width;
|
||||||
height = result.height;
|
height = result.height;
|
||||||
image = url.href;
|
image = url.href;
|
||||||
}
|
} else if (type.headers.get("content-type")?.indexOf("video") !== -1) {
|
||||||
else if (type.headers.get("content-type")?.indexOf("video") !== -1) {
|
|
||||||
// TODO
|
// TODO
|
||||||
return null;
|
return null;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// have to download the page, unfortunately
|
// have to download the page, unfortunately
|
||||||
const response = await doFetch(url);
|
const response = await doFetch(url);
|
||||||
if (!response) return null;
|
if (!response) return null;
|
||||||
@ -113,13 +119,15 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
|
|||||||
height: height,
|
height: height,
|
||||||
url: url.href,
|
url: url.href,
|
||||||
proxy_url: getProxyUrl(new URL(image), width, height),
|
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
|
// the url does not have a special handler
|
||||||
"default": async (url: URL) => {
|
default: async (url: URL) => {
|
||||||
const type = await fetch(url, {
|
const type = await fetch(url, {
|
||||||
...DEFAULT_FETCH_OPTIONS,
|
...DEFAULT_FETCH_OPTIONS,
|
||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
@ -154,7 +162,13 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
width: metas.width,
|
width: metas.width,
|
||||||
height: metas.height,
|
height: metas.height,
|
||||||
url: metas.image,
|
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,
|
description: metas.description,
|
||||||
};
|
};
|
||||||
@ -169,26 +183,28 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
// TODO: facebook
|
// TODO: facebook
|
||||||
// have to use their APIs or something because they don't send the metas in initial html
|
// 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) => {
|
"www.twitter.com": async (url: URL) => {
|
||||||
const token = Config.get().external.twitter;
|
const token = Config.get().external.twitter;
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
|
|
||||||
if (!url.href.includes("/status/")) return null; // TODO;
|
if (!url.href.includes("/status/")) return null; // TODO;
|
||||||
const id = url.pathname.split("/")[3]; // super bad lol
|
const id = url.pathname.split("/")[3]; // super bad lol
|
||||||
if (!parseInt(id)) return null;
|
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` +
|
`?expansions=author_id,attachments.media_keys` +
|
||||||
`&media.fields=url,width,height` +
|
`&media.fields=url,width,height` +
|
||||||
`&tweet.fields=created_at,public_metrics` +
|
`&tweet.fields=created_at,public_metrics` +
|
||||||
`&user.fields=profile_image_url`;
|
`&user.fields=profile_image_url`;
|
||||||
|
|
||||||
|
|
||||||
const response = await fetch(endpointUrl, {
|
const response = await fetch(endpointUrl, {
|
||||||
...DEFAULT_FETCH_OPTIONS,
|
...DEFAULT_FETCH_OPTIONS,
|
||||||
headers: {
|
headers: {
|
||||||
authorization: `Bearer ${token}`,
|
authorization: `Bearer ${token}`,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.errors) return null;
|
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 text = json.data.text;
|
||||||
const created_at = new Date(json.data.created_at);
|
const created_at = new Date(json.data.created_at);
|
||||||
const metrics = json.data.public_metrics;
|
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 = {
|
const embed: Embed = {
|
||||||
type: EmbedType.rich,
|
type: EmbedType.rich,
|
||||||
@ -205,19 +223,38 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
author: {
|
author: {
|
||||||
url: `https://twitter.com/${author.username}`,
|
url: `https://twitter.com/${author.username}`,
|
||||||
name: `${author.name} (@${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,
|
icon_url: author.profile_image_url,
|
||||||
},
|
},
|
||||||
timestamp: created_at,
|
timestamp: created_at,
|
||||||
fields: [
|
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,
|
color: 1942002,
|
||||||
footer: {
|
footer: {
|
||||||
text: "Twitter",
|
text: "Twitter",
|
||||||
proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192),
|
proxy_icon_url: getProxyUrl(
|
||||||
icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png"
|
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?
|
// Discord doesn't send this?
|
||||||
// provider: {
|
// provider: {
|
||||||
@ -231,7 +268,11 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
width: media[0].width,
|
width: media[0].width,
|
||||||
height: media[0].height,
|
height: media[0].height,
|
||||||
url: media[0].url,
|
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();
|
media.shift();
|
||||||
}
|
}
|
||||||
@ -265,17 +306,21 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
thumbnail: {
|
thumbnail: {
|
||||||
width: 640,
|
width: 640,
|
||||||
height: 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,
|
url: metas.image,
|
||||||
},
|
},
|
||||||
provider: {
|
provider: {
|
||||||
url: "https://spotify.com",
|
url: "https://spotify.com",
|
||||||
name: "Spotify",
|
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) => {
|
"www.pixiv.net": async (url: URL) => {
|
||||||
const response = await doFetch(url);
|
const response = await doFetch(url);
|
||||||
if (!response) return null;
|
if (!response) return null;
|
||||||
@ -291,12 +336,18 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
width: metas.width,
|
width: metas.width,
|
||||||
height: metas.height,
|
height: metas.height,
|
||||||
url: url.href,
|
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: {
|
provider: {
|
||||||
url: "https://pixiv.net",
|
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,
|
type: EmbedType.rich,
|
||||||
title: metas.title,
|
title: metas.title,
|
||||||
description: metas.description,
|
description: metas.description,
|
||||||
image: { // TODO: meant to be thumbnail.
|
image: {
|
||||||
|
// TODO: meant to be thumbnail.
|
||||||
// isn't this standard across all of steam?
|
// isn't this standard across all of steam?
|
||||||
width: 460,
|
width: 460,
|
||||||
height: 215,
|
height: 215,
|
||||||
url: metas.image,
|
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: {
|
provider: {
|
||||||
url: "https://store.steampowered.com",
|
url: "https://store.steampowered.com",
|
||||||
name: "Steam"
|
name: "Steam",
|
||||||
},
|
},
|
||||||
// TODO: fields for release date
|
// TODO: fields for release date
|
||||||
// TODO: Video
|
// 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) => {
|
"www.reddit.com": async (url: URL) => {
|
||||||
const res = await EmbedHandlers["default"](url);
|
const res = await EmbedHandlers["default"](url);
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
color: 16777215,
|
color: 16777215,
|
||||||
provider: {
|
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> => {
|
"www.youtube.com": async (url: URL): Promise<Embed | null> => {
|
||||||
const response = await doFetch(url);
|
const response = await doFetch(url);
|
||||||
if (!response) return null;
|
if (!response) return null;
|
||||||
@ -358,7 +416,13 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
width: metas.width,
|
width: metas.width,
|
||||||
height: metas.height,
|
height: metas.height,
|
||||||
url: metas.image,
|
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: {
|
provider: {
|
||||||
url: "https://www.youtube.com",
|
url: "https://www.youtube.com",
|
||||||
@ -369,12 +433,12 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
author: {
|
author: {
|
||||||
name: metas.author,
|
name: metas.author,
|
||||||
// TODO: author channel url
|
// TODO: author channel url
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// the url is an image from this instance
|
// 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);
|
const result = await probe(url.href);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -385,7 +449,7 @@ export const EmbedHandlers: { [key: string]: (url: URL) => Promise<Embed | Embed
|
|||||||
height: result.height,
|
height: result.height,
|
||||||
url: url.href,
|
url: url.href,
|
||||||
proxy_url: url.href,
|
proxy_url: url.href,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};;
|
};
|
||||||
|
@ -75,8 +75,14 @@ async function main() {
|
|||||||
// Filter breadcrumbs that we don't care about
|
// Filter breadcrumbs that we don't care about
|
||||||
if (x.message?.includes("identified as")) return false;
|
if (x.message?.includes("identified as")) return false;
|
||||||
if (x.message?.includes("[WebSocket] closed")) return false;
|
if (x.message?.includes("[WebSocket] closed")) return false;
|
||||||
if (x.message?.includes("Got Resume -> cancel not implemented")) return false;
|
if (
|
||||||
if (x.message?.includes("[Gateway] New connection from")) return false;
|
x.message?.includes(
|
||||||
|
"Got Resume -> cancel not implemented",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if (x.message?.includes("[Gateway] New connection from"))
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -75,10 +75,16 @@ export class CDNServer extends Server {
|
|||||||
this.app.use("/channel-icons/", avatarsRoute);
|
this.app.use("/channel-icons/", avatarsRoute);
|
||||||
this.log("verbose", "[Server] Route /channel-icons registered");
|
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.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");
|
this.log("verbose", "[Server] Route /guilds/banners registered");
|
||||||
|
|
||||||
return super.start();
|
return super.start();
|
||||||
|
@ -12,21 +12,32 @@ import { storage } from "../util/Storage";
|
|||||||
// TODO: delete old icons
|
// TODO: delete old icons
|
||||||
|
|
||||||
const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"];
|
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 ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES];
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/", multer.single("file"), async (req: Request, res: Response) => {
|
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");
|
if (!req.file) throw new HTTPError("Missing file");
|
||||||
const { buffer, mimetype, size, originalname, fieldname } = req.file;
|
const { buffer, mimetype, size, originalname, fieldname } = req.file;
|
||||||
const { guild_id, user_id } = req.params;
|
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);
|
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
|
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}`;
|
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,
|
id: hash,
|
||||||
content_type: type.mime,
|
content_type: type.mime,
|
||||||
size,
|
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) => {
|
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 { guild_id, user_id, id } = req.params;
|
||||||
const path = `guilds/${guild_id}/users/${user_id}/avatars/${id}`;
|
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) {
|
async set(path: string, value: any) {
|
||||||
path = getPath(path);
|
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);
|
value = Readable.from(value);
|
||||||
const cleaned_file = fs.createWriteStream(path);
|
const cleaned_file = fs.createWriteStream(path);
|
||||||
|
@ -12,7 +12,7 @@ import { Config } from "@fosscord/util";
|
|||||||
var erlpack: any;
|
var erlpack: any;
|
||||||
try {
|
try {
|
||||||
erlpack = require("@yukikaze-bot/erlpack");
|
erlpack = require("@yukikaze-bot/erlpack");
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
|
|
||||||
// TODO: check rate limit
|
// TODO: check rate limit
|
||||||
// TODO: specify rate limit in config
|
// TODO: specify rate limit in config
|
||||||
@ -48,7 +48,7 @@ export async function Connection(
|
|||||||
"open",
|
"open",
|
||||||
"ping",
|
"ping",
|
||||||
"pong",
|
"pong",
|
||||||
"unexpected-response"
|
"unexpected-response",
|
||||||
].forEach((x) => {
|
].forEach((x) => {
|
||||||
socket.on(x, (y) => console.log(x, y));
|
socket.on(x, (y) => console.log(x, y));
|
||||||
});
|
});
|
||||||
|
@ -10,30 +10,36 @@ const bigIntJson = BigIntJson({ storeAsString: true });
|
|||||||
var erlpack: any;
|
var erlpack: any;
|
||||||
try {
|
try {
|
||||||
erlpack = require("@yukikaze-bot/erlpack");
|
erlpack = require("@yukikaze-bot/erlpack");
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
|
|
||||||
export async function Message(this: WebSocket, buffer: WS.Data) {
|
export async function Message(this: WebSocket, buffer: WS.Data) {
|
||||||
// TODO: compression
|
// TODO: compression
|
||||||
var data: Payload;
|
var data: Payload;
|
||||||
|
|
||||||
if ((buffer instanceof Buffer && buffer[0] === 123) || // ASCII 123 = `{`. Bad check for JSON
|
if (
|
||||||
(typeof buffer === "string")) {
|
(buffer instanceof Buffer && buffer[0] === 123) || // ASCII 123 = `{`. Bad check for JSON
|
||||||
|
typeof buffer === "string"
|
||||||
|
) {
|
||||||
data = bigIntJson.parse(buffer.toString());
|
data = bigIntJson.parse(buffer.toString());
|
||||||
}
|
} else if (this.encoding === "json" && buffer instanceof Buffer) {
|
||||||
else if (this.encoding === "json" && buffer instanceof Buffer) {
|
|
||||||
if (this.inflate) {
|
if (this.inflate) {
|
||||||
try { buffer = this.inflate.process(buffer) as any; }
|
try {
|
||||||
catch { buffer = buffer.toString() as any; }
|
buffer = this.inflate.process(buffer) as any;
|
||||||
|
} catch {
|
||||||
|
buffer = buffer.toString() as any;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
data = bigIntJson.parse(buffer as string);
|
data = bigIntJson.parse(buffer as string);
|
||||||
}
|
} else if (this.encoding === "etf" && buffer instanceof Buffer) {
|
||||||
else if (this.encoding === "etf" && buffer instanceof Buffer) {
|
try {
|
||||||
try { data = erlpack.unpack(buffer); }
|
data = erlpack.unpack(buffer);
|
||||||
catch { return this.close(CLOSECODES.Decode_error); }
|
} catch {
|
||||||
}
|
return this.close(CLOSECODES.Decode_error);
|
||||||
else 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);
|
check.call(this, PayloadSchema, data);
|
||||||
|
|
||||||
@ -46,14 +52,17 @@ export async function Message(this: WebSocket, buffer: WS.Data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transaction = data.op != 1 ? Sentry.startTransaction({
|
const transaction =
|
||||||
op: OPCODES[data.op],
|
data.op != 1
|
||||||
name: `GATEWAY ${OPCODES[data.op]}`,
|
? Sentry.startTransaction({
|
||||||
data: {
|
op: OPCODES[data.op],
|
||||||
...data.d,
|
name: `GATEWAY ${OPCODES[data.op]}`,
|
||||||
token: data?.d?.token ? "[Redacted]" : undefined,
|
data: {
|
||||||
},
|
...data.d,
|
||||||
}) : undefined;
|
token: data?.d?.token ? "[Redacted]" : undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var ret = await OPCodeHandler.call(this, data);
|
var ret = await OPCodeHandler.call(this, data);
|
||||||
|
@ -259,7 +259,10 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
|
|
||||||
const d: ReadyEventData = {
|
const d: ReadyEventData = {
|
||||||
v: 9,
|
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: privateUser,
|
||||||
user_settings: user.settings,
|
user_settings: user.settings,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -267,7 +270,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
return {
|
return {
|
||||||
...new ReadyGuildDTO(x as Guild & { joined_at: Date }).toJSON(),
|
...new ReadyGuildDTO(x as Guild & { joined_at: Date }).toJSON(),
|
||||||
guild_hashes: {},
|
guild_hashes: {},
|
||||||
joined_at: x.joined_at
|
joined_at: x.joined_at,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
guild_experiments: [], // TODO
|
guild_experiments: [], // TODO
|
||||||
|
@ -159,7 +159,11 @@ async function getMembers(guild_id: string, range: [number, number]) {
|
|||||||
groups,
|
groups,
|
||||||
range,
|
range,
|
||||||
members: items
|
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),
|
.filter((x) => !!x),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
RegisterConfiguration,
|
RegisterConfiguration,
|
||||||
SecurityConfiguration,
|
SecurityConfiguration,
|
||||||
SentryConfiguration,
|
SentryConfiguration,
|
||||||
TemplateConfiguration
|
TemplateConfiguration,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
|
|
||||||
export class ConfigValue {
|
export class ConfigValue {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ClientReleaseConfiguration } from ".";
|
import { ClientReleaseConfiguration } from ".";
|
||||||
|
|
||||||
export class ClientConfiguration {
|
export class ClientConfiguration {
|
||||||
releases: ClientReleaseConfiguration = new ClientReleaseConfiguration();
|
releases: ClientReleaseConfiguration = new ClientReleaseConfiguration();
|
||||||
useTestClient: boolean = false;
|
useTestClient: boolean = false;
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { GuildDefaults, UserDefaults } from ".";
|
import { GuildDefaults, UserDefaults } from ".";
|
||||||
|
|
||||||
export class DefaultsConfiguration {
|
export class DefaultsConfiguration {
|
||||||
guild: GuildDefaults = new GuildDefaults();
|
guild: GuildDefaults = new GuildDefaults();
|
||||||
user: UserDefaults = new UserDefaults();
|
user: UserDefaults = new UserDefaults();
|
||||||
}
|
}
|
@ -2,7 +2,8 @@ import { Snowflake } from "@fosscord/util";
|
|||||||
|
|
||||||
export class GeneralConfiguration {
|
export class GeneralConfiguration {
|
||||||
instanceName: string = "Fosscord Instance";
|
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;
|
frontPage: string | null = null;
|
||||||
tosPage: string | null = null;
|
tosPage: string | null = null;
|
||||||
correspondenceEmail: string | null = null;
|
correspondenceEmail: string | null = null;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export class GifConfiguration {
|
export class GifConfiguration {
|
||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
provider: "tenor" = "tenor"; // more coming soon
|
provider: "tenor" = "tenor"; // more coming soon
|
||||||
apiKey?: string = "LIVDSRZULELA";
|
apiKey?: string = "LIVDSRZULELA";
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { DiscoveryConfiguration, AutoJoinConfiguration } from ".";
|
import { DiscoveryConfiguration, AutoJoinConfiguration } from ".";
|
||||||
|
|
||||||
export class GuildConfiguration {
|
export class GuildConfiguration {
|
||||||
discovery: DiscoveryConfiguration = new DiscoveryConfiguration();
|
discovery: DiscoveryConfiguration = new DiscoveryConfiguration();
|
||||||
autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration();
|
autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration();
|
||||||
defaultFeatures: string[] = [];
|
defaultFeatures: string[] = [];
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { KafkaBroker } from ".";
|
import { KafkaBroker } from ".";
|
||||||
|
|
||||||
export class KafkaConfiguration {
|
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 {
|
export class LimitsConfiguration {
|
||||||
user: UserLimits = new UserLimits();
|
user: UserLimits = new UserLimits();
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export class LoginConfiguration {
|
export class LoginConfiguration {
|
||||||
requireCaptcha: boolean = false;
|
requireCaptcha: boolean = false;
|
||||||
}
|
}
|
@ -1,3 +1,3 @@
|
|||||||
export class MetricsConfiguration {
|
export class MetricsConfiguration {
|
||||||
timeout: number = 30000;
|
timeout: number = 30000;
|
||||||
}
|
}
|
@ -1,3 +1,3 @@
|
|||||||
export class RabbitMQConfiguration {
|
export class RabbitMQConfiguration {
|
||||||
host: string | null = null;
|
host: string | null = null;
|
||||||
}
|
}
|
@ -1,16 +1,16 @@
|
|||||||
import { Region } from ".";
|
import { Region } from ".";
|
||||||
|
|
||||||
export class RegionConfiguration {
|
export class RegionConfiguration {
|
||||||
default: string = "fosscord";
|
default: string = "fosscord";
|
||||||
useDefaultAsOptimal: boolean = true;
|
useDefaultAsOptimal: boolean = true;
|
||||||
available: Region[] = [
|
available: Region[] = [
|
||||||
{
|
{
|
||||||
id: "fosscord",
|
id: "fosscord",
|
||||||
name: "Fosscord",
|
name: "Fosscord",
|
||||||
endpoint: "127.0.0.1:3004",
|
endpoint: "127.0.0.1:3004",
|
||||||
vip: false,
|
vip: false,
|
||||||
custom: false,
|
custom: false,
|
||||||
deprecated: false,
|
deprecated: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
@ -1,16 +1,20 @@
|
|||||||
import { DateOfBirthConfiguration, EmailConfiguration, PasswordConfiguration } from ".";
|
import {
|
||||||
|
DateOfBirthConfiguration,
|
||||||
|
EmailConfiguration,
|
||||||
|
PasswordConfiguration,
|
||||||
|
} from ".";
|
||||||
|
|
||||||
export class RegisterConfiguration {
|
export class RegisterConfiguration {
|
||||||
email: EmailConfiguration = new EmailConfiguration();
|
email: EmailConfiguration = new EmailConfiguration();
|
||||||
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
|
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
|
||||||
password: PasswordConfiguration = new PasswordConfiguration();
|
password: PasswordConfiguration = new PasswordConfiguration();
|
||||||
disabled: boolean = false;
|
disabled: boolean = false;
|
||||||
requireCaptcha: boolean = true;
|
requireCaptcha: boolean = true;
|
||||||
requireInvite: boolean = false;
|
requireInvite: boolean = false;
|
||||||
guestsRequireInvite: boolean = true;
|
guestsRequireInvite: boolean = true;
|
||||||
allowNewRegistration: boolean = true;
|
allowNewRegistration: boolean = true;
|
||||||
allowMultipleAccounts: boolean = true;
|
allowMultipleAccounts: boolean = true;
|
||||||
blockProxies: boolean = true;
|
blockProxies: boolean = true;
|
||||||
incrementingDiscriminators: boolean = false; // random otherwise
|
incrementingDiscriminators: boolean = false; // random otherwise
|
||||||
defaultRights: string = "30644591655940"; // See `npm run generate:rights`
|
defaultRights: string = "30644591655940"; // See `npm run generate:rights`
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ export class SecurityConfiguration {
|
|||||||
// X-Forwarded-For for nginx/reverse proxies
|
// X-Forwarded-For for nginx/reverse proxies
|
||||||
// CF-Connecting-IP for cloudflare
|
// CF-Connecting-IP for cloudflare
|
||||||
forwadedFor: string | null = null;
|
forwadedFor: string | null = null;
|
||||||
ipdataApiKey: string | null = "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9";
|
ipdataApiKey: string | null =
|
||||||
|
"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9";
|
||||||
mfaBackupCodeCount: number = 10;
|
mfaBackupCodeCount: number = 10;
|
||||||
statsWorldReadable: boolean = true;
|
statsWorldReadable: boolean = true;
|
||||||
defaultRegistrationTokenExpiration: number = 1000 * 60 * 60 * 24 * 7; //1 week
|
defaultRegistrationTokenExpiration: number = 1000 * 60 * 60 * 24 * 7; //1 week
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { hostname } from "os";
|
import { hostname } from "os";
|
||||||
|
|
||||||
export class SentryConfiguration {
|
export class SentryConfiguration {
|
||||||
enabled: boolean = false;
|
enabled: boolean = false;
|
||||||
endpoint: string = "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6";
|
endpoint: string =
|
||||||
traceSampleRate: number = 1.0;
|
"https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6";
|
||||||
environment: string = hostname();
|
traceSampleRate: number = 1.0;
|
||||||
|
environment: string = hostname();
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
export class TemplateConfiguration {
|
export class TemplateConfiguration {
|
||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
allowTemplateCreation: boolean = true;
|
allowTemplateCreation: boolean = true;
|
||||||
allowDiscordTemplates: boolean = true;
|
allowDiscordTemplates: boolean = true;
|
||||||
allowRaws: boolean = true;
|
allowRaws: boolean = true;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
export class ClientReleaseConfiguration {
|
export class ClientReleaseConfiguration {
|
||||||
useLocalRelease: boolean = true; //TODO
|
useLocalRelease: boolean = true; //TODO
|
||||||
upstreamVersion: string = "0.0.264";
|
upstreamVersion: string = "0.0.264";
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
export class GuildDefaults {
|
export class GuildDefaults {
|
||||||
maxPresences: number = 250000;
|
maxPresences: number = 250000;
|
||||||
maxVideoChannelUsers: number = 200;
|
maxVideoChannelUsers: number = 200;
|
||||||
afkTimeout: number = 300;
|
afkTimeout: number = 300;
|
||||||
defaultMessageNotifications: number = 1;
|
defaultMessageNotifications: number = 1;
|
||||||
explicitContentFilter: number = 0;
|
explicitContentFilter: number = 0;
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
export class UserDefaults {
|
export class UserDefaults {
|
||||||
premium: boolean = true;
|
premium: boolean = true;
|
||||||
premiumType: number = 2;
|
premiumType: number = 2;
|
||||||
verified: boolean = true;
|
verified: boolean = true;
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
export class AutoJoinConfiguration {
|
export class AutoJoinConfiguration {
|
||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
guilds: string[] = [];
|
guilds: string[] = [];
|
||||||
canLeave: boolean = true;
|
canLeave: boolean = true;
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
export class DiscoveryConfiguration {
|
export class DiscoveryConfiguration {
|
||||||
showAllGuilds: boolean = false;
|
showAllGuilds: boolean = false;
|
||||||
useRecommendation: boolean = false; // TODO: Recommendation, privacy concern?
|
useRecommendation: boolean = false; // TODO: Recommendation, privacy concern?
|
||||||
offset: number = 0;
|
offset: number = 0;
|
||||||
limit: number = 24;
|
limit: number = 24;
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
export class ChannelLimits {
|
export class ChannelLimits {
|
||||||
maxPins: number = 500;
|
maxPins: number = 500;
|
||||||
maxTopic: number = 1024;
|
maxTopic: number = 1024;
|
||||||
maxWebhooks: number = 100;
|
maxWebhooks: number = 100;
|
||||||
}
|
}
|
@ -1,6 +1,14 @@
|
|||||||
export class GlobalRateLimits {
|
export class GlobalRateLimits {
|
||||||
register: GlobalRateLimit = { limit: 25, window: 60 * 60 * 1000, enabled: true };
|
register: GlobalRateLimit = {
|
||||||
sendMessage: GlobalRateLimit = { limit: 200, window: 60 * 1000, enabled: true };
|
limit: 25,
|
||||||
|
window: 60 * 60 * 1000,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
sendMessage: GlobalRateLimit = {
|
||||||
|
limit: 200,
|
||||||
|
window: 60 * 1000,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GlobalRateLimit {
|
export class GlobalRateLimit {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export class GuildLimits {
|
export class GuildLimits {
|
||||||
maxRoles: number = 1000;
|
maxRoles: number = 1000;
|
||||||
maxEmojis: number = 2000;
|
maxEmojis: number = 2000;
|
||||||
maxMembers: number = 25000000;
|
maxMembers: number = 25000000;
|
||||||
maxChannels: number = 65535;
|
maxChannels: number = 65535;
|
||||||
maxChannelsInCategory: number = 65535;
|
maxChannelsInCategory: number = 65535;
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
export class MessageLimits {
|
export class MessageLimits {
|
||||||
maxCharacters: number = 1048576;
|
maxCharacters: number = 1048576;
|
||||||
maxTTSCharacters: number = 160;
|
maxTTSCharacters: number = 160;
|
||||||
maxReactions: number = 2048;
|
maxReactions: number = 2048;
|
||||||
maxAttachmentSize: number = 1024 * 1024 * 1024;
|
maxAttachmentSize: number = 1024 * 1024 * 1024;
|
||||||
maxBulkDelete: number = 1000;
|
maxBulkDelete: number = 1000;
|
||||||
maxEmbedDownloadSize: number = 1024 * 1024 * 5;
|
maxEmbedDownloadSize: number = 1024 * 1024 * 5;
|
||||||
}
|
}
|
@ -4,15 +4,15 @@ export class RateLimits {
|
|||||||
enabled: boolean = false;
|
enabled: boolean = false;
|
||||||
ip: Omit<RateLimitOptions, "bot_count"> = {
|
ip: Omit<RateLimitOptions, "bot_count"> = {
|
||||||
count: 500,
|
count: 500,
|
||||||
window: 5
|
window: 5,
|
||||||
};
|
};
|
||||||
global: RateLimitOptions = {
|
global: RateLimitOptions = {
|
||||||
count: 250,
|
count: 250,
|
||||||
window: 5
|
window: 5,
|
||||||
};
|
};
|
||||||
error: RateLimitOptions = {
|
error: RateLimitOptions = {
|
||||||
count: 10,
|
count: 10,
|
||||||
window: 5
|
window: 5,
|
||||||
};
|
};
|
||||||
routes: RouteRateLimit = new RouteRateLimit();
|
routes: RouteRateLimit = new RouteRateLimit();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export class UserLimits {
|
export class UserLimits {
|
||||||
maxGuilds: number = 1048576;
|
maxGuilds: number = 1048576;
|
||||||
maxUsername: number = 127;
|
maxUsername: number = 127;
|
||||||
maxFriends: number = 5000;
|
maxFriends: number = 5000;
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import { RateLimitOptions } from "./RateLimitOptions";
|
import { RateLimitOptions } from "./RateLimitOptions";
|
||||||
|
|
||||||
export class AuthRateLimit {
|
export class AuthRateLimit {
|
||||||
login: RateLimitOptions = {
|
login: RateLimitOptions = {
|
||||||
count: 5,
|
count: 5,
|
||||||
window: 60
|
window: 60,
|
||||||
};
|
};
|
||||||
register: RateLimitOptions = {
|
register: RateLimitOptions = {
|
||||||
count: 2,
|
count: 2,
|
||||||
window: 60 * 60 * 12
|
window: 60 * 60 * 12,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -4,15 +4,15 @@ import { RateLimitOptions } from "./RateLimitOptions";
|
|||||||
export class RouteRateLimit {
|
export class RouteRateLimit {
|
||||||
guild: RateLimitOptions = {
|
guild: RateLimitOptions = {
|
||||||
count: 5,
|
count: 5,
|
||||||
window: 5
|
window: 5,
|
||||||
};
|
};
|
||||||
webhook: RateLimitOptions = {
|
webhook: RateLimitOptions = {
|
||||||
count: 10,
|
count: 10,
|
||||||
window: 5
|
window: 5,
|
||||||
};
|
};
|
||||||
channel: RateLimitOptions = {
|
channel: RateLimitOptions = {
|
||||||
count: 10,
|
count: 10,
|
||||||
window: 5
|
window: 5,
|
||||||
};
|
};
|
||||||
auth: AuthRateLimit = new AuthRateLimit();
|
auth: AuthRateLimit = new AuthRateLimit();
|
||||||
// TODO: rate limit configuration for all routes
|
// TODO: rate limit configuration for all routes
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export class DateOfBirthConfiguration {
|
export class DateOfBirthConfiguration {
|
||||||
required: boolean = true;
|
required: boolean = true;
|
||||||
minimum: number = 13; // in years
|
minimum: number = 13; // in years
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
export class EmailConfiguration {
|
export class EmailConfiguration {
|
||||||
required: boolean = false;
|
required: boolean = false;
|
||||||
allowlist: boolean = false;
|
allowlist: boolean = false;
|
||||||
blocklist: boolean = true;
|
blocklist: boolean = true;
|
||||||
domains: string[] = [];// TODO: efficiently save domain blocklist in database
|
domains: string[] = []; // TODO: efficiently save domain blocklist in database
|
||||||
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
|
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
export class PasswordConfiguration {
|
export class PasswordConfiguration {
|
||||||
required: boolean = false;
|
required: boolean = false;
|
||||||
minLength: number = 8;
|
minLength: number = 8;
|
||||||
minNumbers: number = 2;
|
minNumbers: number = 2;
|
||||||
minUpperCase: number =2;
|
minUpperCase: number = 2;
|
||||||
minSymbols: number = 0;
|
minSymbols: number = 0;
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
export class CaptchaConfiguration {
|
export class CaptchaConfiguration {
|
||||||
enabled: boolean = false;
|
enabled: boolean = false;
|
||||||
service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom
|
service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom
|
||||||
sitekey: string | null = null;
|
sitekey: string | null = null;
|
||||||
secret: string | null = null;
|
secret: string | null = null;
|
||||||
}
|
}
|
@ -1,3 +1,3 @@
|
|||||||
export class TwoFactorConfiguration {
|
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 { BaseClass } from "./BaseClass";
|
||||||
import { Guild } from "./Guild";
|
import { Guild } from "./Guild";
|
||||||
import { Team } from "./Team";
|
import { Team } from "./Team";
|
||||||
@ -79,7 +86,7 @@ export class Application extends BaseClass {
|
|||||||
cover_image?: string; // the application's default rich presence invite cover image hash
|
cover_image?: string; // the application's default rich presence invite cover image hash
|
||||||
|
|
||||||
@Column({ type: "simple-json", nullable: true })
|
@Column({ type: "simple-json", nullable: true })
|
||||||
install_params?: {scopes: string[], permissions: string};
|
install_params?: { scopes: string[]; permissions: string };
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
terms_of_service_url?: string;
|
terms_of_service_url?: string;
|
||||||
@ -105,11 +112,10 @@ export class Application extends BaseClass {
|
|||||||
@JoinColumn({ name: "team_id" })
|
@JoinColumn({ name: "team_id" })
|
||||||
@ManyToOne(() => Team, {
|
@ManyToOne(() => Team, {
|
||||||
onDelete: "CASCADE",
|
onDelete: "CASCADE",
|
||||||
nullable: true
|
nullable: true,
|
||||||
})
|
})
|
||||||
team?: Team;
|
team?: Team;
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApplicationCommand {
|
export interface ApplicationCommand {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -209,7 +209,10 @@ export class Channel extends BaseClass {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Categories skip these checks on discord.com
|
// 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(" "))
|
if (channel.name.includes(" "))
|
||||||
throw new HTTPError(
|
throw new HTTPError(
|
||||||
"Channel name cannot include invalid characters",
|
"Channel name cannot include invalid characters",
|
||||||
@ -286,10 +289,10 @@ export class Channel extends BaseClass {
|
|||||||
Channel.create(channel).save(),
|
Channel.create(channel).save(),
|
||||||
!opts?.skipEventEmit
|
!opts?.skipEventEmit
|
||||||
? emitEvent({
|
? emitEvent({
|
||||||
event: "CHANNEL_CREATE",
|
event: "CHANNEL_CREATE",
|
||||||
data: channel,
|
data: channel,
|
||||||
guild_id: channel.guild_id,
|
guild_id: channel.guild_id,
|
||||||
} as ChannelCreateEvent)
|
} as ChannelCreateEvent)
|
||||||
: Promise.resolve(),
|
: Promise.resolve(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -79,7 +79,8 @@ export class Guild extends BaseClass {
|
|||||||
banner?: string;
|
banner?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
default_message_notifications?: number = Config.get().defaults.guild.defaultMessageNotifications;
|
default_message_notifications?: number =
|
||||||
|
Config.get().defaults.guild.defaultMessageNotifications;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -88,7 +89,8 @@ export class Guild extends BaseClass {
|
|||||||
discovery_splash?: string;
|
discovery_splash?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@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" })
|
@Column({ type: "simple-array" })
|
||||||
features: string[] = Config.get().guild.defaultFeatures || []; //TODO use enum
|
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;
|
max_presences?: number = Config.get().defaults.guild.maxPresences;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@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 })
|
@Column({ nullable: true })
|
||||||
member_count?: number;
|
member_count?: number;
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
|
||||||
Column,
|
|
||||||
Entity,
|
|
||||||
JoinColumn,
|
|
||||||
ManyToOne,
|
|
||||||
RelationId,
|
|
||||||
} from "typeorm";
|
|
||||||
import { Member } from "./Member";
|
import { Member } from "./Member";
|
||||||
import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass";
|
import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass";
|
||||||
import { Channel } from "./Channel";
|
import { Channel } from "./Channel";
|
||||||
@ -62,7 +56,7 @@ export class Invite extends BaseClassWithoutId {
|
|||||||
|
|
||||||
@JoinColumn({ name: "inviter_id" })
|
@JoinColumn({ name: "inviter_id" })
|
||||||
@ManyToOne(() => User, {
|
@ManyToOne(() => User, {
|
||||||
onDelete: "CASCADE"
|
onDelete: "CASCADE",
|
||||||
})
|
})
|
||||||
inviter: User;
|
inviter: User;
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ export class Member extends BaseClassWithoutId {
|
|||||||
bio: string;
|
bio: string;
|
||||||
|
|
||||||
@Column({ nullable: true, type: "simple-array" })
|
@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 })
|
@Column({ nullable: true })
|
||||||
pronouns?: string;
|
pronouns?: string;
|
||||||
@ -309,9 +309,9 @@ export class Member extends BaseClassWithoutId {
|
|||||||
guild_id,
|
guild_id,
|
||||||
user: {
|
user: {
|
||||||
sessions: {
|
sessions: {
|
||||||
status: Not("invisible" as "invisible") // lol typescript?
|
status: Not("invisible" as "invisible"), // lol typescript?
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
take: 10,
|
take: 10,
|
||||||
});
|
});
|
||||||
@ -380,7 +380,7 @@ export class Member extends BaseClassWithoutId {
|
|||||||
stage_instances: [],
|
stage_instances: [],
|
||||||
threads: [],
|
threads: [],
|
||||||
embedded_activities: [],
|
embedded_activities: [],
|
||||||
voice_states: guild.voice_states
|
voice_states: guild.voice_states,
|
||||||
},
|
},
|
||||||
user_id,
|
user_id,
|
||||||
} as GuildCreateEvent),
|
} as GuildCreateEvent),
|
||||||
|
@ -15,13 +15,7 @@ import { ConnectedAccount } from "./ConnectedAccount";
|
|||||||
import { Member } from "./Member";
|
import { Member } from "./Member";
|
||||||
import { UserSettings } from "./UserSettings";
|
import { UserSettings } from "./UserSettings";
|
||||||
import { Session } from "./Session";
|
import { Session } from "./Session";
|
||||||
import {
|
import { Config, FieldErrors, Snowflake, trimSpecial, adjustEmail } from "..";
|
||||||
Config,
|
|
||||||
FieldErrors,
|
|
||||||
Snowflake,
|
|
||||||
trimSpecial,
|
|
||||||
adjustEmail,
|
|
||||||
} from "..";
|
|
||||||
|
|
||||||
export enum PublicUserEnum {
|
export enum PublicUserEnum {
|
||||||
username,
|
username,
|
||||||
@ -68,7 +62,7 @@ export const PrivateUserProjection = [
|
|||||||
// Private user data that should never get sent to the client
|
// Private user data that should never get sent to the client
|
||||||
export type PublicUser = Pick<User, PublicUserKeys>;
|
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> {
|
export interface UserPrivate extends Pick<User, PrivateUserKeys> {
|
||||||
locale: string;
|
locale: string;
|
||||||
@ -92,7 +86,7 @@ export class User extends BaseClass {
|
|||||||
banner?: string; // hash of the user banner
|
banner?: string; // hash of the user banner
|
||||||
|
|
||||||
@Column({ nullable: true, type: "simple-array" })
|
@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 })
|
@Column({ nullable: true })
|
||||||
pronouns?: string;
|
pronouns?: string;
|
||||||
@ -140,7 +134,7 @@ export class User extends BaseClass {
|
|||||||
premium_since: Date = new Date(); // premium date
|
premium_since: Date = new Date(); // premium date
|
||||||
|
|
||||||
@Column({ select: false })
|
@Column({ select: false })
|
||||||
verified: boolean = true; // email is verified
|
verified: boolean = true; // email is verified
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
disabled: boolean = false; // if the account is disabled
|
disabled: boolean = false; // if the account is disabled
|
||||||
@ -203,7 +197,7 @@ export class User extends BaseClass {
|
|||||||
@OneToOne(() => UserSettings, {
|
@OneToOne(() => UserSettings, {
|
||||||
cascade: true,
|
cascade: true,
|
||||||
orphanedRowAction: "delete",
|
orphanedRowAction: "delete",
|
||||||
eager: false
|
eager: false,
|
||||||
})
|
})
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
settings: UserSettings;
|
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) {
|
if (Config.get().register.incrementingDiscriminators) {
|
||||||
// discriminator will be incrementally generated
|
// discriminator will be incrementally generated
|
||||||
|
|
||||||
@ -322,7 +318,7 @@ export class User extends BaseClass {
|
|||||||
password?: string;
|
password?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
date_of_birth?: Date; // "2000-04-03"
|
date_of_birth?: Date; // "2000-04-03"
|
||||||
id?: string,
|
id?: string;
|
||||||
req?: any;
|
req?: any;
|
||||||
}) {
|
}) {
|
||||||
// trim special uf8 control characters -> Backspace, Newline, ...
|
// trim special uf8 control characters -> Backspace, Newline, ...
|
||||||
@ -347,7 +343,7 @@ export class User extends BaseClass {
|
|||||||
|
|
||||||
const settings = UserSettings.create({
|
const settings = UserSettings.create({
|
||||||
locale: language,
|
locale: language,
|
||||||
})
|
});
|
||||||
|
|
||||||
const user = User.create({
|
const user = User.create({
|
||||||
username: username,
|
username: username,
|
||||||
@ -367,15 +363,12 @@ export class User extends BaseClass {
|
|||||||
});
|
});
|
||||||
|
|
||||||
user.validate();
|
user.validate();
|
||||||
await Promise.all([
|
await Promise.all([user.save(), settings.save()]);
|
||||||
user.save(),
|
|
||||||
settings.save(),
|
|
||||||
])
|
|
||||||
|
|
||||||
setImmediate(async () => {
|
setImmediate(async () => {
|
||||||
if (Config.get().guild.autoJoin.enabled) {
|
if (Config.get().guild.autoJoin.enabled) {
|
||||||
for (const guild of Config.get().guild.autoJoin.guilds || []) {
|
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")
|
@Entity("user_settings")
|
||||||
export class UserSettings extends BaseClassWithoutId {
|
export class UserSettings extends BaseClassWithoutId {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
index: string;
|
index: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
afk_timeout: number = 3600;
|
afk_timeout: number = 3600;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
allow_accessibility_detection: boolean = true;
|
allow_accessibility_detection: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
animate_emoji: boolean = true;
|
animate_emoji: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
animate_stickers: number = 0;
|
animate_stickers: number = 0;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
contact_sync_enabled: boolean = false;
|
contact_sync_enabled: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
convert_emoticons: boolean = false;
|
convert_emoticons: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true, type: "simple-json" })
|
@Column({ nullable: true, type: "simple-json" })
|
||||||
custom_status: CustomStatus | null = null;
|
custom_status: CustomStatus | null = null;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
default_guilds_restricted: boolean = false;
|
default_guilds_restricted: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
detect_platform_accounts: boolean = false;
|
detect_platform_accounts: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
developer_mode: boolean = true;
|
developer_mode: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
disable_games_tab: boolean = true;
|
disable_games_tab: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
enable_tts_command: boolean = false;
|
enable_tts_command: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
explicit_content_filter: number = 0;
|
explicit_content_filter: number = 0;
|
||||||
|
|
||||||
@Column({ nullable: true, type: "simple-json" })
|
@Column({ nullable: true, type: "simple-json" })
|
||||||
friend_source_flags: FriendSourceFlags = { all: true };
|
friend_source_flags: FriendSourceFlags = { all: true };
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
gateway_connected: boolean = false;
|
gateway_connected: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
gif_auto_play: boolean = false;
|
gif_auto_play: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true, type: "simple-json" })
|
@Column({ nullable: true, type: "simple-json" })
|
||||||
guild_folders: GuildFolder[] = []; // every top guild is displayed as a "folder"
|
guild_folders: GuildFolder[] = []; // every top guild is displayed as a "folder"
|
||||||
|
|
||||||
@Column({ nullable: true, type: "simple-json" })
|
@Column({ nullable: true, type: "simple-json" })
|
||||||
guild_positions: string[] = []; // guild ids ordered by position
|
guild_positions: string[] = []; // guild ids ordered by position
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
inline_attachment_media: boolean = true;
|
inline_attachment_media: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
inline_embed_media: boolean = true;
|
inline_embed_media: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
locale: string = "en-US"; // en_US
|
locale: string = "en-US"; // en_US
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
message_display_compact: boolean = false;
|
message_display_compact: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
native_phone_integration_enabled: boolean = true;
|
native_phone_integration_enabled: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
render_embeds: boolean = true;
|
render_embeds: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
render_reactions: boolean = true;
|
render_reactions: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true, type: "simple-json" })
|
@Column({ nullable: true, type: "simple-json" })
|
||||||
restricted_guilds: string[] = [];
|
restricted_guilds: string[] = [];
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
show_current_game: boolean = true;
|
show_current_game: boolean = true;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
status: "online" | "offline" | "dnd" | "idle" | "invisible" = "online";
|
status: "online" | "offline" | "dnd" | "idle" | "invisible" = "online";
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
stream_notifications_enabled: boolean = false;
|
stream_notifications_enabled: boolean = false;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
theme: "dark" | "light" = "dark"; // dark
|
theme: "dark" | "light" = "dark"; // dark
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
timezone_offset: number = 0; // e.g -60
|
timezone_offset: number = 0; // e.g -60
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomStatus {
|
interface CustomStatus {
|
||||||
emoji_id?: string;
|
emoji_id?: string;
|
||||||
emoji_name?: string;
|
emoji_name?: string;
|
||||||
expires_at?: number;
|
expires_at?: number;
|
||||||
text?: string;
|
text?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GuildFolder {
|
interface GuildFolder {
|
||||||
color: number;
|
color: number;
|
||||||
guild_ids: string[];
|
guild_ids: string[];
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FriendSourceFlags {
|
interface FriendSourceFlags {
|
||||||
all: boolean
|
all: boolean;
|
||||||
}
|
}
|
@ -6,4 +6,4 @@ export * from "./entities/index";
|
|||||||
export * from "./dtos/index";
|
export * from "./dtos/index";
|
||||||
export * from "./schemas";
|
export * from "./schemas";
|
||||||
export * from "./imports";
|
export * from "./imports";
|
||||||
export * from "./config"
|
export * from "./config";
|
||||||
|
@ -81,9 +81,9 @@ export interface ReadyEventData {
|
|||||||
number,
|
number,
|
||||||
null,
|
null,
|
||||||
number,
|
number,
|
||||||
[[number, { e: number; s: number; }[]]],
|
[[number, { e: number; s: number }[]]],
|
||||||
[number, [[number, [number, number]]]],
|
[number, [[number, [number, number]]]],
|
||||||
{ b: number; k: bigint[]; }[],
|
{ b: number; k: bigint[] }[],
|
||||||
][];
|
][];
|
||||||
guild_join_requests?: any[]; // ? what is this? this is new
|
guild_join_requests?: any[]; // ? what is this? this is new
|
||||||
shard?: [number, number];
|
shard?: [number, number];
|
||||||
@ -481,7 +481,7 @@ export interface SessionsReplace extends Event {
|
|||||||
export interface GuildMemberListUpdate extends Event {
|
export interface GuildMemberListUpdate extends Event {
|
||||||
event: "GUILD_MEMBER_LIST_UPDATE";
|
event: "GUILD_MEMBER_LIST_UPDATE";
|
||||||
data: {
|
data: {
|
||||||
groups: { id: string; count: number; }[];
|
groups: { id: string; count: number }[];
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
id: string;
|
id: string;
|
||||||
member_count: number;
|
member_count: number;
|
||||||
@ -489,8 +489,8 @@ export interface GuildMemberListUpdate extends Event {
|
|||||||
ops: {
|
ops: {
|
||||||
index: number;
|
index: number;
|
||||||
item: {
|
item: {
|
||||||
member?: PublicMember & { presence: Presence; };
|
member?: PublicMember & { presence: Presence };
|
||||||
group?: { id: string; count: number; }[];
|
group?: { id: string; count: number }[];
|
||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
@ -3,5 +3,5 @@ export interface ApplicationAuthorizeSchema {
|
|||||||
guild_id: string;
|
guild_id: string;
|
||||||
permissions: string;
|
permissions: string;
|
||||||
captcha_key?: string;
|
captcha_key?: string;
|
||||||
code?: string; // 2fa code
|
code?: string; // 2fa code
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { GuildCreateSchema } from "@fosscord/util";
|
import { GuildCreateSchema } from "@fosscord/util";
|
||||||
|
|
||||||
export interface GuildUpdateSchema extends Omit<GuildCreateSchema, "channels" | "name"> {
|
export interface GuildUpdateSchema
|
||||||
|
extends Omit<GuildCreateSchema, "channels" | "name"> {
|
||||||
name?: string;
|
name?: string;
|
||||||
banner?: string | null;
|
banner?: string | null;
|
||||||
splash?: string | null;
|
splash?: string | null;
|
||||||
|
@ -5,7 +5,7 @@ export interface MemberChangeProfileSchema {
|
|||||||
pronouns?: string;
|
pronouns?: string;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @items.type integer
|
* @items.type integer
|
||||||
*/
|
*/
|
||||||
theme_colors?: [number, number];
|
theme_colors?: [number, number];
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ export interface UserProfileModifySchema {
|
|||||||
pronouns?: string;
|
pronouns?: string;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @items.type integer
|
* @items.type integer
|
||||||
*/
|
*/
|
||||||
theme_colors?: [number, number]
|
theme_colors?: [number, number];
|
||||||
}
|
}
|
||||||
|
@ -138,10 +138,8 @@ export class BitField {
|
|||||||
const FLAGS = this.FLAGS || this.constructor?.FLAGS;
|
const FLAGS = this.FLAGS || this.constructor?.FLAGS;
|
||||||
|
|
||||||
if (typeof bit === "string") {
|
if (typeof bit === "string") {
|
||||||
if (typeof FLAGS[bit] !== "undefined")
|
if (typeof FLAGS[bit] !== "undefined") return FLAGS[bit];
|
||||||
return FLAGS[bit];
|
else bit = BigInt(bit);
|
||||||
else
|
|
||||||
bit = BigInt(bit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -14,7 +14,7 @@ var pairs: ConfigEntity[];
|
|||||||
export const Config = {
|
export const Config = {
|
||||||
init: async function init() {
|
init: async function init() {
|
||||||
if (config) return config;
|
if (config) return config;
|
||||||
console.log('[Config] Loading configuration...');
|
console.log("[Config] Loading configuration...");
|
||||||
pairs = await ConfigEntity.find();
|
pairs = await ConfigEntity.find();
|
||||||
config = pairsToConfig(pairs);
|
config = pairsToConfig(pairs);
|
||||||
// TODO: this overwrites existing config values with defaults.
|
// TODO: this overwrites existing config values with defaults.
|
||||||
@ -26,16 +26,19 @@ export const Config = {
|
|||||||
if (Object.keys(config).length == 0) config = new ConfigValue();
|
if (Object.keys(config).length == 0) config = new ConfigValue();
|
||||||
|
|
||||||
if (process.env.CONFIG_PATH) {
|
if (process.env.CONFIG_PATH) {
|
||||||
console.log(`[Config] Using config path from environment rather than database.`);
|
console.log(
|
||||||
|
`[Config] Using config path from environment rather than database.`,
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const overrideConfig = JSON.parse(fs.readFileSync(overridePath, { encoding: "utf8" }));
|
const overrideConfig = JSON.parse(
|
||||||
|
fs.readFileSync(overridePath, { encoding: "utf8" }),
|
||||||
|
);
|
||||||
config = overrideConfig.merge(config);
|
config = overrideConfig.merge(config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
fs.writeFileSync(overridePath, JSON.stringify(config, null, 4));
|
fs.writeFileSync(overridePath, JSON.stringify(config, null, 4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return this.set(config);
|
return this.set(config);
|
||||||
},
|
},
|
||||||
get: function get() {
|
get: function get() {
|
||||||
|
@ -1043,7 +1043,7 @@ export const FosscordApiErrors = {
|
|||||||
45006,
|
45006,
|
||||||
501,
|
501,
|
||||||
),
|
),
|
||||||
FEATURE_IS_IMMUTABLE : new ApiError(
|
FEATURE_IS_IMMUTABLE: new ApiError(
|
||||||
"The feature ({}) cannot be edited.",
|
"The feature ({}) cannot be edited.",
|
||||||
45007,
|
45007,
|
||||||
403,
|
403,
|
||||||
|
@ -37,7 +37,6 @@ const DataSourceOptions = new DataSource({
|
|||||||
migrations: [path.join(__dirname, "..", "migration", DatabaseType, "*.js")],
|
migrations: [path.join(__dirname, "..", "migration", DatabaseType, "*.js")],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Gets the existing database connection
|
// Gets the existing database connection
|
||||||
export function getDatabase(): DataSource | null {
|
export function getDatabase(): DataSource | null {
|
||||||
// if (!dbConnection) throw new Error("Tried to get database before it was initialised");
|
// if (!dbConnection) throw new Error("Tried to get database before it was initialised");
|
||||||
@ -60,8 +59,13 @@ export async function initDatabase(): Promise<DataSource> {
|
|||||||
if (!process.env.DB_SYNC) {
|
if (!process.env.DB_SYNC) {
|
||||||
const supported = ["mysql", "mariadb", "postgres", "sqlite"];
|
const supported = ["mysql", "mariadb", "postgres", "sqlite"];
|
||||||
if (!supported.includes(DatabaseType)) {
|
if (!supported.includes(DatabaseType)) {
|
||||||
console.log("[Database]" + red(` We don't have migrations for DB type '${DatabaseType}'` +
|
console.log(
|
||||||
` To ignore, set DB_SYNC=true in your env. https://docs.fosscord.com/setup/server/configuration/env/`));
|
"[Database]" +
|
||||||
|
red(
|
||||||
|
` We don't have migrations for DB type '${DatabaseType}'` +
|
||||||
|
` To ignore, set DB_SYNC=true in your env. https://docs.fosscord.com/setup/server/configuration/env/`,
|
||||||
|
),
|
||||||
|
);
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,12 +75,20 @@ export async function initDatabase(): Promise<DataSource> {
|
|||||||
dbConnection = await DataSourceOptions.initialize();
|
dbConnection = await DataSourceOptions.initialize();
|
||||||
|
|
||||||
// Crude way of detecting if the migrations table exists.
|
// Crude way of detecting if the migrations table exists.
|
||||||
const dbExists = async () => { try { await ConfigEntity.count(); return true; } catch (e) { return false; } };
|
const dbExists = async () => {
|
||||||
if (!await dbExists()) {
|
try {
|
||||||
console.log("[Database] This appears to be a fresh database. Synchronising.");
|
await ConfigEntity.count();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!(await dbExists())) {
|
||||||
|
console.log(
|
||||||
|
"[Database] This appears to be a fresh database. Synchronising.",
|
||||||
|
);
|
||||||
await dbConnection.synchronize();
|
await dbConnection.synchronize();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
await dbConnection.runMigrations();
|
await dbConnection.runMigrations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// List from https://invisible-characters.com/
|
// List from https://invisible-characters.com/
|
||||||
export const InvisibleCharacters = [
|
export const InvisibleCharacters = [
|
||||||
"\u{9}", //Tab
|
"\u{9}", //Tab
|
||||||
'\u{c}', //Form feed
|
"\u{c}", //Form feed
|
||||||
//'\u{20}', //Space //categories can have spaces in them
|
//'\u{20}', //Space //categories can have spaces in them
|
||||||
"\u{ad}", //Soft hyphen
|
"\u{ad}", //Soft hyphen
|
||||||
//"\u{34f}", //Combining grapheme joiner
|
//"\u{34f}", //Combining grapheme joiner
|
||||||
|
@ -32,7 +32,7 @@ export class Rights extends BitField {
|
|||||||
static FLAGS = {
|
static FLAGS = {
|
||||||
OPERATOR: BitFlag(0), // has all rights
|
OPERATOR: BitFlag(0), // has all rights
|
||||||
MANAGE_APPLICATIONS: BitFlag(1),
|
MANAGE_APPLICATIONS: BitFlag(1),
|
||||||
MANAGE_GUILDS: BitFlag(2), // Manage all guilds instance-wide
|
MANAGE_GUILDS: BitFlag(2), // Manage all guilds instance-wide
|
||||||
MANAGE_MESSAGES: BitFlag(3), // Can't see other messages but delete/edit them in channels that they can see
|
MANAGE_MESSAGES: BitFlag(3), // Can't see other messages but delete/edit them in channels that they can see
|
||||||
MANAGE_RATE_LIMITS: BitFlag(4),
|
MANAGE_RATE_LIMITS: BitFlag(4),
|
||||||
MANAGE_ROUTING: BitFlag(5), // can create custom message routes to any channel/guild
|
MANAGE_ROUTING: BitFlag(5), // can create custom message routes to any channel/guild
|
||||||
@ -81,7 +81,7 @@ export class Rights extends BitField {
|
|||||||
SELF_EDIT_FLAGS: BitFlag(45), // can modify own flags
|
SELF_EDIT_FLAGS: BitFlag(45), // can modify own flags
|
||||||
EDIT_FLAGS: BitFlag(46), // can set others' flags
|
EDIT_FLAGS: BitFlag(46), // can set others' flags
|
||||||
MANAGE_GROUPS: BitFlag(47), // can manage others' groups
|
MANAGE_GROUPS: BitFlag(47), // can manage others' groups
|
||||||
VIEW_SERVER_STATS: BitFlag(48) // added per @chrischrome's request — can view server stats)
|
VIEW_SERVER_STATS: BitFlag(48), // added per @chrischrome's request — can view server stats)
|
||||||
};
|
};
|
||||||
|
|
||||||
any(permission: RightResolvable, checkOperator = true) {
|
any(permission: RightResolvable, checkOperator = true) {
|
||||||
|
Loading…
Reference in New Issue
Block a user