"use server"; import { writeFile } from "fs/promises"; import { PLATFORM_MOD_ID } from "./constants"; import mongo, { Account, createDM, fetchChannels, fetchMembershipsByUser, fetchMessages, fetchUserById, findDM, updateLastMessageId, } from "./db"; import { publishMessage, sendChatMessage } from "./redis"; import { ulid } from "ulid"; import { AccountInfo, Bot, File, Member, Message, Report, Server, SessionInfo, User, } from "revolt-api"; export async function sendAlert(userId: string, content: string) { const messageId = ulid(); let dm = await findDM(PLATFORM_MOD_ID, userId); if (!dm) dm = await createDM(PLATFORM_MOD_ID, userId, messageId); else await updateLastMessageId(dm._id, messageId); await sendChatMessage({ _id: messageId, author: PLATFORM_MOD_ID, channel: dm._id, content, }); } export async function updateReportNotes(reportId: string, notes: string) { return await mongo() .db("revolt") .collection("safety_reports") .updateOne( { _id: reportId }, { $set: { notes, }, } ); } export async function disableAccount(userId: string) { await mongo() .db("revolt") .collection("accounts") .updateOne({ _id: userId }, { $set: { disabled: true } }); await mongo().db("revolt").collection("sessions").deleteMany({ user_id: userId, }); } export async function suspendUser(userId: string) { await disableAccount(userId); await mongo() .db("revolt") .collection("users") .updateOne( { _id: userId, }, { $set: { flags: 1, }, } ); const memberships = await fetchMembershipsByUser(userId); for (const topic of memberships.map((x) => x._id.server)) { await publishMessage(topic, { type: "UserUpdate", id: userId, data: { flags: 1, }, clear: [], }); } } export async function updateUserBadges(userId: string, badges: number) { await mongo().db("revolt").collection("users").updateOne( { _id: userId, }, { $set: { badges, }, } ); const memberships = await fetchMembershipsByUser(userId); for (const topic of [userId, ...memberships.map((x) => x._id.server)]) { await publishMessage(topic, { type: "UserUpdate", id: userId, data: { badges, }, clear: [], }); } } export async function wipeUser(userId: string, flags = 4) { // retrieve messages, dm channels, relationships, server memberships const backup = { _event: "wipe", user: await fetchUserById(userId), messages: await fetchMessages({ author: userId }, undefined), dms: await fetchChannels({ channel_type: "DirectMessage", recipients: userId, }), memberships: await fetchMembershipsByUser(userId), }; await writeFile( `./exports/${new Date().toISOString()} - ${userId}.json`, JSON.stringify(backup) ); // mark all attachments as deleted + reported const attachmentIds = backup.messages .filter((message) => message.attachments) .map((message) => message.attachments) .flat() .filter((attachment) => attachment) .map((attachment) => attachment!._id); if (backup.user?.avatar) { attachmentIds.push(backup.user.avatar._id); } if (backup.user?.profile?.background) { attachmentIds.push(backup.user.profile.background._id); } if (attachmentIds.length) { await mongo() .db("revolt") .collection("attachments") .updateMany( { _id: { $in: attachmentIds } }, { $set: { reported: true, deleted: true, }, } ); } // delete messages await mongo().db("revolt").collection("messages").deleteMany({ author: userId, }); // delete server memberships await mongo().db("revolt").collection("server_members").deleteMany({ "_id.user": userId, }); // disable account await disableAccount(userId); // clear user profile await mongo() .db("revolt") .collection("users") .updateOne( { _id: userId, }, { $set: { flags, }, $unset: { avatar: 1, profile: 1, status: 1, }, } ); // broadcast wipe event for (const topic of [ ...backup.dms.map((x) => x._id), ...backup.memberships.map((x) => x._id.server), ]) { await publishMessage(topic, { type: "UserPlatformWipe", user_id: userId, flags, }); } } export async function banUser(userId: string) { return await wipeUser(userId, 4); } export async function unsuspendUser(userId: string) { await restoreAccount(userId); await mongo() .db("revolt") .collection("users") .updateOne( { _id: userId, }, { $unset: { flags: 1, }, } ); } export async function updateServerFlags(serverId: string, flags: number) { await mongo().db("revolt").collection("servers").updateOne( { _id: serverId, }, { $set: { flags, }, } ); await publishMessage(serverId, { type: "ServerUpdate", id: serverId, data: { flags, }, clear: [], }); } export async function updateServerDiscoverability( serverId: string, state: boolean ) { await mongo() .db("revolt") .collection("servers") .updateOne( { _id: serverId, }, { $set: { analytics: state, discoverable: state, }, } ); } export async function updateBotDiscoverability(botId: string, state: boolean) { await mongo() .db("revolt") .collection("bots") .updateOne( { _id: botId, }, { $set: { analytics: state, discoverable: state, }, } ); } export async function restoreAccount(accountId: string) { await mongo() .db("revolt") .collection("accounts") .updateOne( { _id: accountId, }, { $unset: { disabled: 1, }, } ); } export async function queueAccountDeletion(accountId: string) { const twoWeeksFuture = new Date(); twoWeeksFuture.setDate(twoWeeksFuture.getDate() + 14); const $set: Partial = { disabled: true, deletion: { status: "Scheduled", after: twoWeeksFuture.toISOString(), }, }; await disableAccount(accountId); await mongo().db("revolt").collection("accounts").updateOne( { _id: accountId, }, { $set, } ); return $set; } export async function cancelAccountDeletion(accountId: string) { await mongo() .db("revolt") .collection("accounts") .updateOne( { _id: accountId, }, { $unset: { deletion: 1, }, } ); }