✨ Reactions
This commit is contained in:
parent
f72156a40d
commit
159ff75944
184389
assets/passwords.txt
184389
assets/passwords.txt
File diff suppressed because it is too large
Load Diff
30
package-lock.json
generated
30
package-lock.json
generated
@ -10,7 +10,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fosscord/server-util": "^1.2.2",
|
"@fosscord/server-util": "^1.2.4",
|
||||||
"@types/jest": "^26.0.22",
|
"@types/jest": "^26.0.22",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
@ -18,12 +18,12 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-validator": "^6.9.2",
|
"express-validator": "^6.9.2",
|
||||||
"i18next": "^19.8.5",
|
"i18next": "^19.8.5",
|
||||||
"i18next-http-middleware": "^3.1.1",
|
"i18next-http-middleware": "^3.1.3",
|
||||||
"i18next-node-fs-backend": "^2.1.3",
|
"i18next-node-fs-backend": "^2.1.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"lambert-server": "^1.2.2",
|
"lambert-server": "^1.2.2",
|
||||||
"missing-native-js-functions": "^1.2.6",
|
"missing-native-js-functions": "^1.2.6",
|
||||||
"mongodb": "^3.6.4",
|
"mongodb": "^3.6.5",
|
||||||
"mongoose": "^5.12.3",
|
"mongoose": "^5.12.3",
|
||||||
"mongoose-autopopulate": "^0.12.3",
|
"mongoose-autopopulate": "^0.12.3",
|
||||||
"mongoose-long": "^0.3.2",
|
"mongoose-long": "^0.3.2",
|
||||||
@ -493,9 +493,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fosscord/server-util": {
|
"node_modules/@fosscord/server-util": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.2.4.tgz",
|
||||||
"integrity": "sha512-0lcOnN+I+6VXdY118lVsnj539cGv6XElChzzk9W/50sqBI3VZVq2w80GYJdcrT1POrpxJlEEJNxkNYRGGaYpeA==",
|
"integrity": "sha512-szj/JQBYtAjWbagv+T5YbvqzmtHl6C+WyE3us4fHtF7aIuFu0sV4HsPCHKxF9e64sUn/ayn/5nOzFJWKYjFEaA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jsonwebtoken": "^8.5.0",
|
"@types/jsonwebtoken": "^8.5.0",
|
||||||
"@types/mongoose-autopopulate": "^0.10.1",
|
"@types/mongoose-autopopulate": "^0.10.1",
|
||||||
@ -5100,9 +5100,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/i18next-http-middleware": {
|
"node_modules/i18next-http-middleware": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.1.3.tgz",
|
||||||
"integrity": "sha512-MKS2+iac5qO/95tvlpOGDtqWOqp4bOEBHLoqZNS6wQBO7fu/rd2G7IO3R+Vq0xahXkH/Jh/UoG+vHSko2VitYw=="
|
"integrity": "sha512-IVj5w2tJcnSZ3ZG8L/ylFdy/VD1tiyCG798Lw3mFI7hVxMOa0t1eTSzbk48ruS+EqDePQRc/18nzgg+Jbekdmw=="
|
||||||
},
|
},
|
||||||
"node_modules/i18next-node-fs-backend": {
|
"node_modules/i18next-node-fs-backend": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
@ -12549,9 +12549,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@fosscord/server-util": {
|
"@fosscord/server-util": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.2.4.tgz",
|
||||||
"integrity": "sha512-0lcOnN+I+6VXdY118lVsnj539cGv6XElChzzk9W/50sqBI3VZVq2w80GYJdcrT1POrpxJlEEJNxkNYRGGaYpeA==",
|
"integrity": "sha512-szj/JQBYtAjWbagv+T5YbvqzmtHl6C+WyE3us4fHtF7aIuFu0sV4HsPCHKxF9e64sUn/ayn/5nOzFJWKYjFEaA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/jsonwebtoken": "^8.5.0",
|
"@types/jsonwebtoken": "^8.5.0",
|
||||||
"@types/mongoose-autopopulate": "^0.10.1",
|
"@types/mongoose-autopopulate": "^0.10.1",
|
||||||
@ -16469,9 +16469,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"i18next-http-middleware": {
|
"i18next-http-middleware": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.1.3.tgz",
|
||||||
"integrity": "sha512-MKS2+iac5qO/95tvlpOGDtqWOqp4bOEBHLoqZNS6wQBO7fu/rd2G7IO3R+Vq0xahXkH/Jh/UoG+vHSko2VitYw=="
|
"integrity": "sha512-IVj5w2tJcnSZ3ZG8L/ylFdy/VD1tiyCG798Lw3mFI7hVxMOa0t1eTSzbk48ruS+EqDePQRc/18nzgg+Jbekdmw=="
|
||||||
},
|
},
|
||||||
"i18next-node-fs-backend": {
|
"i18next-node-fs-backend": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/fosscord/fosscord-api#readme",
|
"homepage": "https://github.com/fosscord/fosscord-api#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fosscord/server-util": "^1.2.2",
|
"@fosscord/server-util": "^1.2.4",
|
||||||
"@types/jest": "^26.0.22",
|
"@types/jest": "^26.0.22",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
@ -38,12 +38,12 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-validator": "^6.9.2",
|
"express-validator": "^6.9.2",
|
||||||
"i18next": "^19.8.5",
|
"i18next": "^19.8.5",
|
||||||
"i18next-http-middleware": "^3.1.1",
|
"i18next-http-middleware": "^3.1.3",
|
||||||
"i18next-node-fs-backend": "^2.1.3",
|
"i18next-node-fs-backend": "^2.1.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"lambert-server": "^1.2.2",
|
"lambert-server": "^1.2.2",
|
||||||
"missing-native-js-functions": "^1.2.6",
|
"missing-native-js-functions": "^1.2.6",
|
||||||
"mongodb": "^3.6.4",
|
"mongodb": "^3.6.5",
|
||||||
"mongoose": "^5.12.3",
|
"mongoose": "^5.12.3",
|
||||||
"mongoose-autopopulate": "^0.12.3",
|
"mongoose-autopopulate": "^0.12.3",
|
||||||
"mongoose-long": "^0.3.2",
|
"mongoose-long": "^0.3.2",
|
||||||
|
@ -37,13 +37,17 @@ export class FosscordServer extends Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setupSchema() {
|
async setupSchema() {
|
||||||
await db.collection("users").createIndex({ id: 1 }, { unique: true });
|
return Promise.all([
|
||||||
await db.collection("messages").createIndex({ id: 1 }, { unique: true });
|
db.collection("users").createIndex({ id: 1 }, { unique: true }),
|
||||||
await db.collection("channels").createIndex({ id: 1 }, { unique: true });
|
db.collection("messages").createIndex({ id: 1 }, { unique: true }),
|
||||||
await db.collection("guilds").createIndex({ id: 1 }, { unique: true });
|
db.collection("channels").createIndex({ id: 1 }, { unique: true }),
|
||||||
await db.collection("members").createIndex({ id: 1, guild_id: 1 }, { unique: true });
|
db.collection("guilds").createIndex({ id: 1 }, { unique: true }),
|
||||||
await db.collection("roles").createIndex({ id: 1 }, { unique: true });
|
db.collection("members").createIndex({ id: 1, guild_id: 1 }, { unique: true }),
|
||||||
await db.collection("emojis").createIndex({ id: 1 }, { unique: true });
|
db.collection("roles").createIndex({ id: 1 }, { unique: true }),
|
||||||
|
db.collection("emojis").createIndex({ id: 1 }, { unique: true }),
|
||||||
|
db.collection("invites").createIndex({ code: 1 }, { unique: true }),
|
||||||
|
db.collection("invites").createIndex({ expires_at: 1 }, { expireAfterSeconds: 0 }) // after 0 seconds of expires_at the invite will get delete
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
@ -70,9 +74,9 @@ export class FosscordServer extends Server {
|
|||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
ns,
|
ns,
|
||||||
backend: {
|
backend: {
|
||||||
loadPath: __dirname + "/../locales/{{lng}}/{{ns}}.json",
|
loadPath: __dirname + "/../locales/{{lng}}/{{ns}}.json"
|
||||||
},
|
},
|
||||||
load: "all",
|
load: "all"
|
||||||
});
|
});
|
||||||
this.app.use(i18nextMiddleware.handle(i18next, {}));
|
this.app.use(i18nextMiddleware.handle(i18next, {}));
|
||||||
|
|
||||||
@ -92,8 +96,8 @@ export class FosscordServer extends Server {
|
|||||||
const response = await fetch(`https://discord.com/assets/${req.params.file}`, {
|
const response = await fetch(`https://discord.com/assets/${req.params.file}`, {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
headers: {
|
headers: {
|
||||||
...req.headers,
|
...req.headers
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
const buffer = await response.buffer();
|
const buffer = await response.buffer();
|
||||||
|
|
||||||
@ -107,7 +111,7 @@ export class FosscordServer extends Server {
|
|||||||
"transfer-encoding",
|
"transfer-encoding",
|
||||||
"expect-ct",
|
"expect-ct",
|
||||||
"access-control-allow-origin",
|
"access-control-allow-origin",
|
||||||
"content-encoding",
|
"content-encoding"
|
||||||
].includes(name.toLowerCase())
|
].includes(name.toLowerCase())
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,6 +1,144 @@
|
|||||||
import { Router } from "express";
|
import {
|
||||||
|
ChannelModel,
|
||||||
|
EmojiModel,
|
||||||
|
getPermission,
|
||||||
|
MemberModel,
|
||||||
|
MessageModel,
|
||||||
|
MessageReactionAddEvent,
|
||||||
|
MessageReactionRemoveEvent,
|
||||||
|
PartialEmoji,
|
||||||
|
PublicUserProjection,
|
||||||
|
toObject,
|
||||||
|
UserModel
|
||||||
|
} from "@fosscord/server-util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
import { emitEvent } from "../../../../../util/Event";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
// TODO:
|
// TODO: check if emoji is really an unicode emoji or a prperly encoded external emoji
|
||||||
|
|
||||||
|
function getEmoji(emoji: string): PartialEmoji {
|
||||||
|
emoji = decodeURIComponent(emoji);
|
||||||
|
const parts = emoji.includes(":") && emoji.split(":");
|
||||||
|
if (parts)
|
||||||
|
return {
|
||||||
|
name: parts[0],
|
||||||
|
id: parts[1]
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: undefined,
|
||||||
|
name: emoji
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get("/:emoji", async (req, res) => {
|
||||||
|
const { message_id, channel_id } = req.params;
|
||||||
|
const emoji = getEmoji(req.params.emoji);
|
||||||
|
|
||||||
|
const message = await MessageModel.findOne({ id: message_id, channel_id }).exec();
|
||||||
|
if (!message) throw new HTTPError("Message not found", 404);
|
||||||
|
const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
|
||||||
|
if (!reaction) throw new HTTPError("Reaction not found", 404);
|
||||||
|
|
||||||
|
const permissions = await getPermission(req.user_id, undefined, channel_id);
|
||||||
|
permissions.hasThrow("VIEW_CHANNEL");
|
||||||
|
|
||||||
|
const users = await UserModel.find({ id: { $in: reaction.user_ids } }, PublicUserProjection).exec();
|
||||||
|
|
||||||
|
res.json(toObject(users));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put("/:emoji/:user_id", async (req, res) => {
|
||||||
|
const { message_id, channel_id, user_id } = req.params;
|
||||||
|
if (user_id !== "@me") throw new HTTPError("Invalid user");
|
||||||
|
const emoji = getEmoji(req.params.emoji);
|
||||||
|
|
||||||
|
const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true }).exec();
|
||||||
|
if (!channel) throw new HTTPError("Channel not found", 404);
|
||||||
|
|
||||||
|
const message = await MessageModel.findOne({ id: message_id, channel_id }).exec();
|
||||||
|
if (!message) throw new HTTPError("Message not found", 404);
|
||||||
|
const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
|
||||||
|
|
||||||
|
const permissions = await getPermission(req.user_id, undefined, channel_id);
|
||||||
|
permissions.hasThrow("READ_MESSAGE_HISTORY");
|
||||||
|
if (!already_added) permissions.hasThrow("ADD_REACTIONS");
|
||||||
|
|
||||||
|
if (emoji.id) {
|
||||||
|
const external_emoji = await EmojiModel.findOne({ id: emoji.id }).exec();
|
||||||
|
if (!external_emoji) throw new HTTPError("Emoji not found", 404);
|
||||||
|
if (!already_added) permissions.hasThrow("USE_EXTERNAL_EMOJIS");
|
||||||
|
emoji.animated = external_emoji.animated;
|
||||||
|
emoji.name = external_emoji.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (already_added) {
|
||||||
|
if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
|
||||||
|
already_added.count++;
|
||||||
|
} else message.reactions.push({ count: 1, emoji, user_ids: [req.user_id] });
|
||||||
|
|
||||||
|
await MessageModel.updateOne({ id: message_id, channel_id }, message).exec();
|
||||||
|
|
||||||
|
const member = channel.guild_id && (await MemberModel.findOne({ id: req.user_id }).exec());
|
||||||
|
|
||||||
|
await emitEvent({
|
||||||
|
event: "MESSAGE_REACTION_ADD",
|
||||||
|
channel_id,
|
||||||
|
guild_id: channel.guild_id,
|
||||||
|
data: {
|
||||||
|
user_id: req.user_id,
|
||||||
|
channel_id,
|
||||||
|
message_id,
|
||||||
|
guild_id: channel.guild_id,
|
||||||
|
emoji,
|
||||||
|
member
|
||||||
|
}
|
||||||
|
} as MessageReactionAddEvent);
|
||||||
|
|
||||||
|
res.sendStatus(204);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete("/:emoji/:user_id", async (req, res) => {
|
||||||
|
var { message_id, channel_id, user_id } = req.params;
|
||||||
|
|
||||||
|
const emoji = getEmoji(req.params.emoji);
|
||||||
|
|
||||||
|
const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true }).exec();
|
||||||
|
if (!channel) throw new HTTPError("Channel not found", 404);
|
||||||
|
|
||||||
|
const message = await MessageModel.findOne({ id: message_id, channel_id }).exec();
|
||||||
|
if (!message) throw new HTTPError("Message not found", 404);
|
||||||
|
|
||||||
|
const permissions = await getPermission(req.user_id, undefined, channel_id);
|
||||||
|
|
||||||
|
if (user_id === "@me") user_id = req.user_id;
|
||||||
|
else permissions.hasThrow("MANAGE_MESSAGES");
|
||||||
|
|
||||||
|
const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
|
||||||
|
if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
|
||||||
|
|
||||||
|
already_added.count--;
|
||||||
|
|
||||||
|
if (already_added.count <= 0) message.reactions.remove(already_added);
|
||||||
|
|
||||||
|
await MessageModel.updateOne({ id: message_id, channel_id }, message).exec();
|
||||||
|
|
||||||
|
await emitEvent({
|
||||||
|
event: "MESSAGE_REACTION_REMOVE",
|
||||||
|
channel_id,
|
||||||
|
guild_id: channel.guild_id,
|
||||||
|
data: {
|
||||||
|
user_id: req.user_id,
|
||||||
|
channel_id,
|
||||||
|
message_id,
|
||||||
|
guild_id: channel.guild_id,
|
||||||
|
emoji
|
||||||
|
}
|
||||||
|
} as MessageReactionRemoveEvent);
|
||||||
|
|
||||||
|
res.sendStatus(204);
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -76,7 +76,18 @@ router.get("/", async (req, res) => {
|
|||||||
|
|
||||||
const messages = await query.limit(limit).exec();
|
const messages = await query.limit(limit).exec();
|
||||||
|
|
||||||
return res.json(toObject(messages));
|
return res.json(
|
||||||
|
toObject(messages).map((x) => {
|
||||||
|
(x.reactions || []).forEach((x) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if ((x.user_ids || []).includes(req.user_id)) x.me = true;
|
||||||
|
// @ts-ignore
|
||||||
|
delete x.user_ids;
|
||||||
|
});
|
||||||
|
|
||||||
|
return x;
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: config max upload size
|
// TODO: config max upload size
|
||||||
|
Loading…
x
Reference in New Issue
Block a user