1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-09-19 17:21:35 +02:00

Merge branch 'master' of https://github.com/DEVTomatoCake/spacebar-server into feat/webhooks-3

This commit is contained in:
TomatoCake 2024-07-18 14:41:14 +02:00
commit 128b81bc6a
140 changed files with 118820 additions and 11695 deletions

3
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,3 @@
# Nix stuff is owned by Rory& - we& want to be notified if these are changed.
/flake.nix root@rory.gay
/nix-update.sh root@rory.gay

18
.github/workflows/nix-build.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Nix build
on:
push:
pull_request:
jobs:
build-nix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v25
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: DeterminateSystems/magic-nix-cache-action@v2
- run: nix build -L
- run: nix develop --command echo OK

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ build
*.tmp
tmp/
dump/
result

View File

@ -1,4 +1,28 @@
#!/usr/bin/env sh
#!nix-shell -i "bash" -p bash prefetch-npm-deps jq nodejs nix-output-monitor
. "$(dirname -- "$0")/_/husky.sh"
# Check if nix is available
if [ -x "$(/usr/bin/env which nix-shell 2>/dev/null)" ]; then
# Check if we haven't re-executed ourselves yet
if [ ! "$HOOK_NIX_SHELL" ]; then
echo "Nix is available, updating nix flake..."
export HOOK_NIX_SHELL=1
nix-shell $0
exit $?
else
nix flake update
# run ./nix-update.sh if package lock has changed and has no unstaged changes
if [ -n "$(git status --porcelain=v1 2>/dev/null | grep -E '^(MM| M) package-lock.json')" ]; then
echo "package-lock.json has unstaged changes. Skipping update of nix dependencies."
elif [ ! -n "$(git status --porcelain=v1 2>/dev/null | grep -E '^M package-lock.json')" ]; then
echo "package-lock.json has no changes. Skipping update of nix dependencies."
else
./nix-update.sh || exit $?
fi
fi
else
echo "You do not appear to have nix installed. Skipping update of nix dependencies."
fi
npx -y lint-staged

View File

@ -1,7 +1,6 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<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" />
@ -23,32 +22,39 @@
width: 100%;
}
</style>
</head>
</head>
<body>
<div style="background-color: #202225;">
<img src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
alt="Branding" style="
<body>
<div style="background-color: #202225">
<img
src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
alt="Branding"
style="
width: 100%;
max-width: 200px;
margin: 0 auto;
display: block;
padding: 20px;
" />
<div style="
"
/>
<div
style="
width: 100%;
max-width: 500px;
margin: 0 auto;
padding: 40px 50px;
background-color: #32353b;
border-radius: 5px;
">
<p style="
"
>
<p
style="
font-weight: 600;
font-size: 20px;
letter-spacing: 0.27px;
line-height: 24px;
">
"
>
Hey {userUsername},
</p>
<p>
@ -65,38 +71,50 @@
{locationCountryName}
</p>
<div>
<div style="
<div
style="
text-align: center;
justify-content: center;
padding-bottom: 10px;
">
<a href="{actionUrl}" target="_blank" style="
"
>
<a
href="{actionUrl}"
target="_blank"
style="
font-size: 15px;
border: none;
border-radius: 3px;
text-decoration: none;
color: white;
cursor: pointer;
padding: 15px 19px;
background-color: #0185ff;
border-radius: 5px;
">Verify Login</a>
"
>Verify Login</a
>
</div>
<hr />
<div style="
<div
style="
text-align: center;
justify-content: center;
padding-bottom: 10px;
">
"
>
<p>
Alternatively, you can directly paste this link into
your browser:
</p>
<a href="{actionUrl}" target="_blank" style="word-wrap: break-word;">{actionUrl}</a>
<a
href="{actionUrl}"
target="_blank"
style="word-wrap: break-word"
>{actionUrl}</a
>
</div>
</div>
</div>
</div>
</body>
</body>
</html>

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@ -22,7 +22,7 @@
</style>
</head>
<body>
<div style="background-color: #202225;">
<div style="background-color: #202225">
<img
src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
alt="Branding"

View File

@ -1,7 +1,6 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<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" />
@ -23,32 +22,39 @@
width: 100%;
}
</style>
</head>
</head>
<body>
<div style="background-color: #202225;">
<img src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
alt="Branding" style="
<body>
<div style="background-color: #202225">
<img
src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
alt="Branding"
style="
width: 100%;
max-width: 200px;
margin: 0 auto;
display: block;
padding: 20px;
" />
<div style="
"
/>
<div
style="
width: 100%;
max-width: 500px;
margin: 0 auto;
padding: 40px 50px;
background-color: #32353b;
border-radius: 5px;
">
<p style="
"
>
<p
style="
font-weight: 600;
font-size: 20px;
letter-spacing: 0.27px;
line-height: 24px;
">
"
>
Hey {userUsername},
</p>
<p>
@ -57,22 +63,28 @@
ignore this email.
</p>
<div>
<div style="
<div
style="
text-align: center;
justify-content: center;
padding-bottom: 10px;
">
<a href="{actionUrl}" target="_blank" style="
"
>
<a
href="{actionUrl}"
target="_blank"
style="
font-size: 15px;
border: none;
border-radius: 3px;
text-decoration: none;
color: white;
cursor: pointer;
padding: 15px 19px;
background-color: #ff5f00;
border-radius: 5px;
">Reset Password</a>
"
>Reset Password</a
>
</div>
<hr />
<div style="text-align: center">
@ -80,11 +92,15 @@
Alternatively, you can directly paste this link into
your browser:
</p>
<a href="{actionUrl}" target="_blank" style="word-wrap: break-word;">{actionUrl}</a>
<a
href="{actionUrl}"
target="_blank"
style="word-wrap: break-word"
>{actionUrl}</a
>
</div>
</div>
</div>
</div>
</body>
</body>
</html>

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@ -22,7 +22,7 @@
</style>
</head>
<body>
<div style="background-color: #202225;">
<div style="background-color: #202225">
<img
src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
alt="Branding"

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@ -22,7 +22,7 @@
</style>
</head>
<body>
<div style="background-color: #202225;">
<div style="background-color: #202225">
<img
src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
alt="Branding"
@ -69,12 +69,11 @@
>
<a
class="btn"
href="{emailVerificationUrl}"
href="{actionUrl}"
target="_blank"
style="
font-size: 15px;
border: none;
border-radius: 3px;
text-decoration: none;
color: white;
cursor: pointer;
@ -91,8 +90,11 @@
Alternatively, you can directly paste this link into
your browser:
</p>
<a href="{emailVerificationUrl}" target="_blank" style="word-wrap: break-word;"
>{emailVerificationUrl}</a
<a
href="{actionUrl}"
target="_blank"
style="word-wrap: break-word"
>{actionUrl}</a
>
</div>
</div>

View File

@ -1,16 +1,24 @@
{
"login": {
"INVALID_LOGIN": "E-Mail or Phone not found",
"INVALID_PASSWORD": "Invalid Password",
"ACCOUNT_DISABLED": "This account is disabled"
"INVALID_LOGIN": "Ungültiger Benutzername/E-Mail oder Passwort.",
"INVALID_PASSWORD": "Ungültiges Passwort",
"ACCOUNT_DISABLED": "Dieser Account wurde deaktiviert",
"INVALID_TOTP_CODE": "Ungültiger Zwei-Faktor-Authentifierungs-Code.",
"INVALID_TOTP_SECRET": "Ungültiges Zwei-Faktor-Authentifierungs-Secret"
},
"register": {
"REGISTRATION_DISABLED": "New user registration is disabled",
"INVITE_ONLY": "You must be invited to register",
"EMAIL_INVALID": "Invalid Email",
"EMAIL_ALREADY_REGISTERED": "Email is already registered",
"DATE_OF_BIRTH_UNDERAGE": "You need to be {{years}} years or older",
"CONSENT_REQUIRED": "You must agree to the Terms of Service and Privacy Policy.",
"USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another"
"REGISTRATION_DISABLED": "Die Registrierung von neuen Benutzern ist deaktiviert",
"INVITE_ONLY": "Du musst eingeladen werden, um dich registrieren zu können",
"EMAIL_INVALID": "Ungültige E-Mail-Adresse",
"EMAIL_ALREADY_REGISTERED": "E-Mail-Adresse ist bereits registriert",
"DATE_OF_BIRTH_UNDERAGE": "Du musst mindestens {{years}} Jahre oder älter sein",
"PASSWORD_REQUIREMENTS_MIN_LENGTH": "Das Passwort muss mindestens {{min}} Zeichen lang sein.",
"CONSENT_REQUIRED": "Du musst den Nutzungsbedingungen und der Datenschutzerklärung zustimmen.",
"USERNAME_TOO_MANY_USERS": "Zu viele Nutzer haben diesen Benutzernamen, bitte probiere einen anderen",
"TOO_MANY_REGISTRATIONS": "Zu viele Registrierungen, bitte versuche es später erneut"
},
"password_reset": {
"EMAIL_DOES_NOT_EXIST": "E-Mail-Adresse existiert nicht.",
"INVALID_TOKEN": "Ungültiger Token."
}
}

