diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e3bcc81f..d5ce87e2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,9 +2,9 @@ name: Build
on:
push:
- branches: [ "**" ]
+ branches: [ "master" ]
pull_request:
- branches: [ "**" ]
+ branches: [ "master" ]
jobs:
build:
@@ -25,4 +25,4 @@ jobs:
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- - run: npm run test --if-present
\ No newline at end of file
+ - run: npm run test --if-present
diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml
index 4fd62bae..8b18f6e6 100644
--- a/.github/workflows/style.yml
+++ b/.github/workflows/style.yml
@@ -2,9 +2,9 @@ name: Style
on:
push:
- branches: [ "**" ]
+ branches: [ "master" ]
pull_request:
- branches: [ "**" ]
+ branches: [ "master" ]
jobs:
build:
@@ -25,4 +25,4 @@ jobs:
cache: 'npm'
- run: npm i --only=dev
- run: npx eslint .
- - run: npx prettier --check .
\ No newline at end of file
+ - run: npx prettier --check .
diff --git a/README.md b/README.md
index c990c1d1..9e1d9490 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,9 @@
## [About](https://spacebar.chat)
+Spacebar/server is a Discord backend re-implementation and extension.
+We aim to reverse engineer and add additional features to the Discord backend, while remaining completely backwards compatible with existing bots, applications, and clients.
+
This repository contains:
- [Spacebar HTTP API Server](/src/api)
@@ -23,6 +26,13 @@ This repository contains:
- [HTTP CDN Server](/src/cdn)
- [Utility and Database Models](/src/util)
+## [Documentation](https://docs.spacebar.chat)
+
## [Contributing](https://docs.spacebar.chat/contributing/)
-## [Setup](https://docs.spacebar.chat/setup/server/)
+## Clients
+
+You *should* be able to use any client designed for Discord.com to connect to a Spacebar instance.
+However, some incompatibilities still exist between Spacebar and Discord. For this reason, not every client will connect.
+The [WIP official Spacebar client](https://github.com/spacebarchat/client) will always work.
+You can find a [live version here](https://app.spacebar.chat).
diff --git a/assets/schemas.json b/assets/schemas.json
index 647e693b..6c00662e 100644
--- a/assets/schemas.json
+++ b/assets/schemas.json
@@ -1348,7 +1348,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -4912,7 +4911,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -8476,7 +8474,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -12035,7 +12032,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -15630,7 +15626,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -19194,7 +19189,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -22749,7 +22743,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -26307,7 +26300,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -29874,7 +29866,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -33429,7 +33420,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -36984,7 +36974,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -40558,7 +40547,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -44116,7 +44104,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -47734,7 +47721,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -51311,7 +51297,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -55029,7 +55014,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -58605,7 +58589,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -62191,7 +62174,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -65759,7 +65741,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -69333,7 +69314,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -72897,7 +72877,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -76456,7 +76435,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -80119,7 +80097,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -83779,7 +83756,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -87334,7 +87310,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -90897,7 +90872,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -94453,7 +94427,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -98009,7 +97982,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -101600,7 +101572,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -105156,7 +105127,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -108711,7 +108681,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -112281,7 +112250,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -115840,7 +115808,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -119473,7 +119440,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -123028,7 +122994,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -126583,7 +126548,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -130135,7 +130099,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -133693,7 +133656,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -137261,7 +137223,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -140813,7 +140774,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -144414,7 +144374,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -148001,7 +147960,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -151553,7 +151511,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -155130,7 +155087,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -157694,6 +157650,16 @@
"UserSettingsSchema": {
"type": "object",
"properties": {
+ "status": {
+ "enum": [
+ "dnd",
+ "idle",
+ "invisible",
+ "offline",
+ "online"
+ ],
+ "type": "string"
+ },
"afk_timeout": {
"type": "integer"
},
@@ -157791,16 +157757,6 @@
"show_current_game": {
"type": "boolean"
},
- "status": {
- "enum": [
- "dnd",
- "idle",
- "invisible",
- "offline",
- "online"
- ],
- "type": "string"
- },
"stream_notifications_enabled": {
"type": "boolean"
},
@@ -158796,7 +158752,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -162347,7 +162302,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -165937,7 +165891,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -169515,7 +169468,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -173145,7 +173097,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -176697,7 +176648,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -180257,7 +180207,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -183807,7 +183756,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -187363,7 +187311,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -190919,7 +190866,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -194475,7 +194421,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -198035,7 +197980,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -201591,7 +201535,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -205158,7 +205101,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -208739,7 +208681,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -212291,7 +212232,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -215846,7 +215786,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -219430,7 +219369,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -222982,7 +222920,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -226613,7 +226550,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -230172,7 +230108,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -233724,7 +233659,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -237276,7 +237210,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -240835,7 +240768,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -244391,7 +244323,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -247943,7 +247874,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -251570,7 +251500,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -255133,7 +255062,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -258697,7 +258625,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -262311,7 +262238,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -265884,7 +265810,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -269463,7 +269388,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -273036,7 +272960,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -276588,7 +276511,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -280168,7 +280090,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -283760,7 +283681,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -287306,7 +287226,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -290862,7 +290781,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -294414,7 +294332,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -297973,7 +297890,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -301613,7 +301529,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -305157,7 +305072,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -308701,7 +308615,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -312245,7 +312158,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -315812,7 +315724,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -319372,7 +319283,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -323041,7 +322951,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -326611,7 +326520,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -330175,7 +330083,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -333731,7 +333638,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -337297,7 +337203,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -340853,7 +340758,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -344396,7 +344300,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -347951,7 +347854,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -351521,7 +351423,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -355095,7 +354996,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -358648,7 +358548,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -362200,7 +362099,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -365752,7 +365650,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
@@ -369310,7 +369207,6 @@
"created_at",
"default_thread_rate_limit_per_user",
"flags",
- "guild",
"id",
"nsfw",
"owner",
diff --git a/package-lock.json b/package-lock.json
index 0a58af93..193fd7cd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,9 +11,8 @@
"license": "AGPL-3.0-only",
"dependencies": {
"@aws-sdk/client-s3": "^3.385.0",
- "@sentry/integrations": "^7.61.1",
- "@sentry/node": "^7.61.1",
- "@sentry/tracing": "^7.61.1",
+ "@sentry/integrations": "^7.66.0",
+ "@sentry/node": "^7.66.0",
"ajv": "8.6.2",
"ajv-formats": "2.1.1",
"amqplib": "^0.10.3",
@@ -1249,41 +1248,34 @@
"node": "6.* || 8.* || >=10.*"
}
},
- "node_modules/@sentry-internal/tracing": {
- "version": "7.63.0",
- "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.63.0.tgz",
- "integrity": "sha512-Fxpc53p6NGvLSURg3iRvZA0k10K9yfeVhtczvJnpX30POBuV41wxpkLHkb68fjksirjEma1K3Ut1iLOEEDpPQg==",
+ "node_modules/@sentry/integrations": {
+ "version": "7.66.0",
+ "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.66.0.tgz",
+ "integrity": "sha512-2PNEnihG9e9Rjbz205+A4BYtFcS2XdgwsN6obAU6Yir7VIbskwZXxx87lKZuz6S53sOWPHleC7uvUBjL+Q6vYg==",
"dependencies": {
- "@sentry/core": "7.63.0",
- "@sentry/types": "7.63.0",
- "@sentry/utils": "7.63.0",
+ "@sentry/types": "7.66.0",
+ "@sentry/utils": "7.66.0",
+ "localforage": "^1.8.1",
"tslib": "^2.4.1 || ^1.9.3"
},
"engines": {
"node": ">=8"
}
},
- "node_modules/@sentry/core": {
- "version": "7.63.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.63.0.tgz",
- "integrity": "sha512-13Ljiq8hv6ieCkO+Am99/PljYJO5ynKT/hRQrWgGy9IIEgUr8sV3fW+1W6K4/3MCeOJou0HsiGBjOD1mASItVg==",
- "dependencies": {
- "@sentry/types": "7.63.0",
- "@sentry/utils": "7.63.0",
- "tslib": "^2.4.1 || ^1.9.3"
- },
+ "node_modules/@sentry/integrations/node_modules/@sentry/types": {
+ "version": "7.66.0",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.66.0.tgz",
+ "integrity": "sha512-uUMSoSiar6JhuD8p7ON/Ddp4JYvrVd2RpwXJRPH1A4H4Bd4DVt1mKJy1OLG6HdeQv39XyhB1lPZckKJg4tATPw==",
"engines": {
"node": ">=8"
}
},
- "node_modules/@sentry/integrations": {
- "version": "7.63.0",
- "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.63.0.tgz",
- "integrity": "sha512-+P8GNqFZNH/yS/KPbvUfUDERneoRNUrqp9ayvvp8aq4cTtrBdM72CYgI21oG6cti42SSM1VDLYZomTV3ElPzSg==",
+ "node_modules/@sentry/integrations/node_modules/@sentry/utils": {
+ "version": "7.66.0",
+ "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.66.0.tgz",
+ "integrity": "sha512-9GYUVgXjK66uXXcLXVMXVzlptqMtq1eJENCuDeezQiEFrNA71KkLDg00wESp+LL+bl3wpVTBApArpbF6UEG5hQ==",
"dependencies": {
- "@sentry/types": "7.63.0",
- "@sentry/utils": "7.63.0",
- "localforage": "^1.8.1",
+ "@sentry/types": "7.66.0",
"tslib": "^2.4.1 || ^1.9.3"
},
"engines": {
@@ -1291,14 +1283,14 @@
}
},
"node_modules/@sentry/node": {
- "version": "7.63.0",
- "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.63.0.tgz",
- "integrity": "sha512-tSMyfQNbfjX1w8vJDZtvWeaD4QQ/Z4zVW/TLXfL/JZFIIksPgDZmqLdF+NJS4bSGTU5JiHiUh4pYhME4mHgNBQ==",
- "dependencies": {
- "@sentry-internal/tracing": "7.63.0",
- "@sentry/core": "7.63.0",
- "@sentry/types": "7.63.0",
- "@sentry/utils": "7.63.0",
+ "version": "7.66.0",
+ "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.66.0.tgz",
+ "integrity": "sha512-PxqIqLr4Sh5xcDfECiBQ4PuZ7v8yTgLhaRkruWrZPYxQrcJFPkwbFkw/IskzVnhT2VwXUmeWEIlRMQKBJ0t83A==",
+ "dependencies": {
+ "@sentry-internal/tracing": "7.66.0",
+ "@sentry/core": "7.66.0",
+ "@sentry/types": "7.66.0",
+ "@sentry/utils": "7.66.0",
"cookie": "^0.4.1",
"https-proxy-agent": "^5.0.0",
"lru_map": "^0.3.3",
@@ -1308,31 +1300,47 @@
"node": ">=8"
}
},
- "node_modules/@sentry/tracing": {
- "version": "7.63.0",
- "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.63.0.tgz",
- "integrity": "sha512-91gjqM/3CD6XdN1JVSLnUTD7HAI77NodP48+FZ2kgRkNmD2jojJBWsTC9NHG4UEO0PppjjwDPPJR1iHwybaO8g==",
+ "node_modules/@sentry/node/node_modules/@sentry-internal/tracing": {
+ "version": "7.66.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.66.0.tgz",
+ "integrity": "sha512-3vCgC2hC3T45pn53yTDVcRpHoJTBxelDPPZVsipAbZnoOVPkj7n6dNfDhj3I3kwWCBPahPkXmE+R4xViR8VqJg==",
"dependencies": {
- "@sentry-internal/tracing": "7.63.0"
+ "@sentry/core": "7.66.0",
+ "@sentry/types": "7.66.0",
+ "@sentry/utils": "7.66.0",
+ "tslib": "^2.4.1 || ^1.9.3"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@sentry/node/node_modules/@sentry/core": {
+ "version": "7.66.0",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.66.0.tgz",
+ "integrity": "sha512-WMAEPN86NeCJ1IT48Lqiz4MS5gdDjBwP4M63XP4msZn9aujSf2Qb6My5uT87AJr9zBtgk8MyJsuHr35F0P3q1w==",
+ "dependencies": {
+ "@sentry/types": "7.66.0",
+ "@sentry/utils": "7.66.0",
+ "tslib": "^2.4.1 || ^1.9.3"
},
"engines": {
"node": ">=8"
}
},
- "node_modules/@sentry/types": {
- "version": "7.63.0",
- "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.63.0.tgz",
- "integrity": "sha512-pZNwJVW7RqNLGuTUAhoygt0c9zmc0js10eANAz0MstygJRhQI1tqPDuiELVdujPrbeL+IFKF+7NvRDAydR2Niw==",
+ "node_modules/@sentry/node/node_modules/@sentry/types": {
+ "version": "7.66.0",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.66.0.tgz",
+ "integrity": "sha512-uUMSoSiar6JhuD8p7ON/Ddp4JYvrVd2RpwXJRPH1A4H4Bd4DVt1mKJy1OLG6HdeQv39XyhB1lPZckKJg4tATPw==",
"engines": {
"node": ">=8"
}
},
- "node_modules/@sentry/utils": {
- "version": "7.63.0",
- "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.63.0.tgz",
- "integrity": "sha512-7FQv1RYAwnuTuarruP+1+Jd6YQuN7i/Y7KltwPMVEwU7j5mzYQaexLr/Jz1XIdR2KYVdkbXQyP8jj8BmA6u9Jw==",
+ "node_modules/@sentry/node/node_modules/@sentry/utils": {
+ "version": "7.66.0",
+ "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.66.0.tgz",
+ "integrity": "sha512-9GYUVgXjK66uXXcLXVMXVzlptqMtq1eJENCuDeezQiEFrNA71KkLDg00wESp+LL+bl3wpVTBApArpbF6UEG5hQ==",
"dependencies": {
- "@sentry/types": "7.63.0",
+ "@sentry/types": "7.66.0",
"tslib": "^2.4.1 || ^1.9.3"
},
"engines": {
diff --git a/package.json b/package.json
index 7d0bd2a6..de0374d6 100644
--- a/package.json
+++ b/package.json
@@ -68,9 +68,8 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.385.0",
- "@sentry/integrations": "^7.61.1",
- "@sentry/node": "^7.61.1",
- "@sentry/tracing": "^7.61.1",
+ "@sentry/integrations": "^7.66.0",
+ "@sentry/node": "^7.66.0",
"ajv": "8.6.2",
"ajv-formats": "2.1.1",
"amqplib": "^0.10.3",
diff --git a/scripts/schema.js b/scripts/schema.js
index ff3280ac..74ef908c 100644
--- a/scripts/schema.js
+++ b/scripts/schema.js
@@ -72,6 +72,7 @@ function main() {
(x.endsWith("Schema") ||
x.endsWith("Response") ||
x.startsWith("API")) &&
+ !x.startsWith("AP") &&
!Excluded.includes(x)
);
});
diff --git a/src/activitypub/util/transforms/index.ts b/src/activitypub/util/transforms/index.ts
index e8107ca0..7333233e 100644
--- a/src/activitypub/util/transforms/index.ts
+++ b/src/activitypub/util/transforms/index.ts
@@ -126,7 +126,7 @@ export const messageFromAP = async (data: APNote): Promise<Message> => {
const member =
channel instanceof Channel
? await Member.findOneOrFail({
- where: { id: user.id, guild_id: channel.guild.id },
+ where: { id: user.id, guild_id: channel.guild!.id },
})
: undefined;
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index c384a05b..a5bfcfd7 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -40,7 +40,13 @@ import {
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import multer from "multer";
-import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm";
+import {
+ FindManyOptions,
+ FindOperator,
+ LessThan,
+ MoreThan,
+ MoreThanOrEqual,
+} from "typeorm";
import { URL } from "url";
const router: Router = Router();
@@ -122,12 +128,24 @@ router.get(
if (around) {
query.take = Math.floor(limit / 2);
- const [right, left] = await Promise.all([
- Message.find({ ...query, where: { id: LessThan(around) } }),
- Message.find({ ...query, where: { id: MoreThan(around) } }),
- ]);
- right.push(...left);
- messages = right;
+ if (query.take != 0) {
+ const [right, left] = await Promise.all([
+ Message.find({ ...query, where: { id: LessThan(around) } }),
+ Message.find({
+ ...query,
+ where: { id: MoreThanOrEqual(around) },
+ }),
+ ]);
+ left.push(...right);
+ messages = left;
+ } else {
+ query.take = 1;
+ const message = await Message.findOne({
+ ...query,
+ where: { id: around },
+ });
+ messages = message ? [message] : [];
+ }
} else {
if (after) {
if (BigInt(after) > BigInt(Snowflake.generate()))
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index cafb922e..c168f2dc 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -18,6 +18,7 @@
import { route } from "@spacebar/api";
import {
+ DiscordApiErrors,
emitEvent,
Emoji,
getPermission,
@@ -198,7 +199,9 @@ router.put(
member_id = req.user_id;
rights.hasThrow("JOIN_GUILDS");
} else {
- // TODO: join others by controller
+ // TODO: check oauth2 scope
+
+ throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE;
}
const guild = await Guild.findOneOrFail({
diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
index 15e3f67f..0f1e88a5 100644
--- a/src/api/util/utility/EmbedHandlers.ts
+++ b/src/api/util/utility/EmbedHandlers.ts
@@ -16,12 +16,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Config, Embed, EmbedType } from "@spacebar/util";
-import fetch, { RequestInit } from "node-fetch";
+import { Config, Embed, EmbedImage, EmbedType } from "@spacebar/util";
import * as cheerio from "cheerio";
-import probe from "probe-image-size";
import crypto from "crypto";
+import fetch, { RequestInit } from "node-fetch";
import { yellow } from "picocolors";
+import probe from "probe-image-size";
export const DEFAULT_FETCH_OPTIONS: RequestInit = {
redirect: "follow",
@@ -35,6 +35,20 @@ export const DEFAULT_FETCH_OPTIONS: RequestInit = {
method: "GET",
};
+const makeEmbedImage = (
+ url: string | undefined,
+ width: number | undefined,
+ height: number | undefined,
+): Required<EmbedImage> | undefined => {
+ if (!url || !width || !height) return undefined;
+ return {
+ url,
+ width,
+ height,
+ proxy_url: getProxyUrl(new URL(url), width, height),
+ };
+};
+
let hasWarnedAboutImagor = false;
export const getProxyUrl = (
@@ -78,13 +92,24 @@ export const getProxyUrl = (
const getMeta = ($: cheerio.CheerioAPI, name: string): string | undefined => {
let elem = $(`meta[property="${name}"]`);
if (!elem.length) elem = $(`meta[name="${name}"]`);
- return elem.attr("content") || elem.text();
+ const ret = elem.attr("content") || elem.text();
+ return ret.trim().length == 0 ? undefined : ret;
+};
+
+const tryParseInt = (str: string | undefined) => {
+ if (!str) return undefined;
+ try {
+ return parseInt(str);
+ } catch (e) {
+ return undefined;
+ }
};
export const getMetaDescriptions = (text: string) => {
const $ = cheerio.load(text);
return {
+ type: getMeta($, "og:type"),
title: getMeta($, "og:title") || $("title").first().text(),
provider_name: getMeta($, "og:site_name"),
author: getMeta($, "article:author"),
@@ -92,10 +117,13 @@ export const getMetaDescriptions = (text: string) => {
image: getMeta($, "og:image") || getMeta($, "twitter:image"),
image_fallback: $(`image`).attr("src"),
video_fallback: $(`video`).attr("src"),
- width: parseInt(getMeta($, "og:image:width") || "0"),
- height: parseInt(getMeta($, "og:image:height") || "0"),
+ width: tryParseInt(getMeta($, "og:image:width")),
+ height: tryParseInt(getMeta($, "og:image:height")),
url: getMeta($, "og:url"),
youtube_embed: getMeta($, "og:video:secure_url"),
+ site_name: getMeta($, "og:site_name"),
+
+ $,
};
};
@@ -116,13 +144,11 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
method: "HEAD",
});
- let width, height, image;
+ let image;
if (type.headers.get("content-type")?.indexOf("image") !== -1) {
const result = await probe(url.href);
- width = result.width;
- height = result.height;
- image = url.href;
+ image = makeEmbedImage(url.href, result.width, result.height);
} else if (type.headers.get("content-type")?.indexOf("video") !== -1) {
// TODO
return null;
@@ -131,22 +157,19 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => {
const response = await doFetch(url);
if (!response) return null;
const metas = getMetaDescriptions(await response.text());
- width = metas.width;
- height = metas.height;
- image = metas.image || metas.image_fallback;
+ image = makeEmbedImage(
+ metas.image || metas.image_fallback,
+ metas.width,
+ metas.height,
+ );
}
- if (!width || !height || !image) return null;
+ if (!image) return null;
return {
url: url.href,
type: EmbedType.image,
- thumbnail: {
- width: width,
- height: height,
- url: url.href,
- proxy_url: getProxyUrl(new URL(image), width, height),
- },
+ thumbnail: image,
};
};
@@ -165,7 +188,8 @@ export const EmbedHandlers: {
const response = await doFetch(url);
if (!response) return null;
- const metas = getMetaDescriptions(await response.text());
+ const text = await response.text();
+ const metas = getMetaDescriptions(text);
// TODO: handle video
@@ -178,26 +202,27 @@ export const EmbedHandlers: {
}
if (!metas.image && (!metas.title || !metas.description)) {
+ // we don't have any content to display
return null;
}
+ let embedType = EmbedType.link;
+ if (metas.type == "article") embedType = EmbedType.article;
+ if (metas.type == "object") embedType = EmbedType.article; // github
+ if (metas.type == "rich") embedType = EmbedType.rich;
+
return {
url: url.href,
- type: EmbedType.link,
+ type: embedType,
title: metas.title,
- thumbnail: {
- width: metas.width,
- height: metas.height,
- url: metas.image,
- proxy_url: metas.image
- ? getProxyUrl(
- new URL(metas.image),
- metas.width,
- metas.height,
- )
- : undefined,
- },
+ thumbnail: makeEmbedImage(metas.image, metas.width, metas.height),
description: metas.description,
+ provider: metas.site_name
+ ? {
+ name: metas.site_name,
+ url: url.origin,
+ }
+ : undefined,
};
},
@@ -207,12 +232,23 @@ export const EmbedHandlers: {
"c.tenor.com": genericImageHandler,
"media.tenor.com": genericImageHandler,
- // TODO: facebook
- // have to use their APIs or something because they don't send the metas in initial html
+ "facebook.com": (url) => EmbedHandlers["www.facebook.com"](url),
+ "www.facebook.com": async (url: URL) => {
+ const response = await doFetch(url);
+ if (!response) return null;
+ const metas = getMetaDescriptions(await response.text());
- "twitter.com": (url: URL) => {
- return EmbedHandlers["www.twitter.com"](url);
+ return {
+ url: url.href,
+ type: EmbedType.link,
+ title: metas.title,
+ description: metas.description,
+ thumbnail: makeEmbedImage(metas.image, 640, 640),
+ color: 16777215,
+ };
},
+
+ "twitter.com": (url) => EmbedHandlers["www.twitter.com"](url),
"www.twitter.com": async (url: URL) => {
const token = Config.get().external.twitter;
if (!token) return null;
@@ -330,14 +366,7 @@ export const EmbedHandlers: {
type: EmbedType.link,
title: metas.title,
description: metas.description,
- thumbnail: {
- width: 640,
- height: 640,
- proxy_url: metas.image
- ? getProxyUrl(new URL(metas.image), 640, 640)
- : undefined,
- url: metas.image,
- },
+ thumbnail: makeEmbedImage(metas.image, 640, 640),
provider: {
url: "https://spotify.com",
name: "Spotify",
@@ -345,32 +374,25 @@ export const EmbedHandlers: {
};
},
- "pixiv.net": (url: URL) => {
- return EmbedHandlers["www.pixiv.net"](url);
- },
+ // TODO: docs: Pixiv won't work without Imagor
+ "pixiv.net": (url) => EmbedHandlers["www.pixiv.net"](url),
"www.pixiv.net": async (url: URL) => {
const response = await doFetch(url);
if (!response) return null;
const metas = getMetaDescriptions(await response.text());
- // TODO: doesn't show images. think it's a bug in the cdn
+ if (!metas.image) return null;
+
return {
url: url.href,
type: EmbedType.image,
title: metas.title,
description: metas.description,
- image: {
- width: metas.width,
- height: metas.height,
- url: url.href,
- proxy_url: metas.image
- ? getProxyUrl(
- new URL(metas.image),
- metas.width,
- metas.height,
- )
- : undefined,
- },
+ image: makeEmbedImage(
+ metas.image || metas.image_fallback,
+ metas.width,
+ metas.height,
+ ),
provider: {
url: "https://pixiv.net",
name: "Pixiv",
@@ -382,6 +404,42 @@ export const EmbedHandlers: {
const response = await doFetch(url);
if (!response) return null;
const metas = getMetaDescriptions(await response.text());
+ const numReviews = metas.$("#review_summary_num_reviews").val() as
+ | string
+ | undefined;
+ const price = metas
+ .$(".game_purchase_price.price")
+ .data("price-final") as number | undefined;
+ const releaseDate = metas
+ .$(".release_date")
+ .find("div.date")
+ .text()
+ .trim();
+ const isReleased = new Date(releaseDate) < new Date();
+
+ const fields: Embed["fields"] = [];
+
+ if (numReviews)
+ fields.push({
+ name: "Reviews",
+ value: numReviews,
+ inline: true,
+ });
+
+ if (price)
+ fields.push({
+ name: "Price",
+ value: `$${price / 100}`,
+ inline: true,
+ });
+
+ // if the release date is in the past, it's already out
+ if (releaseDate && !isReleased)
+ fields.push({
+ name: "Release Date",
+ value: releaseDate,
+ inline: true,
+ });
return {
url: url.href,
@@ -402,14 +460,12 @@ export const EmbedHandlers: {
url: "https://store.steampowered.com",
name: "Steam",
},
- // TODO: fields for release date
+ fields,
// TODO: Video
};
},
- "reddit.com": (url: URL) => {
- return EmbedHandlers["www.reddit.com"](url);
- },
+ "reddit.com": (url) => EmbedHandlers["www.reddit.com"](url),
"www.reddit.com": async (url: URL) => {
const res = await EmbedHandlers["default"](url);
return {
@@ -420,49 +476,65 @@ export const EmbedHandlers: {
},
};
},
- "youtu.be": (url: URL) => {
- return EmbedHandlers["www.youtube.com"](url);
- },
- "youtube.com": (url: URL) => {
- return EmbedHandlers["www.youtube.com"](url);
- },
+
+ "youtu.be": (url) => EmbedHandlers["www.youtube.com"](url),
+ "youtube.com": (url) => EmbedHandlers["www.youtube.com"](url),
"www.youtube.com": async (url: URL): Promise<Embed | null> => {
const response = await doFetch(url);
if (!response) return null;
const metas = getMetaDescriptions(await response.text());
return {
- video: {
- // TODO: does this adjust with aspect ratio?
- width: metas.width,
- height: metas.height,
- url: metas.youtube_embed,
- },
+ video: makeEmbedImage(
+ metas.youtube_embed,
+ metas.width,
+ metas.height,
+ ),
url: url.href,
- type: EmbedType.video,
+ type: metas.youtube_embed ? EmbedType.video : EmbedType.link,
title: metas.title,
- thumbnail: {
- width: metas.width,
- height: metas.height,
- url: metas.image,
- proxy_url: metas.image
- ? getProxyUrl(
- new URL(metas.image),
- metas.width,
- metas.height,
- )
- : undefined,
- },
+ thumbnail: makeEmbedImage(
+ metas.image || metas.image_fallback,
+ metas.width,
+ metas.height,
+ ),
provider: {
url: "https://www.youtube.com",
name: "YouTube",
},
description: metas.description,
color: 16711680,
- author: {
- name: metas.author,
- // TODO: author channel url
- },
+ author: metas.author
+ ? {
+ name: metas.author,
+ // TODO: author channel url
+ }
+ : undefined,
+ };
+ },
+
+ "www.xkcd.com": (url) => EmbedHandlers["xkcd.com"](url),
+ "xkcd.com": async (url) => {
+ const response = await doFetch(url);
+ if (!response) return null;
+
+ const metas = getMetaDescriptions(await response.text());
+ const hoverText = metas.$("#comic img").attr("title");
+
+ if (!metas.image) return null;
+
+ const { width, height } = await probe(metas.image);
+
+ return {
+ url: url.href,
+ type: EmbedType.rich,
+ title: `xkcd: ${metas.title}`,
+ image: makeEmbedImage(metas.image, width, height),
+ footer: hoverText
+ ? {
+ text: hoverText,
+ }
+ : undefined,
};
},
diff --git a/src/gateway/events/Message.ts b/src/gateway/events/Message.ts
index 45790146..52d9edd8 100644
--- a/src/gateway/events/Message.ts
+++ b/src/gateway/events/Message.ts
@@ -16,15 +16,15 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { WebSocket, Payload, CLOSECODES, OPCODES } from "@spacebar/gateway";
-import OPCodeHandlers from "../opcodes";
-import { check } from "../opcodes/instanceOf";
-import WS from "ws";
-import { PayloadSchema, ErlpackType } from "@spacebar/util";
import * as Sentry from "@sentry/node";
+import { CLOSECODES, OPCODES, Payload, WebSocket } from "@spacebar/gateway";
+import { ErlpackType, PayloadSchema } from "@spacebar/util";
+import fs from "fs/promises";
import BigIntJson from "json-bigint";
import path from "path";
-import fs from "fs/promises";
+import WS from "ws";
+import OPCodeHandlers from "../opcodes";
+import { check } from "../opcodes/instanceOf";
const bigIntJson = BigIntJson({ storeAsString: true });
let erlpack: ErlpackType | null = null;
@@ -88,33 +88,28 @@ export async function Message(this: WebSocket, buffer: WS.Data) {
return;
}
- const transaction =
- data.op != 1
- ? Sentry.startTransaction({
- op: OPCODES[data.op],
- name: `GATEWAY ${OPCODES[data.op]}`,
- data: {
- ...data.d,
- token: data?.d?.token ? "[Redacted]" : undefined,
- },
- })
- : undefined;
-
try {
- const ret = await OPCodeHandler.call(this, data);
- Sentry.withScope((scope) => {
- scope.setSpan(transaction);
- scope.setUser({ id: this.user_id });
- transaction?.finish();
- });
- return ret;
+ return await Sentry.startActiveSpan(
+ {
+ op: "websocket.server",
+ name: `GATEWAY ${OPCODES[data.op]}`,
+ data: {
+ ...data.d,
+ token: data?.d?.token ? "[Redacted]" : undefined,
+ },
+ },
+ async () => {
+ const ret = await OPCodeHandler.call(this, data);
+ Sentry.setUser({ id: this.user_id });
+ return ret;
+ },
+ );
} catch (error) {
- Sentry.withScope((scope) => {
- scope.setSpan(transaction);
- if (this.user_id) scope.setUser({ id: this.user_id });
- Sentry.captureException(error);
+ Sentry.captureException(error, {
+ user: {
+ id: this.user_id,
+ },
});
- transaction?.finish();
console.error(`Error: Op ${data.op}`, error);
// if (!this.CLOSED && this.CLOSING)
return this.close(CLOSECODES.Unknown_error);
diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts
index 7610901a..9a3128d9 100644
--- a/src/gateway/opcodes/Identify.ts
+++ b/src/gateway/opcodes/Identify.ts
@@ -17,49 +17,57 @@
*/
import {
- WebSocket,
- Payload,
- setupListener,
- Capabilities,
CLOSECODES,
+ Capabilities,
OPCODES,
+ Payload,
Send,
+ WebSocket,
+ setupListener,
} from "@spacebar/gateway";
import {
- checkToken,
+ Application,
+ Config,
+ DMChannel,
+ DefaultUserGuildSettings,
+ EVENTEnum,
+ Guild,
+ GuildOrUnavailable,
+ IdentifySchema,
Intents,
Member,
- ReadyEventData,
- Session,
- EVENTEnum,
- Config,
- PublicUser,
- PrivateUserProjection,
- ReadState,
- Application,
- emitEvent,
- SessionsReplace,
- PrivateSessionProjection,
MemberPrivateProjection,
+ OPCodes,
+ Permissions,
PresenceUpdateEvent,
- IdentifySchema,
- DefaultUserGuildSettings,
- ReadyGuildDTO,
- Guild,
+ PrivateSessionProjection,
+ PrivateUserProjection,
+ PublicUser,
PublicUserProjection,
+ ReadState,
+ ReadyEventData,
+ ReadyGuildDTO,
ReadyUserGuildSettingsEntries,
- UserSettings,
- Permissions,
- DMChannel,
- GuildOrUnavailable,
Recipient,
- OPCodes,
+ Session,
+ SessionsReplace,
+ UserSettings,
+ checkToken,
+ emitEvent,
} from "@spacebar/util";
import { check } from "./instanceOf";
// TODO: user sharding
// TODO: check privileged intents, if defined in the config
+const tryGetUserFromToken = async (...args: Parameters<typeof checkToken>) => {
+ try {
+ return (await checkToken(...args)).user;
+ } catch (e) {
+ return null;
+ }
+};
+
export async function onIdentify(this: WebSocket, data: Payload) {
if (this.user_id) {
// we've already identified
@@ -74,7 +82,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
this.capabilities = new Capabilities(identify.capabilities || 0);
- const { user } = await checkToken(identify.token, {
+ const user = await tryGetUserFromToken(identify.token, {
relations: ["relationships", "relationships.to", "settings"],
select: [...PrivateUserProjection, "relationships"],
});
@@ -332,10 +340,9 @@ export async function onIdentify(this: WebSocket, data: Payload) {
// TODO how is active determined?
// in our lazy request impl, we just pick the 'most relevant' session
active: x.session_id == session.session_id,
- activities: x.activities,
+ activities: x.activities ?? [],
client_info: x.client_info,
- // TODO: what does all mean?
- session_id: x.session_id == session.session_id ? "all" : x.session_id,
+ session_id: x.session_id, // TODO: discord.com sends 'all', what is that???
status: x.status,
}));
diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts
index 8b692ac7..0ccabd62 100644
--- a/src/util/entities/Channel.ts
+++ b/src/util/entities/Channel.ts
@@ -102,10 +102,11 @@ export class Channel extends BaseClass {
guild_id?: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, {
+ @ManyToOne(() => Guild, (guild) => guild.channels, {
onDelete: "CASCADE",
+ nullable: true,
})
- guild: Guild;
+ guild?: Guild;
@Column({ nullable: true })
@RelationId((channel: Channel) => channel.parent)
@@ -571,7 +572,6 @@ export interface DMChannel extends Omit<Channel, "type" | "recipients"> {
export function isTextChannel(type: ChannelType): boolean {
switch (type) {
case ChannelType.GUILD_STORE:
- case ChannelType.GUILD_VOICE:
case ChannelType.GUILD_STAGE_VOICE:
case ChannelType.GUILD_CATEGORY:
case ChannelType.GUILD_FORUM:
@@ -580,6 +580,7 @@ export function isTextChannel(type: ChannelType): boolean {
case ChannelType.DM:
case ChannelType.GROUP_DM:
case ChannelType.GUILD_NEWS:
+ case ChannelType.GUILD_VOICE:
case ChannelType.GUILD_NEWS_THREAD:
case ChannelType.GUILD_PUBLIC_THREAD:
case ChannelType.GUILD_PRIVATE_THREAD:
diff --git a/src/util/entities/Emoji.ts b/src/util/entities/Emoji.ts
index 0bc2f423..4d851698 100644
--- a/src/util/entities/Emoji.ts
+++ b/src/util/entities/Emoji.ts
@@ -33,7 +33,7 @@ export class Emoji extends BaseClass {
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, {
+ @ManyToOne(() => Guild, (guild) => guild.emojis, {
onDelete: "CASCADE",
})
guild: Guild;
diff --git a/src/util/entities/Invite.ts b/src/util/entities/Invite.ts
index 7970c4f0..f7e54fbe 100644
--- a/src/util/entities/Invite.ts
+++ b/src/util/entities/Invite.ts
@@ -53,7 +53,7 @@ export class Invite extends BaseClassWithoutId {
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, {
+ @ManyToOne(() => Guild, (guild) => guild.invites, {
onDelete: "CASCADE",
})
guild: Guild;
diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts
index e7b89976..16b18ab1 100644
--- a/src/util/entities/Member.ts
+++ b/src/util/entities/Member.ts
@@ -327,6 +327,7 @@ export class Member extends BaseClassWithoutId {
id: guild_id,
},
relations: PublicGuildRelations,
+ relationLoadStrategy: "query",
});
const memberCount = await Member.count({ where: { guild_id } });
diff --git a/src/util/entities/Role.ts b/src/util/entities/Role.ts
index 9a601f31..e8e5feda 100644
--- a/src/util/entities/Role.ts
+++ b/src/util/entities/Role.ts
@@ -23,12 +23,12 @@ import { Guild } from "./Guild";
@Entity("roles")
export class Role extends BaseClass {
- @Column({ nullable: true })
+ @Column()
@RelationId((role: Role) => role.guild)
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, {
+ @ManyToOne(() => Guild, (guild) => guild.roles, {
onDelete: "CASCADE",
})
guild: Guild;
diff --git a/src/util/entities/Sticker.ts b/src/util/entities/Sticker.ts
index cd07e65a..e9294f92 100644
--- a/src/util/entities/Sticker.ts
+++ b/src/util/entities/Sticker.ts
@@ -17,9 +17,9 @@
*/
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
-import { User } from "./User";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
+import { User } from "./User";
export enum StickerType {
STANDARD = 1,
@@ -62,7 +62,7 @@ export class Sticker extends BaseClass {
guild_id?: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, {
+ @ManyToOne(() => Guild, (guild) => guild.stickers, {
onDelete: "CASCADE",
})
guild?: Guild;
diff --git a/src/util/entities/VoiceState.ts b/src/util/entities/VoiceState.ts
index b291c4d3..84b0ca71 100644
--- a/src/util/entities/VoiceState.ts
+++ b/src/util/entities/VoiceState.ts
@@ -20,8 +20,8 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
import { Guild } from "./Guild";
-import { User } from "./User";
import { Member } from "./Member";
+import { User } from "./User";
//https://gist.github.com/vassjozsef/e482c65df6ee1facaace8b3c9ff66145#file-voice_state-ex
@Entity("voice_states")
@@ -31,7 +31,7 @@ export class VoiceState extends BaseClass {
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, {
+ @ManyToOne(() => Guild, (guild) => guild.voice_states, {
onDelete: "CASCADE",
})
guild?: Guild;
diff --git a/src/util/util/Database.ts b/src/util/util/Database.ts
index a6b24b3e..3a45eea0 100644
--- a/src/util/util/Database.ts
+++ b/src/util/util/Database.ts
@@ -16,12 +16,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { DataSource } from "typeorm";
-import { yellow, green, red } from "picocolors";
-import { Migration } from "../entities/Migration";
-import { ConfigEntity } from "../entities/Config";
import { config } from "dotenv";
import path from "path";
+import { green, red, yellow } from "picocolors";
+import { DataSource } from "typeorm";
+import { ConfigEntity } from "../entities/Config";
+import { Migration } from "../entities/Migration";
// UUID extension option is only supported with postgres
// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class
@@ -50,7 +50,7 @@ const DataSourceOptions = new DataSource({
database: isSqlite ? dbConnectionString : undefined,
entities: [path.join(__dirname, "..", "entities", "*.js")],
synchronize: !!process.env.DB_SYNC,
- logging: false,
+ logging: !!process.env.DB_LOGGING,
bigNumberStrings: false,
supportBigNumbers: true,
name: "default",
@@ -129,7 +129,7 @@ export async function initDatabase(): Promise<DataSource> {
return dbConnection;
}
-export { dbConnection, DataSourceOptions, DatabaseType };
+export { DataSourceOptions, DatabaseType, dbConnection };
export async function closeDatabase() {
await dbConnection?.destroy();
diff --git a/src/util/util/Sentry.ts b/src/util/util/Sentry.ts
index e302da0c..74a23a1e 100644
--- a/src/util/util/Sentry.ts
+++ b/src/util/util/Sentry.ts
@@ -16,13 +16,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Config } from "./Config";
import { yellow } from "picocolors";
+import { Config } from "./Config";
-import express from "express";
-import * as SentryNode from "@sentry/node";
-import * as Tracing from "@sentry/tracing";
import * as Integrations from "@sentry/integrations";
+import * as SentryNode from "@sentry/node";
+import express from "express";
// Work around for when bundle calls api/etc
let errorHandlersUsed = false;
@@ -46,16 +45,28 @@ export const Sentry = {
);
}
+ const integrations = [
+ new SentryNode.Integrations.Http({ tracing: true }),
+ new Integrations.RewriteFrames({
+ root: __dirname,
+ }),
+ new SentryNode.Integrations.Http({
+ tracing: true,
+ breadcrumbs: true,
+ }),
+ ...SentryNode.autoDiscoverNodePerformanceMonitoringIntegrations(),
+ ];
+
+ if (app)
+ integrations.push(
+ new SentryNode.Integrations.Express({
+ app,
+ }),
+ );
+
SentryNode.init({
dsn: endpoint,
- integrations: [
- new SentryNode.Integrations.Http({ tracing: true }),
- new Tracing.Integrations.Express({ app }),
- new Tracing.Integrations.Mysql(),
- new Integrations.RewriteFrames({
- root: __dirname,
- }),
- ],
+ integrations,
tracesSampleRate: traceSampleRate, // naming?
environment,
});
|