This commit is contained in:
Madeline 2022-10-28 15:25:58 +11:00
parent c00c70985c
commit e991e00f32
35 changed files with 185 additions and 1519 deletions

View File

@ -1 +0,0 @@
Additional resources/services for [Slowcord](https://slowcord.maddy.k.vu/login)

View File

@ -1,12 +0,0 @@
{
"configurations": [
{
"name": "Slowcord Bot",
"program": "${workspaceFolder}/build/index.js",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node",
"preLaunchTask": "npm: build"
}
]
}

View File

@ -1,13 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": "build",
"problemMatcher": [],
"label": "npm: build",
"detail": "tsc -b"
}
]
}

Binary file not shown.

View File

@ -1,18 +0,0 @@
{
"name": "bot",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc -b",
"start": "node build/index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"fosscord-server": "file:../..",
"fosscord-gopnik": "^1.0.0",
"mysql": "^2.18.1",
"typescript": "^4.7.4"
}
}

View File

@ -1,50 +0,0 @@
import { Message } from "discord.js";
import { Client } from "fosscord-gopnik/build/lib"; // huh? oh well. some bugs in my lib Ig
import { Command, getCommands } from "./commands/index.js";
export default class Bot {
client: Client;
commands: { [key: string]: Command } = {};
constructor(client: Client) {
this.client = client;
}
onReady = async () => {
this.commands = await getCommands();
console.log(`Logged in as ${this.client.user!.tag}`);
this.client.user!.setPresence({
activities: [
{
name: "EVERYTHING",
type: "WATCHING",
},
],
});
};
onMessageCreate = async (msg: Message) => {
const prefix = process.env.PREFIX as string;
if (msg.author.bot) return;
if (!msg.content || msg.content.indexOf(prefix) === -1) return;
const content = msg.content.slice(prefix.length).split(" ");
const cmd = content.shift();
if (!cmd) return;
const args = content;
const command = this.commands[cmd];
if (!command) return;
await command.exec({
user: msg.author,
member: msg.member,
guild: msg.guild,
message: msg,
args: args,
});
};
}

View File

@ -1,36 +0,0 @@
import { Message, GuildMember, Guild, User } from "discord.js";
import fs from "fs";
export type CommandContext = {
user: User;
guild: Guild | null;
member: GuildMember | null;
message: Message;
args: string[];
};
export type Command = {
name: string;
exec: (ctx: CommandContext) => any;
};
const walk = async (path: string) => {
const files = fs.readdirSync(path);
const out = [];
for (var file of files) {
if (fs.statSync(`${path}/${file}`).isDirectory()) continue;
if (file.indexOf("index") !== -1) continue;
if (file.indexOf(".js") !== file.length - 3) continue;
var imported = (await import(`./${file}`)).default;
out.push(imported);
}
return out;
};
export const getCommands = async () => {
const map: { [key: string]: Command } = {};
for (var cmd of await walk("./build/commands")) {
map[cmd.name] = cmd;
}
return map;
};

View File

@ -1,56 +0,0 @@
import { Command } from "./index.js";
import { User, Guild, Message } from "@fosscord/util";
const cache: { [key: string]: number } = {
users: 0,
guilds: 0,
messages: 0,
lastChecked: 0,
};
export default {
name: "instance",
exec: async ({ message }) => {
if (
Date.now() >
cache.lastChecked + parseInt(process.env.CACHE_TTL as string)
) {
cache.users = await User.count();
cache.guilds = await Guild.count();
cache.messages = await Message.count();
cache.lastChecked = Date.now();
}
return message.reply({
embeds: [
{
title: "Instance Stats",
description:
"For more indepth information, check out https://grafana.understars.dev",
footer: {
text: `Last checked: ${Math.floor(
(Date.now() - cache.lastChecked) / (1000 * 60),
)} minutes ago`,
},
fields: [
{
inline: true,
name: "Total Users",
value: cache.users.toString(),
},
{
inline: true,
name: "Total Guilds",
value: cache.guilds.toString(),
},
{
inline: true,
name: "Total Messages",
value: cache.messages.toString(),
},
],
},
],
});
},
} as Command;

View File