View File

@ -1,6 +1,6 @@
{
"login": {
"INVALID_LOGIN": "E-Mail or Phone not found",
"INVALID_LOGIN": "Invalid login or password.",
"INVALID_PASSWORD": "Invalid Password",
"ACCOUNT_DISABLED": "This account is disabled",
"INVALID_TOTP_CODE": "Invalid two-factor code.",
@ -12,9 +12,9 @@
"EMAIL_INVALID": "Invalid Email",
"EMAIL_ALREADY_REGISTERED": "Email is already registered",
"DATE_OF_BIRTH_UNDERAGE": "You need to be {{years}} years or older",
"PASSWORD_REQUIREMENTS_MIN_LENGTH": "The password must be at least {{min}} characters long.",
"CONSENT_REQUIRED": "You must agree to the Terms of Service and Privacy Policy.",
"USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another",
"GUESTS_DISABLED": "Guest users are disabled",
"TOO_MANY_REGISTRATIONS": "Too many registrations, please try again later"
},
"password_reset": {

View File

@ -10,7 +10,7 @@
"EMAIL_INVALID": "Invalid Email",
"EMAIL_ALREADY_REGISTERED": "Email is already registered",
"DATE_OF_BIRTH_UNDERAGE": "You need to be {{years}} years or older",
"PASSWORD_REQUIREMENTS_MIN_LENGTH": "Must be at least {{min}} characters long.",
"PASSWORD_REQUIREMENTS_MIN_LENGTH": "The password must be at least {{min}} characters long.",
"CONSENT_REQUIRED": "You must agree to the Terms of Service and Privacy Policy.",
"USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +0,0 @@
const supportedLocales = [
"bg",
"cs",
"da",
"de",
"el",
"en-GB",
"es-ES",
"fi",
"fr",
"hi",
"hr",
"hu",
"it",
"ja",
"ko",
"lt",
"nl",
"no",
"pl",
"pt-BR",
"ro",
"ru",
"sv-SE",
"th",
"tr",
"uk",
"vi",
"zh-CN",
"zh-TW"
];
const settings = JSON.parse(window.localStorage.getItem("UserSettingsStore"));
if (settings && !supportedLocales.includes(settings.locale)) {
// fix client locale wrong and client not loading at all
settings.locale = "en-US";
window.localStorage.setItem("UserSettingsStore", JSON.stringify(settings));
}

View File

@ -1,9 +0,0 @@
// Fixes /oauth2 endpoints not requesting a CSS file
if (location.pathname.startsWith("/oauth2/")) {
const link = document.createElement("link");
link.rel = "stylesheet"
link.type = "text/css"
link.href = "/assets/40532.f7b1e10347ef10e790ac.css"
document.head.appendChild(link)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,68 +0,0 @@
/* replace tos acceptance popup */
#app-mount > div:nth-child(7) > div > div > div.tooltipContent-bqVLWK {
visibility: hidden;
}
#app-mount > div:nth-child(7) > div > div > div.tooltipContent-bqVLWK::after {
visibility: visible;
display: block;
content: "You need to agree to this instance's rules to continue";
margin-top: -32px;
}
/* replace login header */
#app-mount > div.app-1q1i1E > div > div > div > div > form > div > div > div.mainLoginContainer-1ddwnR > h3 {
visibility: hidden;
}
h3.title-jXR8lp.marginBottom8-AtZOdT.base-1x0h_U.size24-RIRrxO::after {
margin-top: -32px;
content: "Welcome to Spacebar!";
visibility: visible;
display: block;
}
/* Logo in top left when bg removed */
#app-mount > div.app-1q1i1E > div > a {
/* replace me: original dimensions: 130x36 */
background: url(https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg);
width: 130px;
height: 23px;
background-size: contain;
}
/* replace TOS text */
#app-mount
> div.app-1q1i1E
> div
> div
> div
> form
> div
> div
> div.flex-1xMQg5.flex-1O1GKY.horizontal-1ae9ci.horizontal-2EEEnY.flex-1O1GKY.directionRow-3v3tfG.justifyStart-2NDFzi.alignCenter-1dQNNs.noWrap-3jynv6.marginTop20-3TxNs6
> label
> div.label-cywgfr.labelClickable-11AuB8.labelForward-1wfipV
> * {
visibility: hidden;
}
#app-mount
> div.app-1q1i1E
> div
> div
> div
> form
> div
> div
> div.flex-1xMQg5.flex-1O1GKY.horizontal-1ae9ci.horizontal-2EEEnY.flex-1O1GKY.directionRow-3v3tfG.justifyStart-2NDFzi.alignCenter-1dQNNs.noWrap-3jynv6.marginTop20-3TxNs6
> label
> div.label-cywgfr.labelClickable-11AuB8.labelForward-1wfipV::after {
visibility: visible;
content: "I have read and agree with the rules set by this instance.";
display: block;
margin-top: -16px;
}
/* shrink login box to same size as register */
.authBoxExpanded-2jqaBe {
width: 480px !important;
}

View File

@ -1,92 +0,0 @@
/* loading spinner */
#app-mount > div.app-1q1i1E > div.container-16j22k.fixClipping-3qAKRb > div.content-1-zrf2 > video {
filter: opacity(1);
background: url("http://www.clipartbest.com/cliparts/7ca/6Rr/7ca6RrLAi.gif");
background-size: contain;
/* width: 64px;
height: 64px; */
padding-bottom: 64px;
background-repeat: no-repeat;
}
/* home button icon */
#app-mount
> div.app-1q1i1E
> div
> div.layers-3iHuyZ.layers-3q14ss
> div
> div
> nav
> ul
> div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih
> div.tutorialContainer-2sGCg9
> div
> div.listItemWrapper-KhRmzM
> div
> svg
> foreignObject
> div
> div {
background-image: url(https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Icon-Rounded-Subtract.svg);
background-size: contain;
border-radius: 50%;
}
#app-mount
> div.app-1q1i1E
> div
> div.layers-3iHuyZ.layers-3q14ss
> div
> div
> nav
> ul
> div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih
> div.tutorialContainer-2sGCg9
> div
> div.listItemWrapper-KhRmzM
> div
> svg
> foreignObject
> div
> div,
#app-mount
> div.app-1q1i1E
> div
> div.layers-3iHuyZ.layers-3q14ss
> div
> div
> nav
> ul
> div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih
> div.tutorialContainer-2sGCg9
> div
> div.listItemWrapper-KhRmzM
> div
> svg
> foreignObject
> div
> div:hover {
background-color: white;
}
/* Login QR */
#app-mount > div.app-1q1i1E > div > div > div > div > form > div > div > div.transitionGroup-aR7y1d.qrLogin-1AOZMt,
#app-mount > div.app-1q1i1E > div > div > div > div > form > div > div > div.verticalSeparator-3huAjp,
/* Remove login bg */
#app-mount > div.app-1q1i1E > div > svg,
/* Download bar */
#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > div > div.notice-3bPHh-.colorDefault-22HBa0,
/* Connection problem links */
#app-mount > div.app-1q1i1E > div.container-16j22k.fixClipping-3qAKRb > div.problems-3mgf6w.slideIn-sCvzGz > div:nth-child(2),
/* Downloads button */
#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > nav > ul > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih > div:nth-child(7) > div.listItemWrapper-KhRmzM > div > svg > foreignObject > div,
#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > nav > ul > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih > div:nth-child(6) > div,
/* help button */
#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > div > div.content-98HsJk > div.chat-3bRxxu > section > div.toolbar-1t6TWx > a,
/* download button start of guild */
#chat-messages-899316648933185083 > div > div > div:nth-child(5),
/* Thread permissions etc popups */
#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > div > div.content-98HsJk > div.sidebar-2K8pFh.hasNotice-1XRy4h > nav > div.container-3O_wAf,
/* home button icon */
#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > nav > ul > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih > div.tutorialContainer-2sGCg9 > div > div.listItemWrapper-KhRmzM > div > svg > foreignObject > div > div > svg {
display: none;
}

