Move src-slowcord to own repo https://github.com/MaddyUnderStars/slowcord-services
This commit is contained in:
parent
c00c70985c
commit
e991e00f32
@ -1 +0,0 @@
|
||||
Additional resources/services for [Slowcord](https://slowcord.maddy.k.vu/login)
|
||||
12
src-slowcord/bot/.vscode/launch.json
vendored
12
src-slowcord/bot/.vscode/launch.json
vendored
@ -1,12 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Slowcord Bot",
|
||||
"program": "${workspaceFolder}/build/index.js",
|
||||
"request": "launch",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"type": "node",
|
||||
"preLaunchTask": "npm: build"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
src-slowcord/bot/.vscode/tasks.json
vendored
13
src-slowcord/bot/.vscode/tasks.json
vendored
@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: build",
|
||||
"detail": "tsc -b"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
src-slowcord/bot/package-lock.json
generated
BIN
src-slowcord/bot/package-lock.json
generated
Binary file not shown.
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
@ -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);
|
||||
})();
|
||||
@ -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. */
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
DATABASE=../bundle/database.db
|
||||
PORT=3010
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_SECRET=
|
||||
DISCORD_REDIRECT=
|
||||
BIN
src-slowcord/login/package-lock.json
generated
BIN
src-slowcord/login/package-lock.json
generated
Binary file not shown.
@ -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"
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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";
|
||||
};
|
||||
@ -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 <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>
|
||||
@ -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>
|
||||
@ -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}`);
|
||||
});
|
||||
})();
|
||||
@ -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. */
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -1,6 +0,0 @@
|
||||
DATABASE=
|
||||
INSTANCE_API=
|
||||
INSTANCE_CDN=
|
||||
INSTANCE_TOKEN=
|
||||
MEASURE_INTERVAL=1000
|
||||
RETENTION_DAYS=30
|
||||
BIN
src-slowcord/status/package-lock.json
generated
BIN
src-slowcord/status/package-lock.json
generated
Binary file not shown.
@ -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"
|
||||
}
|
||||
@ -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);
|
||||
})();
|
||||
@ -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();
|
||||
@ -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. */
|
||||
}
|
||||
}
|
||||
@ -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)/,
|
||||
];
|
||||
|
||||
38
src/api/routes/oauth2/callback.ts
Normal file
38
src/api/routes/oauth2/callback.ts
Normal 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;
|
||||
21
src/api/routes/policies/instance/stats.ts
Normal file
21
src/api/routes/policies/instance/stats.ts
Normal 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;
|
||||
83
src/api/util/handlers/Oauth.ts
Normal file
83
src/api/util/handlers/Oauth.ts
Normal 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 };
|
||||
@ -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";
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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; }[];
|
||||
};
|
||||
}[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user