@ -1,24 +0,0 @@
import "dotenv/config";
import Fosscord from "fosscord-gopnik";
import Bot from "./Bot.js"; // huh?
import { initDatabase } from "fosscord-server/src/util";
const client = new Fosscord.Client({
intents: ["GUILD_MESSAGES"],
http: {
api: process.env.ENDPOINT_API,
cdn: process.env.ENDPOINT_CDN,
invite: process.env.ENDPOINT_INV,
},
});
const bot = new Bot(client);
client.on("ready", bot.onReady);
client.on("messageCreate", bot.onMessageCreate);
(async () => {
await initDatabase();
await client.login(process.env.TOKEN);
})();

View File

@ -1,103 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"ES2021"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "CommonJS" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
"strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */,
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@ -1,5 +0,0 @@
DATABASE=../bundle/database.db
PORT=3010
DISCORD_CLIENT_ID=
DISCORD_SECRET=
DISCORD_REDIRECT=

Binary file not shown.

View File

@ -1,35 +0,0 @@
{
"name": "slowcord-login",
"version": "1.0.0",
"description": "Slowcord login service",
"main": "build/index.js",
"types": "src/index.ts",
"scripts": {
"build": "tsc -b",
"start": "node build/index.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/maddyunderstars/fosscord-server.git"
},
"author": "MaddyUnderStars",
"license": "AGPL-3.0-only",
"bugs": {
"url": "https://github.com/maddyunderstars/fosscord-server/issues"
},
"homepage": "https://github.com/maddyunderstars/fosscord-server#readme",
"dependencies": {
"fosscord-server": "file:../../",
"cookie-parser": "^1.4.6",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"node-fetch": "^3.2.6",
"sqlite3": "^5.0.8"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13",
"@types/node": "^18.0.0"
},
"type": "module"
}

View File

@ -1,113 +0,0 @@
/* Can you tell I like flexbox? */
html {
--background-primary: rgb(22, 23, 25);
--background-secondary: rgb(15, 16, 18);
--foreground-primary: rgb(200, 200, 200);
--background-login-discord: #5865f2;
background: url("https://slowcord.maddy.k.vu/assets/background.png");
background-size: 100% 100%;
background-repeat: no-repeat;
font-family: "Montserrat", sans-serif;
color: var(--foreground-primary);
}
* {
margin: 0;
padding: 0;
}
.content {
display: flex;
width: 100vw;
height: 100vh;
justify-content: flex-start;
align-items: center;
}
.login {
height: 100%;
width: 25%;
min-width: 400px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: var(--background-primary);
padding: 0 50px 0 50px;
}
.header {
margin: 40px;
width: 100%;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
}
.header-subtext {
text-align: center;
}
.header-subtext a,
.header-subtext p {
display: inline-block;
margin: 0 10px 0 10px;
}
a {
color: var(--foreground-primary);
}
form {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
min-height: 50%;
}
input,
form a {
background-color: var(--background-secondary);
padding: 10px;
margin: 5px 0 5px 0;
outline: none;
border: 1px solid grey;
color: var(--foreground-primary);
text-decoration: none;
cursor: pointer;
}
form a {
text-align: center;
}
label {
text-transform: uppercase;
font-size: 0.75rem;
font-weight: bold;
}
#loginDiscord {
background-color: var(--background-login-discord);
}
#failure {
width: 100%;
margin-top: 10px;
color: rgb(200, 20, 20);
display: none;
}
.h-captcha {
display: flex;
justify-content: center;
margin-top: 10px;
}

View File

@ -1,40 +0,0 @@
const handleSubmit = async (path, body) => {
const failureMessage = document.getElementById("failure");
var response = await fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const json = await response.json();
if (json.token) {
window.localStorage.setItem("token", `"${json.token}"`);
window.location.href = "/app";
return;
}
if (json.ticket) {
// my terrible solution to 2fa
const twoFactorForm = document.forms["2fa"];
const loginForm = document.forms["login"];
twoFactorForm.style.display = "flex";
loginForm.style.display = "none";
twoFactorForm.ticket.value = json.ticket;
return;
}
// Very fun error message here lol
const error = json.errors
? Object.values(json.errors)[0]._errors[0].message
: json.captcha_key
? "Captcha required"
: json.message;
failureMessage.innerHTML = error;
failureMessage.style.display = "block";
};

View File