View File

@ -1 +0,0 @@
/* Your custom CSS goes here, enjoy! */

147
assets/public/verify.html Normal file
View File

@ -0,0 +1,147 @@
<!doctype html>
<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>Spacebar Server</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"
/>
<style>
body {
font-family: "Montserrat", sans-serif;
background-color: rgb(10, 10, 10);
color: white;
font-size: 1.1rem;
height: 100vh;
}
* {
padding: 0;
margin: 0;
}
p {
margin-top: 10px;
}
#wordmark {
width: min(200px, 50%);
margin: 20px;
position: absolute;
top: 20px;
left: 20px;
}
.title {
font-size: 1.5rem;
font-weight: 600;
}
.subtitle {
font-size: 1.1rem;
font-weight: 400;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.box {
width: 22vw;
padding: 32px;
border-radius: 8px;
background-color: rgb(32, 32, 32);
align-items: center;
display: flex;
flex-direction: column;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<img
alt="Spacebar Logo"
id="wordmark"
src="https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/svg/Spacebar__Logo-Blue.svg"
/>
<div class="box">
<p id="title" class="title">Verifying your email</p>
<p id="subtitle" class="subtitle">Please wait...</p>
</div>
</div>
<script>
window.onload = verify;
function verify() {
const title = document.getElementById("title");
const subtitle = document.getElementById("subtitle");
// if no fragment identifier in URL, error
if (!window.location.hash) {
title.innerText = "Invalid Link";
subtitle.innerText = "Please check the link and try again.";
return;
}
// convert fragment to a key-value pair
const fragment = window.location.hash.substring(1);
const pairs = fragment.split("&");
const values = {};
pairs.forEach((pair) => {
const [key, value] = pair.split("=");
values[key] = value;
});
// ensure token key is present
if (!values.token) {
title.innerText = "Invalid Link";
subtitle.innerText = "Please check the link and try again.";
return;
}
// make request to server
const token = values.token;
fetch("/api/auth/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token,
}),
})
.then((response) => response.json())
.then((data) => {
// check for an error response
if ("message" in data) {
title.innerText = "Email Verification Link Expired";
subtitle.innerText =
"Please request a new verification link.";
return;
}
title.innerText = "Email Verified";
subtitle.innerText = "You can now login.";
})
.catch((error) => {
title.innerText = "Email Verification Failed";
subtitle.innerText = error;
});
}
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,82 +0,0 @@
/*
This file is used to patch client version 134842 ( and probably a lot more ) to send additional info when using webrtc.
If you want to use it, throw it into the `preload-plugins` folder.
TODO: Make it so this file is not required for webrtc.
Do note that webrtc, as of 17/12/2022, is not implemented yet in spacebarchat/server.
*/
(this.webpackChunkdiscord_app = this.webpackChunkdiscord_app || []).push([
[[228974]],
{
632540: (module, exports, req) => {
window.find = (filter, options = {}) => {
const { cacheOnly = false } = options;
for (let i in req.c) {
if (req.c.hasOwnProperty(i)) {
let m = req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) return m.default;
if (m && filter(m)) return m;
}
}
if (cacheOnly) {
console.warn("Cannot find loaded module in cache");
return null;
}
console.warn("Cannot find loaded module in cache. Loading all modules may have unexpected side effects");
for (let i = 0; i < req.m.length; ++i) {
let m = req(i);
if (m && m.__esModule && m.default && filter(m.default)) return m.default;
if (m && filter(m)) return m;
}
console.warn("Cannot find module");
return null;
};
window.findByUniqueProperties = (propNames, options) =>
find((module) => propNames.every((prop) => module[prop] !== undefined), options);
window.findByDisplayName = (displayName, options) => find((module) => module.displayName === displayName, options);
window.req = req;
init();
}
},
(t) => t(632540)
]);
function retry(callback) {
return new Promise((resolve) => {
const interval = setInterval(() => {
const mod = callback();
if (!mod) return;
clearInterval(interval);
resolve(mod);
}, 50);
});
}
async function init() {
const SDP = await retry(() => findByUniqueProperties(["truncateSDP"]));
const StringManipulator = findByUniqueProperties(["uniq"]);
const truncateSDP = SDP.truncateSDP;
SDP.truncateSDP = (e) => {
const result = truncateSDP(e);
const i = result.codecs.find((x) => x.name === "VP8");
const a = new RegExp("^a=ice|a=extmap|opus|VP8|fingerprint|" + i?.rtxPayloadType + " rtx", "i");
return {
sdp: StringManipulator(e)
.split(/\r\n/)
.filter(function (e) {
return a.test(e);
})
.uniq()
.join("\n"),
codecs: result.codecs
};
};
// SDP.generateUnifiedSessionDescription = (e) => {
// console.log(e);
// return new RTCSessionDescription({ sdp: e.baseSDP.replace(/sendonly/g, "recvonly"), type: "answer" });
// };
}

61
flake.lock Normal file
View File

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1708118438,
"narHash": "sha256-kk9/0nuVgA220FcqH/D2xaN6uGyHp/zoxPNUmPCMmEE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "5863c27340ba4de8f83e7e3c023b9599c3cb3c80",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

70
flake.nix Normal file
View File

@ -0,0 +1,70 @@
{
description = "Spacebar server, written in Typescript.";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachSystem flake-utils.lib.allSystems (system:
let
pkgs = import nixpkgs {
inherit system;
};
hashesFile = builtins.fromJSON (builtins.readFile ./hashes.json);
in rec {
packages.default = pkgs.buildNpmPackage {
pname = "spacebar-server-ts";
src = ./.;
name = "spacebar-server-ts";
nativeBuildInputs = with pkgs; [ python3 ];
npmDepsHash = hashesFile.npmDepsHash;
makeCacheWritable = true;
postPatch = ''
substituteInPlace package.json --replace 'npx patch-package' '${pkgs.nodePackages.patch-package}/bin/patch-package'
'';
installPhase = ''
runHook preInstall
set -x
#remove packages not needed for production, or at least try to...
npm prune --omit dev --no-save $npmInstallFlags "''${npmInstallFlagsArray[@]}" $npmFlags "''${npmFlagsArray[@]}"
find node_modules -maxdepth 1 -type d -empty -delete
mkdir -p $out/node_modules/
cp -r node_modules/* $out/node_modules/
cp -r dist/ $out/node_modules/@spacebar
for i in dist/**/start.js
do
makeWrapper ${pkgs.nodejs-slim}/bin/node $out/bin/start-`dirname ''${i/dist\//}` --prefix NODE_PATH : $out/node_modules --add-flags $out/node_modules/@spacebar`dirname ''${i/dist/}`/start.js
done
set +x
substituteInPlace package.json --replace 'dist/' 'node_modules/@spacebar/'
find $out/node_modules/@spacebar/ -type f -name "*.js" | while read srcFile; do
echo Patching imports in ''${srcFile/$out\/node_modules\/@spacebar//}...
substituteInPlace $srcFile --replace 'require("./' 'require(__dirname + "/'
substituteInPlace $srcFile --replace 'require("../' 'require(__dirname + "/../'
substituteInPlace $srcFile --replace ', "assets"' ', "..", "assets"'
#substituteInPlace $srcFile --replace 'require("@spacebar/' 'require("
done
set -x
cp -r assets/ $out/
cp package.json $out/
rm -v $out/assets/openapi.json
#rm -v $out/assets/schemas.json
#debug utils:
#cp $out/node_modules/@spacebar/ $out/build_output -r
set +x
runHook postInstall
'';
};
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs
nodePackages.typescript
];
};
}
);
}

