/*
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 .
*/
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { route } from "@spacebar/api";
import { Channel, FieldErrors, Message, getPermission } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { FindManyOptions, In, Like } from "typeorm";
const router: Router = Router();
router.get(
"/",
route({
responses: {
200: {
body: "GuildMessagesSearchResponse",
},
403: {
body: "APIErrorResponse",
},
422: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const {
channel_id,
content,
// include_nsfw, // TODO
offset,
sort_order,
// sort_by, // TODO: Handle 'relevance'
limit,
author_id,
} = req.query;
const parsedLimit = Number(limit) || 50;
if (parsedLimit < 1 || parsedLimit > 100)
throw new HTTPError("limit must be between 1 and 100", 422);
if (sort_order) {
if (
typeof sort_order != "string" ||
["desc", "asc"].indexOf(sort_order) == -1
)
throw FieldErrors({
sort_order: {
message: "Value must be one of ('desc', 'asc').",
code: "BASE_TYPE_CHOICES",
},
}); // todo this is wrong
}
const permissions = await getPermission(
req.user_id,
req.params.guild_id,
channel_id as string | undefined,
);
permissions.hasThrow("VIEW_CHANNEL");
if (!permissions.has("READ_MESSAGE_HISTORY"))
return res.json({ messages: [], total_results: 0 });
const query: FindManyOptions = {
order: {
timestamp: sort_order
? (sort_order.toUpperCase() as "ASC" | "DESC")
: "DESC",
},
take: parsedLimit || 0,
where: {
guild: {
id: req.params.guild_id,
},
},
relations: [
"author",
"webhook",
"application",
"mentions",
"mention_roles",
"mention_channels",
"sticker_items",
"attachments",
],
skip: offset ? Number(offset) : 0,
};
//@ts-ignore
if (channel_id) query.where.channel = { id: channel_id };
else {
// get all channel IDs that this user can access
const channels = await Channel.find({
where: { guild_id: req.params.guild_id },
select: ["id"],
});
const ids = [];
for (const channel of channels) {
const perm = await getPermission(
req.user_id,
req.params.guild_id,
channel.id,
);
if (
!perm.has("VIEW_CHANNEL") ||
!perm.has("READ_MESSAGE_HISTORY")
)
continue;
ids.push(channel.id);
}
//@ts-ignore
query.where.channel = { id: In(ids) };
}
//@ts-ignore
if (author_id) query.where.author = { id: author_id };
//@ts-ignore
if (content) query.where.content = Like(`%${content}%`);
const messages: Message[] = await Message.find(query);
const messagesDto = messages.map((x) => [
{
id: x.id,
type: x.type,
content: x.content,
channel_id: x.channel_id,
author: {
id: x.author?.id,
username: x.author?.username,
avatar: x.author?.avatar,
avatar_decoration: null,
discriminator: x.author?.discriminator,
public_flags: x.author?.public_flags,
},
attachments: x.attachments,
embeds: x.embeds,
mentions: x.mentions,
mention_roles: x.mention_roles,
pinned: x.pinned,
mention_everyone: x.mention_everyone,
tts: x.tts,
timestamp: x.timestamp,
edited_timestamp: x.edited_timestamp,
flags: x.flags,
components: x.components,
hit: true,
},
]);
return res.json({
messages: messagesDto,
total_results: messages.length,
});
},
);
export default router;