@ -1,127 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Slowcord</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="./css/index.css" />
<script src="js/handler.js"></script>
</head>
<body>
<div class="content">
<div class="login">
<div class="header">
<h1>Welcome to Slowcord</h1>
<div class="header-subtext">
<p>Glad to see you &lt;3</p>
<a href="/register">Wait, I'm new!</a>
</div>
<p id="failure">Login failed</p>
</div>
<form action="javascript:void(0);" name="login">
<label for="email">Email</label>
<input type="email" name="email" />
<label for="password">Password</label>
<input type="password" name="password" />
<input type="submit" value="Login" />
<a
id="loginDiscord"
class="oauth"
href="https://discord.com/api/oauth2/authorize?client_id=991688571415175198&redirect_uri=https%3A%2F%2Fslowcord.maddy.k.vu%2Foauth%2Fdiscord&response_type=code&scope=identify%20email"
>
Login with Discord
</a>
<div
class="h-captcha"
data-sitekey="fa3163ea-79a7-4b7b-b752-b58c545906c8"
data-theme="dark"
></div>
<script
src="https://js.hcaptcha.com/1/api.js"
async
defer
></script>
</form>
<form
action="javascript:void(0);"
name="2fa"
style="display: none"
>
<label for="code">2FA Code</label>
<input type="number" name="code" />
<input type="hidden" name="ticket" />
<input type="submit" value="Login" />
</form>
</div>
</div>
<script>
/* https://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript */
const getCookieValue = (name) =>
document.cookie
.match("(^|;)\\s*" + name + "\\s*=\\s*([^;]+)")
?.pop() || "";
let token = getCookieValue("token");
if (token.trim().length) {
/* https://stackoverflow.com/a/27374365 */
// why is clearing cookies so weird? wtf
document.cookie.split(";").forEach(function (c) {
document.cookie = c
.replace(/^ +/, "")
.replace(
/=.*/,
"=;expires=" + new Date().toUTCString() + ";path=/",
);
});
window.localStorage.setItem("token", `"${token}"`);
window.location.href = "/app";
}
token = window.localStorage.getItem("token");
if (token) window.location.href = "/app";
document.forms["login"].addEventListener("submit", async (e) => {
const data = new FormData(e.target);
const email = data.get("email");
const password = data.get("password");
const hcaptcha = data.get("h-captcha-response");
await handleSubmit("/api/v9/auth/login", {
login: email,
password: password,
captcha_key: hcaptcha,
});
});
document.forms["2fa"].addEventListener("submit", async (e) => {
const data = new FormData(e.target);
const code = data.get("code");
const ticket = data.get("ticket");
await handleSubmit("/api/v9/auth/mfa/totp", {
code: code,
ticket: ticket,
});
});
</script>
</body>
</html>

View File

@ -1,88 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Slowcord</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="./css/index.css" />
<script src="js/handler.js"></script>
</head>
<body>
<div class="content">
<div class="login">
<div class="header">
<h1>Welcome to Slowcord</h1>
<div class="header-subtext">
<p>You're new?</p>
<a href="/login">Actually, I'm not!</a>
</div>
<p id="failure">Register failed</p>
</div>
<form action="javascript:void(0);">
<label for="email">Email</label>
<input type="email" name="email" />
<label for="username">Username</label>
<input type="username" name="username" />
<label for="password">Password</label>
<input type="password" name="password" />
<label for="dob">Date of Birth</label>
<input type="date" name="dob" />
<input type="submit" value="Register" />
<a
id="loginDiscord"
class="oauth"
href="https://discord.com/api/oauth2/authorize?client_id=991688571415175198&redirect_uri=https%3A%2F%2Fslowcord.maddy.k.vu%2Foauth%2Fdiscord&response_type=code&scope=identify%20email"
>
Login with Discord
</a>
<div
class="h-captcha"
data-sitekey="fa3163ea-79a7-4b7b-b752-b58c545906c8"
></div>
<script
src="https://js.hcaptcha.com/1/api.js"
async
defer
></script>
</form>
</div>
</div>
<script>
document.forms[0].addEventListener("submit", async (e) => {
const data = new FormData(e.target);
const email = data.get("email");
const username = data.get("username");
const password = data.get("password");
const dob = data.get("dob");
const hcaptcha = data.get("h-captcha-response");
await handleSubmit("/api/v9/auth/register", {
consent: true,
email: email,
username: username,
password: password,
date_of_birth: dob,
captcha_key: hcaptcha,
});
});
</script>
</body>
</html>

View File