3
hashes.json Normal file
View File

@ -0,0 +1,3 @@
{
"npmDepsHash": "sha256-fZNDN2/fNy6Nu7tbr0RhQ8j4BP7X1Yhrh/fSTH7hbJc="
}

10
nix-update.sh Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env nix-shell
#!nix-shell -i "bash -x" -p bash prefetch-npm-deps jq git nix-output-monitor
nix flake update
DEPS_HASH=`prefetch-npm-deps package-lock.json`
TMPFILE=$(mktemp)
jq '.npm_deps_hash = "'$DEPS_HASH'"' hashes.json > $TMPFILE
mv -- "$TMPFILE" hashes.json
nom build .# || exit $?
git add hashes.json flake.lock flake.nix

501
package-lock.json generated
View File

@ -38,10 +38,12 @@
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"murmurhash-js": "^1.0.0",
"mysql": "*",
"node-2fa": "^2.0.3",
"node-fetch": "^2.6.12",
"node-os-utils": "^1.3.7",
"nodemailer": "^6.9.4",
"pg": "*",
"picocolors": "^1.0.0",
"probe-image-size": "^7.2.3",
"proxy-agent": "^6.3.0",
@ -72,9 +74,9 @@
"@types/probe-image-size": "^7.2.0",
"@types/sharp": "^0.31.1",
"@types/ws": "^8.5.5",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.46.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"eslint": "^8.56.0",
"express": "^4.18.2",
"husky": "^8.0.3",
"prettier": "^2.8.8",
@ -83,9 +85,11 @@
},
"optionalDependencies": {
"erlpack": "^0.1.4",
"mysql": "^2.18.1",
"nodemailer-mailgun-transport": "^2.1.5",
"nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport",
"nodemailer-sendgrid-transport": "github:Maria-Golomb/nodemailer-sendgrid-transport",
"pg": "^8.11.3",
"sqlite3": "^5.1.6"
}
},
@ -952,9 +956,9 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@ -997,9 +1001,9 @@
"dev": true
},
"node_modules/@eslint/js": {
"version": "8.47.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz",
"integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
"integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -1017,13 +1021,13 @@
"integrity": "sha512-PdUmzpvcUM3Rh39kvz9RdbPVYhMjBjdV7Suw7ZduP7urRLsZR8l5tzgSWKm7TExwBYDFwTnYrZbnE0rQ3N5NLQ=="
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
"integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1",
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
@ -1044,9 +1048,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"node_modules/@jridgewell/resolve-uri": {
@ -2210,9 +2214,9 @@
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
"integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"dev": true
},
"node_modules/@types/send": {
@ -2263,32 +2267,33 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
"integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.4.0",
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/type-utils": "5.62.0",
"@typescript-eslint/utils": "5.62.0",
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/type-utils": "6.21.0",
"@typescript-eslint/utils": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
"ignore": "^5.2.4",
"natural-compare": "^1.4.0",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
"@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
@ -2297,25 +2302,26 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
"@typescript-eslint/typescript-estree": "5.62.0",
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
@ -2324,16 +2330,16 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
"integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.62.0",
"@typescript-eslint/visitor-keys": "5.62.0"
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -2341,25 +2347,25 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz",
"integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
"integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "5.62.0",
"@typescript-eslint/utils": "5.62.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"@typescript-eslint/utils": "6.21.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "*"
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
@ -2368,12 +2374,12 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
"integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -2381,21 +2387,22 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
"integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.62.0",
"@typescript-eslint/visitor-keys": "5.62.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -2407,49 +2414,78 @@
}
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
"integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
"@typescript-eslint/typescript-estree": "5.62.0",
"eslint-scope": "^5.1.1",
"semver": "^7.3.7"
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
"integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"semver": "^7.5.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
"eslint": "^7.0.0 || ^8.0.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
"integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.62.0",
"eslint-visitor-keys": "^3.3.0"
"@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -2964,6 +3000,15 @@
"resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
"integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
},
"node_modules/buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
"optional": true,
"engines": {
"node": ">=4"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@ -3845,18 +3890,19 @@
}
},
"node_modules/eslint": {
"version": "8.47.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz",
"integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
"integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2",
"@eslint/js": "^8.47.0",
"@humanwhocodes/config-array": "^0.11.10",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.56.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@ -3898,19 +3944,6 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
@ -4045,7 +4078,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"devOptional": true,
"optional": true,
"engines": {
"node": ">=4.0"
}
@ -4620,9 +4653,9 @@
}
},
"node_modules/globals": {
"version": "13.21.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
"integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@ -5704,6 +5737,60 @@
"resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
"integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw=="
},
"node_modules/mysql": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
"optional": true,
"dependencies": {
"bignumber.js": "9.0.0",
"readable-stream": "2.3.7",
"safe-buffer": "5.1.2",
"sqlstring": "2.3.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mysql/node_modules/bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==",
"optional": true,
"engines": {
"node": "*"
}
},
"node_modules/mysql/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"optional": true
},
"node_modules/mysql/node_modules/readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"optional": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/mysql/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"optional": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@ -5726,12 +5813,6 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"node_modules/natural-compare-lite": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
"node_modules/needle": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
@ -6262,6 +6343,12 @@
"node": ">= 14"
}
},
"node_modules/packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==",
"optional": true
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -6357,6 +6444,96 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/pg": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
"optional": true,
"dependencies": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.6.2",
"pg-pool": "^3.6.1",
"pg-protocol": "^1.6.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
"engines": {
"node": ">= 8.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.1.1"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==",
"optional": true
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"optional": true,
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
"optional": true,
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==",
"optional": true
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"optional": true,
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"optional": true,
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -6389,6 +6566,45 @@
"node": ">=12.0.0"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"optional": true,
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"optional": true,
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -7091,6 +7307,15 @@
"source-map": "^0.6.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"optional": true,
"engines": {
"node": ">= 10.x"
}
},
"node_modules/sqlite3": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz",
@ -7120,6 +7345,15 @@
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==",
"optional": true
},
"node_modules/sqlstring": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
"integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==",
"optional": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
@ -7688,6 +7922,18 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-api-utils": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
"integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
}
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@ -7735,27 +7981,6 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
"integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
},
"node_modules/tsutils": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
"integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
"dev": true,
"dependencies": {
"tslib": "^1.8.1"
},
"engines": {
"node": ">= 6"
},
"peerDependencies": {
"typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
}
},
"node_modules/tsutils/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -10,6 +10,7 @@
"start:cdn": "node dist/cdn/start.js",
"start:gateway": "node dist/gateway/start.js",
"build": "tsc -p .",
"watch": "tsc -w -p .",
"test": "node scripts/test.js",
"lint": "eslint .",
"setup": "npm run build && npm run generate:schema",
@ -55,9 +56,9 @@
"@types/probe-image-size": "^7.2.0",
"@types/sharp": "^0.31.1",
"@types/ws": "^8.5.5",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.46.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"eslint": "^8.56.0",
"express": "^4.18.2",
"husky": "^8.0.3",
"prettier": "^2.8.8",
@ -116,9 +117,11 @@
},
"optionalDependencies": {
"erlpack": "^0.1.4",
"mysql": "^2.18.1",
"nodemailer-mailgun-transport": "^2.1.5",
"nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport",
"nodemailer-sendgrid-transport": "github:Maria-Golomb/nodemailer-sendgrid-transport",
"pg": "^8.11.3",
"sqlite3": "^5.1.6"
}
}

View File

