import express from "express"; import { createServer } from "http"; import { ModuleRpcServer } from "rpc_ts/lib/server"; import { ModuleRpcProtocolServer } from "rpc_ts/lib/protocol/server"; import { NoctBotService } from "@shared/rpc/backend"; import { eventLogger, logger } from "./logging"; import { client } from "./client"; import { tryDo } from "@shared/common/async_utils"; import { getRepository } from "typeorm"; import { GuildVerification } from "@shared/db/entity/GuildVerification"; import { isAuthorisedAsync } from "./util"; import { GuildMember } from "discord.js"; import { GuildViolationSettings } from "@shared/db/entity/GuildViolationSettings"; const PORT = +(process.env.RPC_PORT ?? "8181"); const app = express(); async function checkUser(action: string, userId: string, check: (user: GuildMember, verification: GuildVerification) => Promise): Promise { const verificationGuildRepo = getRepository(GuildVerification); const guilds = await verificationGuildRepo.find({ select: [ "guildId", "verifiedRoleId", ] }); for (const guild of guilds) { const guildInstance = await tryDo(client.bot.guilds.fetch(guild.guildId)); if (!guildInstance.ok) { logger.error("Failed to fetch guild instance for guild %s: %s", guild.guildId, guildInstance.error); continue; } const user = await tryDo(guildInstance.result.members.fetch(userId)); if (!user.ok) { const userInfo = await tryDo(client.bot.users.fetch(userId)); const userName = userInfo.ok ? `${userInfo.result.username}#${userInfo.result.discriminator}` : "???"; logger.warn("Couldn't %s for user %s (%s) on server %s because: %s", action, userId, userName, guild.guildId, user.error); } if (user.ok && await check(user.result, guild)) { return true; } } return false; } async function userMuted(user: GuildMember, guild: GuildVerification): Promise { const violationSettingsRepo = await getRepository(GuildViolationSettings); const settings = await violationSettingsRepo.findOne(guild.guildId); if (!settings?.muteRoleId) { return false; } return user.roles.cache.has(settings.muteRoleId); } const handler: ModuleRpcServer.ServiceHandlerFor = { async getPing({ ping }): Promise<{ text: string }> { return { text: `pong: ${ping}` }; }, async userInServer({ userId }): Promise<{ exists: boolean }> { return { exists: await checkUser("check access" ,userId, async () => true) }; }, async userAuthorised({ userId }): Promise<{ authorised: boolean }> { return { authorised: await checkUser("check auth", userId, (user) => isAuthorisedAsync(user)) }; }, async userVerified({ userId }): Promise<{ verified: boolean }> { // Prevent muted users from gaining verified role back return { verified: await checkUser("check verified", userId, async (user, guild) => user.roles.cache.has(guild.verifiedRoleId) || await userMuted(user, guild)) }; }, async verifyUser({ userId }): Promise<{ ok: boolean }> { return { ok: !(await checkUser("verify", userId, async (user, guild) => { if (await userMuted(user, guild)) { logger.info("Muted user %s#%s (%s) tried to re-verify in guild %s", user.user.username, user.user.discriminator, user.user.id, guild.guildId); return false; } const result = await tryDo(user.roles.add(guild.verifiedRoleId)); if (result.ok) { eventLogger.info("Verifying user %s#%s (%s)", user.user.username, user.user.discriminator, user.user.id); logger.info("Verifying user %s#%s (%s) on guild %s", user.user.username, user.user.discriminator, user.user.id, guild.guildId); } else { eventLogger.warn("Failed to verify user %s#%s (%s): %s", user.user.username, user.user.discriminator, user.user.id, result.error); logger.warn("Failed to verify user %s#%s (%s) on guild %s: %s", user.user.username, user.user.discriminator, user.user.id, guild.guildId, result.error); } return !result.ok; }))}; } }; app.use(ModuleRpcProtocolServer.registerRpcRoutes(NoctBotService, handler)); export function startRpcServer(): void { logger.info(`Starting RPC at *:${PORT}`); createServer(app).listen(PORT); }