@ -1,171 +0,0 @@
import "dotenv/config";
import express, { Request, Response } from "express";
import cookieParser from "cookie-parser";
import {
initDatabase,
generateToken,
User,
Config,
handleFile,
} from "fosscord-server/src/util";
import path from "path";
import fetch from "node-fetch";
// apparently dirname doesn't exist in modules, nice
/* https://stackoverflow.com/a/62892482 */
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
app.use(cookieParser());
const port = process.env.PORT;
// ip -> unix epoch that requests will be accepted again
const rateLimits: { [ip: string]: number } = {};
const allowRequestsEveryMs = 0.5 * 1000; // every half second
const allowedRequestsPerSecond = 50;
let requestsThisSecond = 0;
setInterval(() => {
requestsThisSecond = 0;
}, 1000);
const toDataURL = async (url: string) => {
const response = await fetch(url);
const blob = await response.blob();
const buffer = Buffer.from(await blob.text());
return `data:${blob.type};base64,${buffer.toString("base64")}`;
};
class Discord {
static getAccessToken = async (req: Request, res: Response) => {
const { code } = req.query;
const body = new URLSearchParams(
Object.entries({
client_id: process.env.DISCORD_CLIENT_ID as string,
client_secret: process.env.DISCORD_SECRET as string,
redirect_uri: process.env.DISCORD_REDIRECT as string,
code: code as string,
grant_type: "authorization_code",
}),
).toString();
const resp = await fetch("https://discord.com/api/oauth2/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: body,
});
const json = (await resp.json()) as any;
if (json.error) return null;
return {
access_token: json.access_token,
token_type: json.token_type,
expires_in: json.expires_in,
refresh_token: json.refresh_token,
scope: json.scope,
};
};
static getUserDetails = async (token: string) => {
const resp = await fetch("https://discord.com/api/users/@me", {
headers: {
Authorization: `Bearer ${token}`,
},
});
const json = (await resp.json()) as any;
if (!json.username || !json.email) return null; // eh, deal with bad code later
return {
id: json.id,
email: json.email,
username: json.username,
avatar_url: json.avatar
? `https://cdn.discordapp.com/avatars/${json.id}/${json.avatar}?size=2048`
: null,
};
};
}
const handlers: { [key: string]: any } = {
discord: Discord,
};
app.get("/oauth/:type", async (req, res) => {
requestsThisSecond++;
if (requestsThisSecond > allowedRequestsPerSecond)
return res.sendStatus(429);
const ip =
(req.headers["x-forwarded-for"] as string) ||
(req.socket.remoteAddress as string);
console.log(`${ip}`);
if (!rateLimits[ip]) {
rateLimits[ip] = Date.now() + allowRequestsEveryMs;
} else if (rateLimits[ip] > Date.now()) {
rateLimits[ip] += allowRequestsEveryMs;
console.log(
`${new Date()} : user ${ip} was timed out for ${
(rateLimits[ip] - Date.now()) / 1000
}s`,
);
return res.sendStatus(429);
} else {
delete rateLimits[ip];
}
const { type } = req.params;
const handler = handlers[type];
if (!type || !handler) return res.sendStatus(400);
const data = await handler.getAccessToken(req, res);
if (!data) return res.sendStatus(500);
const details = await handler.getUserDetails(data.access_token);
if (!details) return res.sendStatus(500);
let user = await User.findOne({ where: { email: details.email } });
if (!user) {
user = await User.register({
email: details.email,
username: details.username,
req,
});
if (details.avatar_url) {
try {
const avatar = await handleFile(
`/avatars/${user.id}`,
(await toDataURL(details.avatar_url)) as string,
);
user.avatar = avatar;
await user.save();
} catch (e) {
console.error(e);
}
}
}
const token = await generateToken(user.id);
res.cookie("token", token);
res.sendFile(path.join(__dirname, "../public/login.html"));
});
app.use(express.static("public", { extensions: ["html"] }));
(async () => {
await initDatabase();
await Config.init();
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
})();

View File