@ -18,15 +18,15 @@
import {
Config,
Email,
initDatabase,
initEvent,
JSONReplacer,
registerRoutes,
Sentry,
WebAuthn,
ConnectionConfig,
ConnectionLoader,
Email,
JSONReplacer,
Sentry,
WebAuthn,
initDatabase,
initEvent,
registerRoutes,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { Server, ServerOptions } from "lambert-server";
@ -141,6 +141,10 @@ export class SpacebarServer extends Server {
res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")),
);
app.get("/verify", (req, res) =>
res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html")),
);
this.app.use(ErrorHandler);
Sentry.errorHandler(this.app);

View File

@ -21,6 +21,7 @@ import {
Application,
ApplicationModifySchema,
DiscordApiErrors,
handleFile,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
@ -83,6 +84,13 @@ router.patch(
)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
if (body.icon) {
body.icon = await handleFile(
`/app-icons/${app.id}`,
body.icon as string,
);
}
if (app.bot) {
app.bot.assign({ bio: body.description });
await app.bot.save();

View File

@ -38,7 +38,7 @@ router.get(
"The length of each registration token. Defaults to 255.",
},
},
right: "OPERATOR",
right: "CREATE_REGISTRATION_TOKENS",
responses: { 200: { body: "GenerateRegistrationTokensResponse" } },
}),
async (req: Request, res: Response) => {

View File

@ -85,7 +85,7 @@ router.post(
user = userTokenData.user;
} catch {
throw FieldErrors({
password: {
token: {
message: req.t("auth:password_reset.INVALID_TOKEN"),
code: "INVALID_TOKEN",
},

View File

@ -50,7 +50,13 @@ router.get(
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
});
if (!channel.guild_id) return res.send(channel);
channel.position = await Channel.calculatePosition(
channel_id,
channel.guild_id,
channel.guild,
);
return res.send(channel);
},
);
@ -90,6 +96,24 @@ router.delete(
} else if (channel.type === ChannelType.GROUP_DM) {
await Channel.removeRecipientFromChannel(channel, req.user_id);
} else {
if (channel.type == ChannelType.GUILD_CATEGORY) {
const channels = await Channel.find({
where: { parent_id: channel_id },
});
for await (const c of channels) {
c.parent_id = null;
await Promise.all([
c.save(),
emitEvent({
event: "CHANNEL_UPDATE",
data: c,
channel_id: c.id,
} as ChannelUpdateEvent),
]);
}
}
await Promise.all([
Channel.delete({ id: channel_id }),
emitEvent({

View File

@ -56,6 +56,7 @@ router.post(
edited_timestamp: null,
flags: 1,
components: [],
poll: {},
}).status(200);
},
);

View File

@ -91,11 +91,10 @@ router.patch(
}
} else rights.hasThrow("SELF_EDIT_MESSAGES");
// @ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema
const new_message = await handleMessage({
...message,
// TODO: should message_reference be overridable?
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
message_reference: message.message_reference,
...body,
author_id: message.author_id,

View File

@ -183,9 +183,17 @@ router.get(
const uri = y.proxy_url.startsWith("http")
? y.proxy_url
: `https://example.org${y.proxy_url}`;
y.proxy_url = `${endpoint == null ? "" : endpoint}${
new URL(uri).pathname
}`;
let pathname = new URL(uri).pathname;
while (
pathname.split("/")[0] != "attachments" &&
pathname.length > 30
) {
pathname = pathname.split("/").slice(1).join("/");
}
if (!endpoint?.endsWith("/")) pathname = "/" + pathname;
y.proxy_url = `${endpoint == null ? "" : endpoint}${pathname}`;
});
/**

View File

@ -53,6 +53,11 @@ router.put(
where: { id: channel_id },
});
if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
channel.position = await Channel.calculatePosition(
channel_id,
channel.guild_id,
channel.guild,
);
if (body.type === 0) {
if (!(await Role.count({ where: { id: overwrite_id } })))

View File

@ -0,0 +1,45 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import { ConnectionConfig } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get(
"/",
route({
responses: {
200: {
body: "APIConnectionsConfiguration",
},
},
}),
async (req: Request, res: Response) => {
const config = ConnectionConfig.get();
Object.keys(config).forEach((key) => {
delete config[key].clientId;
delete config[key].clientSecret;
});
res.json(config);
},
);
export default router;

View File

@ -19,7 +19,6 @@
import { getIpAdress, route } from "@spacebar/api";
import {
Ban,
BanModeratorSchema,
BanRegistrySchema,
DiscordApiErrors,
GuildBanAddEvent,
@ -82,7 +81,7 @@ router.get(
);
router.get(
"/:user",
"/:user_id",
route({
permission: "BAN_MEMBERS",
responses: {
@ -98,23 +97,21 @@ router.get(
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const user_id = req.params.ban;
const { guild_id, user_id } = req.params;
let ban = (await Ban.findOneOrFail({
const ban = (await Ban.findOneOrFail({
where: { guild_id: guild_id, user_id: user_id },
})) as BanRegistrySchema;
if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
// pretend self-bans don't exist to prevent victim chasing
/* Filter secret from registry. */
const banInfo = {
user: await User.getPublicUser(ban.user_id),
reason: ban.reason,
};
ban = ban as BanModeratorSchema;
delete ban.ip;
return res.json(ban);
return res.json(banInfo);
},
);
@ -151,6 +148,12 @@ router.put(
if (req.permission?.cache.guild?.owner_id === banned_user_id)
throw new HTTPError("You can't ban the owner", 400);
const existingBan = await Ban.findOne({
where: { guild_id: guild_id, user_id: banned_user_id },
});
// Bans on already banned users are silently ignored
if (existingBan) return res.status(204).send();
const banned_user = await User.getPublicUser(banned_user_id);
const ban = Ban.create({
@ -174,59 +177,7 @@ router.put(
} as GuildBanAddEvent),
]);
return res.json(ban);
},
);
router.put(
"/@me",
route({
requestBody: "BanCreateSchema",
responses: {
200: {
body: "Ban",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const banned_user = await User.getPublicUser(req.params.user_id);
if (req.permission?.cache.guild?.owner_id === req.params.user_id)
throw new HTTPError(
"You are the guild owner, hence can't ban yourself",
403,
);
const ban = Ban.create({
user_id: req.params.user_id,
guild_id: guild_id,
ip: getIpAdress(req),
executor_id: req.params.user_id,
reason: req.body.reason, // || otherwise empty
});
await Promise.all([
Member.removeFromGuild(req.user_id, guild_id),
ban.save(),
emitEvent({
event: "GUILD_BAN_ADD",
data: {
guild_id: guild_id,
user: banned_user,
},
guild_id: guild_id,
} as GuildBanAddEvent),
]);
return res.json(ban);
return res.status(204).send();
},
);
@ -247,13 +198,10 @@ router.delete(
async (req: Request, res: Response) => {
const { guild_id, user_id } = req.params;
const ban = await Ban.findOneOrFail({
await Ban.findOneOrFail({
where: { guild_id: guild_id, user_id: user_id },
});
if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
// make self-bans irreversible and hide them from view to avoid victim chasing
const banned_user = await User.getPublicUser(user_id);
await Promise.all([

View File

@ -0,0 +1,130 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getIpAdress, route } from "@spacebar/api";
import {
Ban,
DiscordApiErrors,
GuildBanAddEvent,
Member,
User,
emitEvent,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router: Router = Router();
router.post(
"/",
route({
requestBody: "BulkBanSchema",
permission: ["BAN_MEMBERS", "MANAGE_GUILD"],
responses: {
200: {
body: "Ban",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const userIds: Array<string> = req.body.user_ids;
if (!userIds) throw new HTTPError("The user_ids array is missing", 400);
if (userIds.length > 200)
throw new HTTPError(
"The user_ids array must be between 1 and 200 in length",
400,
);
const banned_users = [];
const failed_users = [];
for await (const banned_user_id of userIds) {
if (
req.user_id === banned_user_id &&
banned_user_id === req.permission?.cache.guild?.owner_id
) {
failed_users.push(banned_user_id);
continue;
}
if (req.permission?.cache.guild?.owner_id === banned_user_id) {
failed_users.push(banned_user_id);
continue;
}
const existingBan = await Ban.findOne({
where: { guild_id: guild_id, user_id: banned_user_id },
});
if (existingBan) {
failed_users.push(banned_user_id);
continue;
}
let banned_user;
try {
banned_user = await User.getPublicUser(banned_user_id);
} catch {
failed_users.push(banned_user_id);
continue;
}
const ban = Ban.create({
user_id: banned_user_id,
guild_id: guild_id,
ip: getIpAdress(req),
executor_id: req.user_id,
reason: req.body.reason, // || otherwise empty
});
try {
await Promise.all([
Member.removeFromGuild(banned_user_id, guild_id),
ban.save(),
emitEvent({
event: "GUILD_BAN_ADD",
data: {
guild_id: guild_id,
user: banned_user,
},
guild_id: guild_id,
} as GuildBanAddEvent),
]);
banned_users.push(banned_user_id);
} catch {
failed_users.push(banned_user_id);
continue;
}
}
if (banned_users.length === 0 && failed_users.length > 0)
throw DiscordApiErrors.BULK_BAN_FAILED;
return res.json({
banned_users: banned_users,
failed_users: failed_users,
});
},
);
export default router;

View File

@ -41,6 +41,15 @@ router.get(
const { guild_id } = req.params;
const channels = await Channel.find({ where: { guild_id } });
for await (const channel of channels) {
channel.position = await Channel.calculatePosition(
channel.id,
guild_id,
channel.guild,
);
}
channels.sort((a, b) => a.position - b.position);
res.json(channels);
},
);
@ -71,6 +80,11 @@ router.post(
{ ...body, guild_id },
req.user_id,
);
channel.position = await Channel.calculatePosition(
channel.id,
guild_id,
channel.guild,
);
res.status(201).json(channel);
},

View File

@ -125,6 +125,7 @@ router.post(
const user = await User.findOneOrFail({ where: { id: req.user_id } });
body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
const mimeType = body.image.split(":")[1].split(";")[0];
const emoji = await Emoji.create({
id: id,
guild_id: guild_id,
@ -132,7 +133,10 @@ router.post(
require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
user: user,
managed: false,
animated: false, // TODO: Add support animated emojis
animated:
mimeType == "image/gif" ||
mimeType == "image/apng" ||
mimeType == "video/webm",
available: true,
roles: [],
}).save();

View File

@ -113,9 +113,6 @@ router.patch(
relations: ["roles", "user"],
});
const permission = await getPermission(req.user_id, guild_id);
const everyone = await Role.findOneOrFail({
where: { guild_id: guild_id, name: "@everyone", position: 0 },
});
if ("nick" in body) {
permission.hasThrow("MANAGE_NICKNAMES");
@ -152,15 +149,14 @@ router.patch(
body.roles = body.roles || [];
body.roles.filter((x) => !!x);
if (body.roles.indexOf(everyone.id) === -1)
body.roles.push(everyone.id);
if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id);
// foreign key constraint will fail if role doesn't exist
member.roles = body.roles.map((x) => Role.create({ id: x }));
}
await member.save();
member.roles = member.roles.filter((x) => x.id !== everyone.id);
member.roles = member.roles.filter((x) => x.id !== guild_id);
// do not use promise.all as we have to first write to db before emitting the event to catch errors
await emitEvent({

View File

@ -162,6 +162,7 @@ router.get(
edited_timestamp: x.edited_timestamp,
flags: x.flags,
components: x.components,
poll: x.poll,
hit: true,
},
]);

View File

@ -18,6 +18,7 @@
import { route } from "@spacebar/api";
import {
Badge,
Member,
PrivateUserProjection,
User,
@ -98,6 +99,9 @@ router.get(
bio: guild_member?.bio || "",
guild_id,
};
const badges = await Badge.find();
res.json({
connected_accounts: user.connected_accounts.filter(
(x) => x.visibility != 0,
@ -111,6 +115,7 @@ router.get(
user_profile: userProfile,
guild_member: guild_member?.toPublicMember(),
guild_member_profile: guild_id && guildMemberProfile,
badges: badges.filter((x) => user.badge_ids?.includes(x.id)),
});
},
);

View File

@ -16,45 +16,49 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as Sentry from "@sentry/node";
import { EmbedHandlers } from "@spacebar/api";
import {
Application,
Attachment,
Channel,
Config,
Embed,
EmbedCache,
emitEvent,
Guild,
Message,
MessageCreateEvent,
MessageUpdateEvent,
EVERYONE_MENTION,
getPermission,
getRights,
Guild,
HERE_MENTION,
Message,
MessageCreateEvent,
MessageCreateSchema,
MessageType,
MessageUpdateEvent,
Role,
ROLE_MENTION,
Sticker,
User,
//CHANNEL_MENTION,
USER_MENTION,
ROLE_MENTION,
Role,
EVERYONE_MENTION,
HERE_MENTION,
MessageType,
User,
Application,
Webhook,
Attachment,
Config,
Sticker,
MessageCreateSchema,
EmbedCache,
handleFile,
Permissions,
} from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { In } from "typeorm";
import { EmbedHandlers } from "@spacebar/api";
import * as Sentry from "@sentry/node";
import fetch from "node-fetch";
const allow_empty = false;
// TODO: check webhook, application, system author, stickers
// TODO: embed gifs/videos/images
const LINK_REGEX =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g;
/<?https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)>?/g;
export async function handleMessage(opts: MessageOptions): Promise<Message> {
const channel = await Channel.findOneOrFail({
@ -69,6 +73,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
: undefined;
const message = Message.create({
...opts,
poll: opts.poll,
sticker_items: stickers,
guild_id: channel.guild_id,
channel_id: opts.channel_id,
@ -185,7 +190,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
otherwise backfilling won't work **/
message.type = MessageType.REPLY;
}
}
// TODO: stickers/activity
if (
@ -193,7 +197,9 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
!opts.content &&
!opts.embeds?.length &&
!opts.attachments?.length &&
!opts.sticker_ids?.length
!opts.sticker_ids?.length &&
!opts.poll &&
!opts.components?.length
) {
throw new HTTPError("Empty messages are not allowed", 50006);
}
@ -272,6 +278,9 @@ export async function postHandleMessage(message: Message) {
const cachePromises = [];
for (const link of links) {
// Don't embed links in <>
if (link.startsWith("<") && link.endsWith(">")) continue;
const url = new URL(link);
const cached = await EmbedCache.findOne({ where: { url: link } });

View File

@ -90,19 +90,23 @@ export function route(opts: RouteOptions) {
return async (req: Request, res: Response, next: NextFunction) => {
if (opts.permission) {
const required = new Permissions(opts.permission);
req.permission = await getPermission(
req.user_id,
req.params.guild_id,
req.params.channel_id,
);
const requiredPerms = Array.isArray(opts.permission)
? opts.permission
: [opts.permission];
requiredPerms.forEach((perm) => {
// bitfield comparison: check if user lacks certain permission
if (!req.permission.has(required)) {
if (!req.permission!.has(new Permissions(perm))) {
throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(
opts.permission as string,
perm as string,
);
}
});
}
if (opts.right) {

View File

@ -0,0 +1,40 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { storage } from "../util/Storage";
import FileType from "file-type";
import { HTTPError } from "lambert-server";
const router = Router();
router.get("/:badge_id", async (req: Request, res: Response) => {
const { badge_id } = req.params;
const path = `badge-icons/${badge_id}`;
const file = await storage.get(path);
if (!file) throw new HTTPError("not found", 404);
const type = await FileType.fromBuffer(file);
res.set("Content-Type", type?.mime);
res.set("Cache-Control", "public, max-age=31536000, must-revalidate");
return res.send(file);
});
export default router;

View File

@ -23,12 +23,12 @@ import { HTTPError } from "lambert-server";
import { join } from "path";
const defaultAvatarHashMap = new Map([
["0", "823a3de61c4dc2415cc4dbc38fca4299"],
["1", "e56a89224be0b2b1f7c04eca975be468"],
["2", "0c8138dcc0dfe2689cdd73f7952c2475"],
["3", "5ac2728593bb455250d11b848a0c36c6"],
["4", "addd2f3268df46459e1d6012ad8e75bd"],
["5", "c4e0c8300fa491d94acfd2a1fb26cea8"],
["0", "4a8562cf00887030c416d3ec2d46385a"],
["1", "9b0bb198936784c45c72833cc426cc55"],
["2", "22341bdb500c7b63a93bbce957d1601e"],
["3", "d9977836b82058bf2f74eebd50edc095"],
["4", "9d6ddb4e4d899a533a8cc617011351c9"],
["5", "7213ab6677377974697dfdfbaf5f6a6f"],
]);
const defaultGroupDMAvatarHashMap = new Map([
@ -63,7 +63,15 @@ router.get("/avatars/:id", async (req: Request, res: Response) => {
id = id.split(".")[0]; // remove .file extension
const hash = defaultAvatarHashMap.get(id);
if (!hash) throw new HTTPError("not found", 404);
const path = join(process.cwd(), "assets", "public", `${hash}.png`);
const path = join(
__dirname,
"..",
"..",
"..",
"assets",
"public",
`${hash}.png`,
);
const file = await getFile(path);
if (!file) throw new HTTPError("not found", 404);
@ -80,7 +88,15 @@ router.get("/group-avatars/:id", async (req: Request, res: Response) => {
id = id.split(".")[0]; // remove .file extension
const hash = defaultGroupDMAvatarHashMap.get(id);
if (!hash) throw new HTTPError("not found", 404);
const path = join(process.cwd(), "assets", "public", `${hash}.png`);
const path = join(
__dirname,
"..",
"..",
"..",
"assets",
"public",
`${hash}.png`,
);
const file = await getFile(path);
if (!file) throw new HTTPError("not found", 404);

View File

@ -47,13 +47,15 @@ export default class BattleNetConnection extends Connection {
settings: BattleNetSettings = new BattleNetSettings();
init(): void {
const settings =
ConnectionLoader.getConnectionConfig<BattleNetSettings>(
this.settings = ConnectionLoader.getConnectionConfig<BattleNetSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -43,12 +43,15 @@ export default class DiscordConnection extends Connection {
settings: DiscordSettings = new DiscordSettings();
init(): void {
const settings = ConnectionLoader.getConnectionConfig<DiscordSettings>(
this.settings = ConnectionLoader.getConnectionConfig<DiscordSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -53,13 +53,15 @@ export default class EpicGamesConnection extends Connection {
settings: EpicGamesSettings = new EpicGamesSettings();
init(): void {
const settings =
ConnectionLoader.getConnectionConfig<EpicGamesSettings>(
this.settings = ConnectionLoader.getConnectionConfig<EpicGamesSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -52,12 +52,15 @@ export default class FacebookConnection extends Connection {
settings: FacebookSettings = new FacebookSettings();
init(): void {
const settings = ConnectionLoader.getConnectionConfig<FacebookSettings>(
this.settings = ConnectionLoader.getConnectionConfig<FacebookSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -42,12 +42,15 @@ export default class GitHubConnection extends Connection {
settings: GitHubSettings = new GitHubSettings();
init(): void {
const settings = ConnectionLoader.getConnectionConfig<GitHubSettings>(
this.settings = ConnectionLoader.getConnectionConfig<GitHubSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -54,12 +54,15 @@ export default class RedditConnection extends Connection {
settings: RedditSettings = new RedditSettings();
init(): void {
const settings = ConnectionLoader.getConnectionConfig<RedditSettings>(
this.settings = ConnectionLoader.getConnectionConfig<RedditSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -63,12 +63,16 @@ export default class SpotifyConnection extends RefreshableConnection {
* So to prevent spamming the spotify api we disable the ability to refresh.
*/
this.refreshEnabled = false;
const settings = ConnectionLoader.getConnectionConfig<SpotifySettings>(
this.settings = ConnectionLoader.getConnectionConfig<SpotifySettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -55,12 +55,15 @@ export default class TwitchConnection extends RefreshableConnection {
settings: TwitchSettings = new TwitchSettings();
init(): void {
const settings = ConnectionLoader.getConnectionConfig<TwitchSettings>(
this.settings = ConnectionLoader.getConnectionConfig<TwitchSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -55,12 +55,15 @@ export default class TwitterConnection extends RefreshableConnection {
settings: TwitterSettings = new TwitterSettings();
init(): void {
const settings = ConnectionLoader.getConnectionConfig<TwitterSettings>(
this.settings = ConnectionLoader.getConnectionConfig<TwitterSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -62,12 +62,15 @@ export default class XboxConnection extends Connection {
settings: XboxSettings = new XboxSettings();
init(): void {
const settings = ConnectionLoader.getConnectionConfig<XboxSettings>(
this.settings = ConnectionLoader.getConnectionConfig<XboxSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -62,12 +62,15 @@ export default class YoutubeConnection extends Connection {
settings: YoutubeSettings = new YoutubeSettings();
init(): void {
const settings = ConnectionLoader.getConnectionConfig<YoutubeSettings>(
this.settings = ConnectionLoader.getConnectionConfig<YoutubeSettings>(
this.id,
this.settings,
);
if (settings.enabled && (!settings.clientId || !settings.clientSecret))
if (
this.settings.enabled &&
(!this.settings.clientId || !this.settings.clientSecret)
)
throw new Error(`Invalid settings for connection ${this.id}`);
}

View File

@ -439,6 +439,10 @@ export async function onIdentify(this: WebSocket, data: Payload) {
tutorial: null,
session_type: "normal", // TODO
auth_session_id_hash: "", // TODO
notification_settings: {
// ????
flags: 0,
},
};
// Send READY

View File

@ -25,6 +25,7 @@ import { SendGridConfiguration } from "./subconfigurations/email/SendGrid";
export class EmailConfiguration {
provider: string | null = null;
senderAddress: string | null = null;
smtp: SMTPConfiguration = new SMTPConfiguration();
mailgun: MailGunConfiguration = new MailGunConfiguration();
mailjet: MailJetConfiguration = new MailJetConfiguration();

View File

@ -43,7 +43,7 @@ export abstract class Connection {
*/
getRedirectUri() {
const endpointPublic =
Config.get().api.endpointPublic ?? "http://localhost:3001";
Config.get().general.frontPage ?? "http://localhost:3001";
return `${endpointPublic}/connections/${this.id}/callback`;
}

View File

@ -22,7 +22,7 @@ import path from "path";
import { ConnectionConfig } from "./ConnectionConfig";
import { ConnectionStore } from "./ConnectionStore";
const root = "dist/connections";
const root = path.join(__dirname, "..", "..", "connections");
const connectionsLoaded = false;
export class ConnectionLoader {

View File

@ -24,6 +24,7 @@ export class MinimalPublicUserDTO {
id: string;
public_flags: number;
username: string;
badge_ids?: string[] | null;
constructor(user: User) {
this.avatar = user.avatar;
@ -31,5 +32,6 @@ export class MinimalPublicUserDTO {
this.id = user.id;
this.public_flags = user.public_flags;
this.username = user.username;
this.badge_ids = user.badge_ids;
}
}

View File

@ -0,0 +1,35 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Column, Entity } from "typeorm";
import { BaseClassWithoutId } from "./BaseClass";
@Entity("badges")
export class Badge extends BaseClassWithoutId {
@Column({ primary: true })
id: string;
@Column()
description: string;
@Column()
icon: string;
@Column({ nullable: true })
link?: string;
}

View File

@ -105,7 +105,7 @@ export class Channel extends BaseClass {
@Column({ nullable: true })
@RelationId((channel: Channel) => channel.parent)
parent_id: string;
parent_id: string | null;
@JoinColumn({ name: "parent_id" })
@ManyToOne(() => Channel)

View File

@ -31,7 +31,7 @@ import {
PrimaryGeneratedColumn,
RelationId,
} from "typeorm";
import { Ban, PublicGuildRelations } from ".";
import { Ban, Channel, PublicGuildRelations } from ".";
import { ReadyGuildDTO } from "../dtos";
import {
GuildCreateEvent,
@ -330,6 +330,13 @@ export class Member extends BaseClassWithoutId {
relationLoadStrategy: "query",
});
for await (const channel of guild.channels) {
channel.position = await Channel.calculatePosition(
channel.id,
guild_id,
);
}
const memberCount = await Member.count({ where: { guild_id } });
const memberPreview = (

View File

@ -192,8 +192,8 @@ export class Message extends BaseClass {
party_id: string;
};
@Column({ nullable: true })
flags?: number;
@Column({ default: 0 })
flags: number;
@Column({ type: "simple-json", nullable: true })
message_reference?: {
@ -252,6 +252,7 @@ export class Message extends BaseClass {
activity: this.activity ?? undefined,
application: this.application ?? undefined,
components: this.components ?? undefined,
poll: this.poll ?? undefined,
content: this.content ?? "",
};
}
@ -263,6 +264,7 @@ export interface MessageComponent {
label?: string;
emoji?: PartialEmoji;
custom_id?: string;
sku_id?: string;
url?: string;
disabled?: boolean;
components: MessageComponent[];
@ -341,3 +343,32 @@ export interface AllowedMentions {
users?: string[];
replied_user?: boolean;
}
export interface Poll {
question: PollMedia;
answers: PollAnswer[];
expiry: Date;
allow_multiselect: boolean;
results?: PollResult;
}
export interface PollMedia {
text?: string;
emoji?: PartialEmoji;
}
export interface PollAnswer {
answer_id?: string;
poll_media: PollMedia;
}
export interface PollResult {
is_finalized: boolean;
answer_counts: PollAnswerCount[];
}
export interface PollAnswerCount {
id: string;
count: number;
me_voted: boolean;
}

View File

@ -49,6 +49,7 @@ export enum PublicUserEnum {
premium_type,
theme_colors,
pronouns,
badge_ids,
}
export type PublicUserKeys = keyof typeof PublicUserEnum;
@ -231,6 +232,9 @@ export class User extends BaseClass {
@OneToMany(() => SecurityKey, (key: SecurityKey) => key.user)
security_keys: SecurityKey[];
@Column({ type: "simple-array", nullable: true })
badge_ids?: string[];
// TODO: I don't like this method?
validate() {
if (this.discriminator) {

View File

@ -63,6 +63,9 @@ export class UserSettings extends BaseClassWithoutId {
@Column({ nullable: true })
explicit_content_filter: number = 0;
@Column({ nullable: true })
friend_discovery_flags: number = 0;
@Column({ nullable: true, type: "simple-json" })
friend_source_flags: FriendSourceFlags = { all: true };
@ -116,6 +119,9 @@ export class UserSettings extends BaseClassWithoutId {
@Column({ nullable: true })
timezone_offset: number = 0; // e.g -60
@Column({ nullable: true })
view_nsfw_guilds: boolean = true;
}
interface CustomStatus {

View File

@ -20,6 +20,7 @@ export * from "./Application";
export * from "./Attachment";
export * from "./AuditLog";
export * from "./BackupCodes";
export * from "./Badge";
export * from "./Ban";
export * from "./BaseClass";
export * from "./Categories";

View File

@ -129,6 +129,9 @@ export interface ReadyEventData {
| "REQUIRE_CAPTCHA" // TODO: allow these to be triggered
| "TOS_UPDATE_ACKNOWLEDGMENT"
| "AGREEMENTS";
notification_settings: {
flags: number;
};
}
export interface ReadyEvent extends Event {

View File

@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MessageFlagsNotNull1713116476900 implements MigrationInterface {
name = "MessageFlagsNotNull1713116476900";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE `messages` CHANGE flags flags_old integer;",
);
await queryRunner.query(
"ALTER TABLE `messages` ADD flags integer NOT NULL DEFAULT 0;",
);
await queryRunner.query(
"UPDATE `messages` SET flags = IFNULL(flags_old, 0);",
);
await queryRunner.query(
"ALTER TABLE `messages` DROP COLUMN flags_old;",
);
}
public async down(): Promise<void> {
// dont care
throw new Error("Migration down is not implemented.");
}
}

View File

@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class NewUserSettings1719776735000 implements MigrationInterface {
name = "NewUserSettings1719776735000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE `user_settings` ADD friend_discovery_flags integer NULL DEFAULT 0;",
);
await queryRunner.query(
"ALTER TABLE `user_settings` ADD view_nsfw_guilds tinyint NULL DEFAULT 1;",
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE `user_settings` DROP COLUMN friend_discovery_flags;",
);
await queryRunner.query(
"ALTER TABLE `user_settings` DROP COLUMN view_nsfw_guilds;",
);
}
}

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MessagePollObject1720157926878 implements MigrationInterface {
name = "MessagePollObject1720157926878";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("ALTER TABLE `messages` ADD `poll` text NULL");
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("ALTER TABLE `messages` DROP COLUMN `poll`");
}
}

View File

@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Badges1720628601997 implements MigrationInterface {
name = "Badges1720628601997";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE \`badges\` (\`id\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL, \`icon\` varchar(255) NOT NULL, \`link\` varchar(255) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`ALTER TABLE \`users\` ADD \`badge_ids\` text NULL`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`users\` DROP COLUMN \`badge_ids\``,
);
await queryRunner.query(`DROP TABLE \`badges\``);
}
}

View File

@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MessageFlagsNotNull1713116476900 implements MigrationInterface {
name = "MessageFlagsNotNull1713116476900";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE `messages` CHANGE flags flags_old integer;",
);
await queryRunner.query(
"ALTER TABLE `messages` ADD flags integer NOT NULL DEFAULT 0;",
);
await queryRunner.query(
"UPDATE `messages` SET flags = IFNULL(flags_old, 0);",
);
await queryRunner.query(
"ALTER TABLE `messages` DROP COLUMN flags_old;",
);
}
public async down(): Promise<void> {
// dont care
throw new Error("Migration down is not implemented.");
}
}

View File

@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class NewUserSettings1719776735000 implements MigrationInterface {
name = "NewUserSettings1719776735000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE `user_settings` ADD friend_discovery_flags integer NULL DEFAULT 0;",
);
await queryRunner.query(
"ALTER TABLE `user_settings` ADD view_nsfw_guilds tinyint NULL DEFAULT 1;",
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE `user_settings` DROP COLUMN friend_discovery_flags;",
);
await queryRunner.query(
"ALTER TABLE `user_settings` DROP COLUMN view_nsfw_guilds;",
);
}
}

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MessagePollObject1720157926878 implements MigrationInterface {
name = "MessagePollObject1720157926878";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("ALTER TABLE `messages` ADD `poll` text NULL");
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("ALTER TABLE `messages` DROP COLUMN `poll`");
}
}

View File

@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Badges1720628601997 implements MigrationInterface {
name = "Badges1720628601997";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE \`badges\` (\`id\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL, \`icon\` varchar(255) NOT NULL, \`link\` varchar(255) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`ALTER TABLE \`users\` ADD \`badge_ids\` text NULL`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`users\` DROP COLUMN \`badge_ids\``,
);
await queryRunner.query(`DROP TABLE \`badges\``);
}
}

View File

@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MessageFlagsNotNull1713116476900 implements MigrationInterface {
name = "MessageFlagsNotNull1713116476900";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE messages RENAME COLUMN flags TO flags_old;",
);
await queryRunner.query(
"ALTER TABLE messages ADD COLUMN flags integer NOT NULL DEFAULT 0;",
);
await queryRunner.query(
"UPDATE messages SET flags = COALESCE(flags_old, 0);",
);
await queryRunner.query("ALTER TABLE messages DROP COLUMN flags_old;");
}
public async down(): Promise<void> {
// dont care
throw new Error("Migration down is not implemented.");
}
}

View File

@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class NewUserSettings1719776735000 implements MigrationInterface {
name = "NewUserSettings1719776735000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE user_settings ADD COLUMN friend_discovery_flags integer DEFAULT 0;",
);
await queryRunner.query(
"ALTER TABLE user_settings ADD COLUMN view_nsfw_guilds boolean DEFAULT true;",
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE user_settings DROP COLUMN friend_discovery_flags;",
);
await queryRunner.query(
"ALTER TABLE user_settings DROP COLUMN view_nsfw_guilds;",
);
}
}

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MessagePollObject1720157926878 implements MigrationInterface {
name = "MessagePollObject1720157926878";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("ALTER TABLE messages ADD poll text NULL");
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("ALTER TABLE messages DROP COLUMN poll");
}
}

View File

@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Badges1720628601997 implements MigrationInterface {
name = "Badges1720628601997";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "badges" ("id" character varying NOT NULL, "description" character varying NOT NULL, "icon" character varying NOT NULL, "link" character varying, CONSTRAINT "PK_8a651318b8de577e8e217676466" PRIMARY KEY ("id"))`,
);
await queryRunner.query(`ALTER TABLE "users" ADD "badge_ids" text`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "badge_ids"`);
}
}

View File

@ -0,0 +1,22 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export interface BulkBanSchema {
user_ids: string[];
delete_message_seconds?: number;
}

Some files were not shown because too many files have changed in this diff Show More