diff --git a/package-lock.json b/package-lock.json
index 4c7bbb7d..ade53630 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"atomically": "^1.7.0",
"bcrypt": "^5.0.1",
"body-parser": "^1.19.0",
+ "canvas": "^2.8.0",
"cheerio": "^1.0.0-rc.9",
"dot-prop": "^6.0.1",
"dotenv": "^8.2.0",
@@ -28,6 +29,7 @@
"i18next": "^19.8.5",
"i18next-http-middleware": "^3.1.3",
"i18next-node-fs-backend": "^2.1.3",
+ "image-size": "^1.0.0",
"jsonwebtoken": "^8.5.1",
"lambert-server": "^1.2.5",
"missing-native-js-functions": "^1.2.6",
@@ -2301,6 +2303,20 @@
"url": "https://opencollective.com/browserslist"
}
},
+ "node_modules/canvas": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.8.0.tgz",
+ "integrity": "sha512-gLTi17X8WY9Cf5GZ2Yns8T5lfBOcGgFehDFb+JQwDqdOoBOcECS9ZWMEAqMSVcMYwXD659J8NyzjRY/2aE+C2Q==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@mapbox/node-pre-gyp": "^1.0.0",
+ "nan": "^2.14.0",
+ "simple-get": "^3.0.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/capture-exit": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
@@ -3155,6 +3171,17 @@
"node": ">=0.10"
}
},
+ "node_modules/decompress-response": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
+ "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
+ "dependencies": {
+ "mimic-response": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -4733,6 +4760,20 @@
}
]
},
+ "node_modules/image-size": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz",
+ "integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==",
+ "dependencies": {
+ "queue": "6.0.2"
+ },
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/import-local": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz",
@@ -6490,6 +6531,17 @@
"node": ">=6"
}
},
+ "node_modules/mimic-response": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
+ "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -6807,6 +6859,11 @@
"integrity": "sha512-nU7mOEuaXiQIB/EgTIjYZJ7g8KqMm2D8l4qp+DqA4jxWOb/tnb1KEoqp+tlbdQIDIAiC1i7j7X/3yHDFXLxr9g==",
"dev": true
},
+ "node_modules/nan": {
+ "version": "2.14.2",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
+ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
+ },
"node_modules/nanoassert": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz",
@@ -7905,6 +7962,14 @@
"node": ">=0.4.x"
}
},
+ "node_modules/queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "dependencies": {
+ "inherits": "~2.0.3"
+ }
+ },
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -8757,7 +8822,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -8773,6 +8837,16 @@
}
]
},
+ "node_modules/simple-get": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
+ "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
+ "dependencies": {
+ "decompress-response": "^4.2.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
"node_modules/single-line-log": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz",
@@ -12461,6 +12535,16 @@
"integrity": "sha512-e4Gyp7P8vqC2qV2iHA+cJNf/yqUKOShXQOJHQt81OHxlIZl/j/j3soEA0adAQi8CPUQgvOdDENyQ5kd6a6mNSg==",
"dev": true
},
+ "canvas": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.8.0.tgz",
+ "integrity": "sha512-gLTi17X8WY9Cf5GZ2Yns8T5lfBOcGgFehDFb+JQwDqdOoBOcECS9ZWMEAqMSVcMYwXD659J8NyzjRY/2aE+C2Q==",
+ "requires": {
+ "@mapbox/node-pre-gyp": "^1.0.0",
+ "nan": "^2.14.0",
+ "simple-get": "^3.0.3"
+ }
+ },
"capture-exit": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
@@ -13195,6 +13279,14 @@
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
},
+ "decompress-response": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
+ "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
+ "requires": {
+ "mimic-response": "^2.0.0"
+ }
+ },
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -14459,6 +14551,14 @@
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true
},
+ "image-size": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz",
+ "integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==",
+ "requires": {
+ "queue": "6.0.2"
+ }
+ },
"import-local": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz",
@@ -15859,6 +15959,11 @@
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
+ "mimic-response": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
+ "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
+ },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -16087,6 +16192,11 @@
"integrity": "sha512-nU7mOEuaXiQIB/EgTIjYZJ7g8KqMm2D8l4qp+DqA4jxWOb/tnb1KEoqp+tlbdQIDIAiC1i7j7X/3yHDFXLxr9g==",
"dev": true
},
+ "nan": {
+ "version": "2.14.2",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
+ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
+ },
"nanoassert": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz",
@@ -16977,6 +17087,14 @@
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
"dev": true
},
+ "queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "requires": {
+ "inherits": "~2.0.3"
+ }
+ },
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -17671,8 +17789,17 @@
"simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
- "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
- "dev": true
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
+ },
+ "simple-get": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
+ "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
+ "requires": {
+ "decompress-response": "^4.2.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
},
"single-line-log": {
"version": "1.1.2",
diff --git a/src/routes/guilds/#guild_id/widget.json.ts b/src/routes/guilds/#guild_id/widget.json.ts
index c1565e6d..256a633d 100644
--- a/src/routes/guilds/#guild_id/widget.json.ts
+++ b/src/routes/guilds/#guild_id/widget.json.ts
@@ -75,48 +75,48 @@ router.get("/", async (req: Request, res: Response) => {
.select("id user nick deaf mute")
.cursor()
.eachAsync(doc => {
- const status = doc.user?.presence.status || "offline";
- if (status != "offline") {
- let item = {}
+ const status = doc.user?.presence?.status || "offline";
+ if (status == "offline")return
+
+ let item = {}
+
+ item = {
+ ...item,
+ id: null, // this is updated during the sort outside of the query
+ username: doc.nick || doc.user?.username,
+ discriminator: "0000", // intended (https://github.com/discord/discord-api-docs/issues/1287)
+ avatar: null, // intended, avatar_url below will return a unique guild + user url to the avatar
+ status: status
+ }
+ const activity = doc.user?.presence?.activities?.[0];
+ if (activity) {
item = {
...item,
- id: null, // this is updated during the sort outside of the query
- username: doc.nick || doc.user?.username,
- discriminator: "0000", // intended (https://github.com/discord/discord-api-docs/issues/1287)
- avatar: null, // intended, avatar_url below will return a unique guild + user url to the avatar
- status: status
+ game: { name: activity.name }
}
+ }
- const activity = doc.user?.presence.activities[0];
- if (activity) {
- item = {
- ...item,
- game: { name: activity.name }
- }
- }
+ // TODO: If the member is in a voice channel, return extra widget details
+ // Extra fields returned include deaf, mute, self_deaf, self_mute, supress, and channel_id (voice channel connected to)
+ // Get this from VoiceState
- // TODO: If the member is in a voice channel, return extra widget details
- // Extra fields returned include deaf, mute, self_deaf, self_mute, supress, and channel_id (voice channel connected to)
- // Get this from VoiceState
-
-
- // TODO: Implement a widget-avatar endpoint on the CDN, and implement logic here to request it
- // Get unique avatar url for guild user, cdn to serve the actual avatar image on this url
- /*
- const avatar = doc.user?.avatar;
- if (avatar) {
- const CDN_HOST = Config.get().cdn.endpoint || "http://localhost:3003";
- const avatar_url = "/widget-avatars/" + ;
- item = {
- ...item,
- avatar_url: avatar_url
- }
- }
- */
- members.push(item);
+ // TODO: Implement a widget-avatar endpoint on the CDN, and implement logic here to request it
+ // Get unique avatar url for guild user, cdn to serve the actual avatar image on this url
+ /*
+ const avatar = doc.user?.avatar;
+ if (avatar) {
+ const CDN_HOST = Config.get().cdn.endpoint || "http://localhost:3003";
+ const avatar_url = "/widget-avatars/" + ;
+ item = {
+ ...item,
+ avatar_url: avatar_url
+ }
}
+ */
+
+ members.push(item);
});
// Sort members, and update ids (Unable to do under the mongoose query due to https://mongoosejs.com/docs/faq.html#populate_sort_order)
diff --git a/src/routes/guilds/#guild_id/widget.png.ts b/src/routes/guilds/#guild_id/widget.png.ts
index 986cb05a..3ddccf20 100644
--- a/src/routes/guilds/#guild_id/widget.png.ts
+++ b/src/routes/guilds/#guild_id/widget.png.ts
@@ -3,9 +3,12 @@ import { GuildModel } from "@fosscord/server-util";
import { HTTPError } from "lambert-server";
import { Image } from "canvas";
import fs from "fs";
+import path from "path"
const router: Router = Router();
+// TODO: use svg templates instead of node-canvas for improved performance and to change it easily
+
// https://discord.com/developers/docs/resources/guild#get-guild-widget-image
// TODO: Cache the response
router.get("/", async (req: Request, res: Response) => {
@@ -32,7 +35,7 @@ router.get("/", async (req: Request, res: Response) => {
const sizeOf = require("image-size");
// TODO: Widget style templates need Fosscord branding
- const source = __dirname + "../../../../../cache/widget/" + style + ".png";
+ const source = path.join(__dirname, "..", "..", "..", "..", "cache","widget", `${style}.png`)
if (!fs.existsSync(source)) {
throw new HTTPError("Widget template does not exist.", 400);
}
diff --git a/src/routes/guilds/#guild_id/widget.ts b/src/routes/guilds/#guild_id/widget.ts
index d8468da2..7682bc87 100644
--- a/src/routes/guilds/#guild_id/widget.ts
+++ b/src/routes/guilds/#guild_id/widget.ts
@@ -27,10 +27,7 @@ router.patch("/", check(WidgetModifySchema), async (req: Request, res: Response)
const perms = await getPermission(req.user_id, guild_id);
perms.hasThrow("MANAGE_GUILD");
- const guild = await GuildModel.findOne({ id: guild_id }).exec();
- if (!guild) throw new HTTPError("Guild not found", 404);
-
- await GuildModel.updateOne({ id: guild_id }, { widget_enabled: body['enabled'], widget_channel_id: body['channel_id'] }).exec();
+ await GuildModel.updateOne({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id }).exec();
// Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
return res.json(body);
diff --git a/src/schema/Widget.ts b/src/schema/Widget.ts
index 6a15a139..d37a38de 100644
--- a/src/schema/Widget.ts
+++ b/src/schema/Widget.ts
@@ -1,7 +1,7 @@
// https://discord.com/developers/docs/resources/guild#guild-widget-object
export const WidgetModifySchema = {
- $enabled: Boolean, // whether the widget is enabled
- $channel_id: String // the widget channel id
+ enabled: Boolean, // whether the widget is enabled
+ channel_id: String // the widget channel id
};
export interface WidgetModifySchema {
|