@ -1,99 +0,0 @@
{
"exclude": ["node_modules"],
"include": ["src/**/*.ts"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"ES2021"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "ES2020" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
"types": [
"node"
] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
"strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */,
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@ -1,54 +0,0 @@
server {
server_name slowcord.understars.dev;
client_max_body_size 150M;
add_header Last-Modified $date_gmt;
proxy_set_header Host $host;
proxy_pass_request_headers on;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
location / {
proxy_pass http://127.0.0.1:3001;
}
location /api {
proxy_pass http://127.0.0.1:3001;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
# TODO: This is a bad solution. Why does proxy_pass not forward all upstream errors to client?
# Or is it just that the server is not actually responding?
proxy_no_cache 1;
proxy_cache_bypass 1;
proxy_connect_timeout 1;
proxy_send_timeout 1;
proxy_read_timeout 1;
send_timeout 1;
}
# TODO: Make the login service not suck
location ~ ^/(login|register|oauth/discord|css/index.css|js/handler.js) {
proxy_pass http://127.0.0.1:3010;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/slowcord.understars.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/slowcord.understars.dev/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = slowcord.understars.dev) {
return 301 https://$host$request_uri;
}
listen 80;
server_name slowcord.understars.dev;
return 404;
}

View File

@ -1,35 +0,0 @@
server {
server_name voice.slowcord.understars.dev;
client_max_body_size 50M;
add_header Last-Modified $date_gmt;
proxy_set_header Host $host;
proxy_pass_request_headers on;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
location / {
proxy_pass http://127.0.0.1:3004;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/voice.slowcord.understars.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/voice.slowcord.understars.dev/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = voice.slowcord.understars.dev) {
return 301 https://$host$request_uri;
}
listen 80;
server_name voice.slowcord.understars.dev;
return 404;
}

View File

@ -1,22 +0,0 @@
# Hi, welcome to Slowcord!
Slowcord is a heavily modded Fosscord instance. You can browse it's source here: https://github.com/MaddyUnderStars/fosscord-server/tree/slowcord
## Here are some general instance-wide rules:
- **Harassment, homophobia, transphobia, etc, violence, and hate speech are forbidden.**
- Behaviour that harms the service - be it malicious/intentional or not - is strictly forbidden. This may include API abuse/spam, exploits, etc.
- - If you do discover an exploit/bug, it would be greatly appreciated if you could create an issue in the above repo, or DM @MaddyUnderStars#0000.
- Any content that would be considered illegal in Australia is also forbidden. Additionally, if it is illegal in your own country, it shouldn't be here.
- Bots/selfbots are allowed. If you would like an account to be given bot status, DM @MaddyUnderStars#0000.
These rules are non-exhaustive, but should give a good idea of what will be enforced.
Permanent Slowcord guild invite: https://slowcord.understars.dev/invite/slowcord
### If a message or user breaks these rules, you can report it here: https://forms.gle/sd6RkdM7gRgJLV368
#### Lastly ( and not rules ):
- If you use BetterDiscord or Powercord, and want an easier time accessing Slowcord and other Fosscord instances, check out https://github.com/maddyunderstars/fosscord-bd!
- Also, if you're on Android, you can download the mobile client at https://slowcord.understars.dev/assets/slowcord.apk

View File

@ -1,6 +0,0 @@
DATABASE=
INSTANCE_API=
INSTANCE_CDN=
INSTANCE_TOKEN=
MEASURE_INTERVAL=1000
RETENTION_DAYS=30

Binary file not shown.

View File

@ -1,31 +0,0 @@
{
"name": "slowcord-status",
"version": "1.0.0",
"description": "Slowcord status service",
"main": "build/index.js",
"scripts": {
"build": "tsc -b",
"start": "node build/index.js",
"start:gateway": "node build/gateway.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/maddyunderstars/fosscord-server.git"
},
"bugs": {
"url": "https://github.com/maddyunderstars/fosscord-server/issues"
},
"homepage": "https://github.com/maddyunderstars/fosscord-server#readme",
"author": "MaddyUnderStars",
"license": "AGPL-3.0-only",
"devDependencies": {
"@types/node": "^18.0.6"
},
"dependencies": {
"dotenv": "^16.0.1",
"fosscord-gopnik": "^1.0.0",
"mysql2": "^2.3.3",
"node-fetch": "^3.2.9"
},
"type": "module"
}

View File

@ -1,99 +0,0 @@
import "dotenv/config";
import Fosscord from "fosscord-gopnik";
import Discord from "discord.js";
import mysql from "mysql2";
import fetch from "node-fetch";
const dbConn = mysql.createConnection(process.env.DATABASE as string);
const executePromise = (sql: string, args: any[]) =>
new Promise((resolve, reject) =>
dbConn.execute(sql, args, (err, res) => {
if (err) reject(err);
else resolve(res);
}),
);
const savePerf = async (time: number, name: string, error?: string | Error) => {
if (error && typeof error != "string") error = error.message;
try {
await executePromise(
"INSERT INTO performance (value, endpoint, timestamp, error) VALUES (?, ?, ?, ?)",
[time ?? 0, name, new Date(), error ?? null],
);
// await executePromise("DELETE FROM performance WHERE DATE(timestamp) < now() - interval ? DAY", [process.env.RETENTION_DAYS]);
} catch (e) {
console.error(e);
}
};
var timestamp: number | undefined;
const doMeasurements = async (channel: Discord.TextChannel) => {
timestamp = Date.now();
await channel.send("hello this is a special message kthxbye");
setTimeout(
doMeasurements,
parseInt(process.env.MEASURE_INTERVAL as string),
channel,
);
};
const instance = {
app: process.env.INSTANCE_WEB_APP as string,
api: process.env.INSTANCE_API as string,
cdn: process.env.INSTANCE_CDN as string,
token: process.env.INSTANCE_TOKEN as string,
};
const client = new Fosscord.Client({
intents: [],
http: {
api: instance.api,
cdn: instance.cdn,
},
});
client.on("ready", async () => {
console.log(`Ready on gateway as ${client.user!.tag}`);
const channel = await client.channels.fetch("1019955729054267764");
if (!channel) return;
doMeasurements(channel as Discord.TextChannel);
});
client.on("messageCreate", async (msg: Discord.Message) => {
if (!timestamp) return;
if (
msg.author.id != "992745947417141682" ||
msg.channel.id != "1019955729054267764" ||
msg.content != "hello this is a special message kthxbye"
)
return;
await savePerf(Date.now() - timestamp, "messageCreate", undefined);
timestamp = undefined;
await fetch(
`${instance.api}/channels/1019955729054267764/messages/${msg.id}`,
{
method: "DELETE",
headers: {
authorization: instance.token,
},
},
);
});
client.on("error", (error: any) => {
console.log(`Gateway error`, error);
});
client.on("warn", (msg: any) => {
console.log(`Gateway warning:`, msg);
});
(async () => {
await new Promise((resolve) => dbConn.connect(resolve));
console.log("Connected to db");
await client.login(instance.token);
})();

View File

@ -1,159 +0,0 @@
import "dotenv/config";
import https from "https";
import mysql from "mysql2";
import fetch from "node-fetch";
const dbConn = mysql.createConnection(process.env.DATABASE as string);
const executePromise = (sql: string, args: any[]) =>
new Promise((resolve, reject) =>
dbConn.execute(sql, args, (err, res) => {
if (err) reject(err);
else resolve(res);
}),
);
const instance = {
app: process.env.INSTANCE_WEB_APP as string,
api: process.env.INSTANCE_API as string,
cdn: process.env.INSTANCE_CDN as string,
token: process.env.INSTANCE_TOKEN as string,
};
const savePerf = async (time: number, name: string, error?: string | Error) => {
if (error && typeof error != "string") error = error.message;
try {
await executePromise(
"INSERT INTO performance (value, endpoint, timestamp, error) VALUES (?, ?, ?, ?)",
[time ?? 0, name, new Date(), error ?? null],
);
// await executePromise("DELETE FROM performance WHERE DATE(timestamp) < now() - interval ? DAY", [process.env.RETENTION_DAYS]);
} catch (e) {
console.error(e);
}
};
const saveSystemUsage = async (
load: number,
procUptime: number,
sysUptime: number,
ram: number,
sessions: number,
) => {
try {
await executePromise(
"INSERT INTO monitor (time, cpu, procUp, sysUp, ram, sessions) VALUES (?, ?, ?, ?, ?, ?)",
[new Date(), load, procUptime, sysUptime, ram, sessions],
);
} catch (e) {
console.error(e);
}
};
const makeTimedRequest = (path: string, body?: object): Promise<number> =>
new Promise((resolve, reject) => {
const opts = {
hostname: new URL(path).hostname,
port: 443,
path: new URL(path).pathname,
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: instance.token,
},
timeout: 1000,
};
let start: number, end: number;
const req = https.request(opts, (res) => {
if (res.statusCode! < 200 || res.statusCode! > 300) {
return reject(`${res.statusCode} ${res.statusMessage}`);
}
res.on("data", (data) => {});
res.on("end", () => {
end = Date.now();
resolve(end - start);
});
});
req.on("finish", () => {
if (body) req.write(JSON.stringify(body));
start = Date.now();
});
req.on("error", (error) => {
reject(error);
});
req.end();
});
const measureApi = async (name: string, path: string, body?: object) => {
let error,
time = -1;
try {
time = await makeTimedRequest(path, body);
} catch (e) {
error = e as Error | string;
}
console.log(
`${name} took ${time}ms ${error ? "with error" : ""}`,
error ?? "",
);
await savePerf(time, name, error);
};
interface monitorzSchema {
load: number[];
procUptime: number;
sysUptime: number;
memPercent: number;
sessions: number;
}
const app = async () => {
await new Promise((resolve) => dbConn.connect(resolve));
console.log("Connected to db");
// await client.login(instance.token);
console.log(
`Monitoring performance for instance at ${
new URL(instance.api).hostname
}`,
);
const doMeasurements = async () => {
await measureApi("ping", `${instance.api}/ping`);
await measureApi("users/@me", `${instance.api}/users/@me`);
await measureApi("login", `${instance.app}/login`);
// await gatewayMeasure("websocketPing");
try {
const res = await fetch(`${instance.api}/-/monitorz`, {
headers: {
Authorization: process.env.INSTANCE_TOKEN as string,
},
});
const json = (await res.json()) as monitorzSchema;
await saveSystemUsage(
json.load[1],
json.procUptime,
json.sysUptime,
json.memPercent,
json.sessions,
);
} catch (e) {}
setTimeout(
doMeasurements,
parseInt(process.env.MEASURE_INTERVAL as string),
);
};
doMeasurements();
};
app();

View File

@ -1,99 +0,0 @@
{
"exclude": ["node_modules"],
"include": ["src/**/*.ts"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"ES2021"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "ES2020" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
"types": [
"node"
] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
"strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */,
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@ -25,6 +25,8 @@ export const NO_AUTHORIZATION_ROUTES = [
"/track",
// Public policy pages
"/policies/instance",
// Oauth callback
"/oauth2/callback",
// Asset delivery
/\/guilds\/\d+\/widget\.(json|png)/,
];

View File

@ -0,0 +1,38 @@
import { Router, Request, Response } from "express";
import { route, OauthCallbackHandlers } from "@fosscord/api";
import { FieldErrors, generateToken, User } from "@fosscord/util";
const router = Router();
router.get("/:type", route({}), async (req: Request, res: Response) => {
const { type } = req.params;
const handler = OauthCallbackHandlers[type];
if (!handler) throw FieldErrors({
type: {
code: "BASE_TYPE_CHOICES",
message: `Value must be one of (${Object.keys(OauthCallbackHandlers).join(", ")}).`,
}
});
const { code } = req.query;
if (!code || typeof code !== "string") throw FieldErrors({ code: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED"), } });
const access = await handler.getAccessToken(code);
const oauthUser = await handler.getUserDetals(access.access_token);
let user = await User.findOne({ where: { email: oauthUser.email } });
if (!user) {
user = await User.register({
email: oauthUser.email,
username: oauthUser.username,
req
});
// TODO: upload pfp, banner?
}
const token = await generateToken(user.id);
return { token };
});
export default router;

View File

@ -0,0 +1,21 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
import { Attachment, Config, Guild, Message, RateLimit, Session, User } from "@fosscord/util";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
res.json({
all_time: {
users: await User.count(),
guilds: await Guild.count(),
messages: await Message.count(),
attachments: await Attachment.count(),
},
now: {
sessions: await Session.count(),
rate_limits: await RateLimit.count(),
}
});
});
export default router;

View File

@ -0,0 +1,83 @@
// TODO: Puyo's connections PR would replace this file
import { Config } from "@fosscord/util";
import fetch from "node-fetch";
export interface OauthAccessToken {
access_token: string;
token_type: string;
expires_in: string;
refresh_token: string;
scope: string;
};
export interface OauthUserDetails {
id: string;
email: string;
username: string;
avatar_url: string | null;
}
interface Connection {
getAccessToken: (code: string) => Promise<OauthAccessToken>;
getUserDetals: (token: string) => Promise<OauthUserDetails>;
}
const DiscordConnection: Connection = {
getAccessToken: async (code) => {
const { external } = Config.get();
const { discord } = external;
if (!discord.id || !discord.secret || !discord.redirect)
throw new Error("Discord Oauth has not been configured.")
const body = new URLSearchParams(
Object.entries({
client_id: discord.id as string,
client_secret: discord.secret as string,
redirect_uri: discord.redirect as string,
code: code as string,
grant_type: "authorization_code",
})
).toString();
const resp = await fetch("https://discord.com/api/oauth2/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: body,
});
if (resp.status !== 200) throw new Error(`Failed to get access token.`,);
const json = await resp.json();
return json;
},
getUserDetals: async (token) => {
const resp = await fetch("https://discord.com/api/users/@me", {
headers: {
Authorization: `Bearer ${token}`
},
});
const json = await resp.json();
if (!json.username || !json.email) throw new Error("Failed to get user details via oauth");
return {
id: json.id,
email: json.email,
username: json.username,
avatar_url: json.avatar
? `https://cdn.discordapp.com/avatars/${json.id}/${json.avatar}?size=2048`
: null,
};
}
};
const OauthCallbackHandlers: { [key: string]: Connection; } = {
discord: DiscordConnection
};
export { OauthCallbackHandlers };

View File

@ -7,4 +7,5 @@ export * from "./handlers/route";
export * from "./utility/String";
export * from "./handlers/Voice";
export * from "./utility/captcha";
export * from "./utility/EmbedHandlers";
export * from "./utility/EmbedHandlers";
export * from "./handlers/Oauth";

View File

@ -211,7 +211,12 @@ export interface ConfigValue {
};
external: {
twitter: string | null;
}
discord: {
id: string | null;
secret: string | null;
redirect: string | null;
};
};
}
export const DefaultConfigOptions: ConfigValue = {
@ -423,5 +428,10 @@ export const DefaultConfigOptions: ConfigValue = {
},
external: {
twitter: null,
discord: {
id: null,
secret: null,
redirect: null,
}
}
};

View File

@ -1,19 +1,26 @@
import { PublicUser, User, UserSettings } from "../entities/User";
import { Channel } from "../entities/Channel";
import { Guild } from "../entities/Guild";
import { Member, PublicMember, UserGuildSettings } from "../entities/Member";
import { Emoji } from "../entities/Emoji";
import { Role } from "../entities/Role";
import { Invite } from "../entities/Invite";
import { Message, PartialEmoji } from "../entities/Message";
import { VoiceState } from "../entities/VoiceState";
import { ApplicationCommand } from "../entities/Application";
import { Interaction } from "./Interaction";
import { ConnectedAccount } from "../entities/ConnectedAccount";
import { Relationship, RelationshipType } from "../entities/Relationship";
import { Presence } from "./Presence";
import { Sticker } from "..";
import { Activity, Status } from ".";
import {
RelationshipType,
ConnectedAccount,
Interaction,
ApplicationCommand,
VoiceState,
Message,
PartialEmoji,
Invite,
Role,
Emoji,
PublicMember,
UserGuildSettings,
Guild,
Channel,
PublicUser,
User,
Sticker,
Activity,
Status,
Presence,
UserSettings,
} from "@fosscord/util";
export interface Event {
guild_id?: string;
@ -73,9 +80,9 @@ export interface ReadyEventData {
number,
null,
number,
[[number, { e: number; s: number }[]]],
[[number, { e: number; s: number; }[]]],
[number, [[number, [number, number]]]],
{ b: number; k: bigint[] }[],
{ b: number; k: bigint[]; }[],
][];
guild_join_requests?: any[]; // ? what is this? this is new
shard?: [number, number];
@ -473,7 +480,7 @@ export interface SessionsReplace extends Event {
export interface GuildMemberListUpdate extends Event {
event: "GUILD_MEMBER_LIST_UPDATE";
data: {
groups: { id: string; count: number }[];
groups: { id: string; count: number; }[];
guild_id: string;
id: string;
member_count: number;
@ -481,8 +488,8 @@ export interface GuildMemberListUpdate extends Event {
ops: {
index: number;
item: {
member?: PublicMember & { presence: Presence };
group?: { id: string; count: number }[];
member?: PublicMember & { presence: Presence; };
group?: { id: string; count: number; }[];
};
}[];
};