#!/usr/bin/env -S deno run --allow-read --allow-write --allow-net --allow-env --unstable-kv import { log_from, read_conf, root_path, SimpleResult } from "./utils.ts"; import { State } from "./state.ts"; import { RuleSet, TargetRole } from "./rules.ts"; import { EpitlsBot } from "./bot.ts"; import { CriApi } from "./cri.ts"; import { Emailer } from "./email.ts"; import { WebVerifier } from "./verifier.ts"; const log = log_from(import.meta.url); async function main() { const conf = await read_conf(root_path() + "/conf.json"); const rules = await RuleSet.from_file(root_path() + "/rules.json"); log("Loaded rules for", rules.size(), "roles."); const state = await State.from_dir(root_path() + "/local"); log("Loaded state with", await state.users_count(), "users."); const bot = new EpitlsBot(conf.discord.bot_token, rules); await bot.start(); log(`Started bot '${bot.bot_name()}' .`); const verifier = new WebVerifier(conf.verif_http); await verifier.start(); log(`Started web verifier at '${verifier.url()}' .`); const cri_api = new CriApi(conf.cri.cri_token); const mailer = new Emailer(conf.email_smtp); const service = new Service(state, bot, cri_api, rules, mailer, verifier); await service.serve(); } /** * Context of the service. */ class Service { state; bot; cri_api; rules; emailer; verifier; constructor(state: State, bot: EpitlsBot, cri_api: CriApi, rules: RuleSet, emailer: Emailer, verifier: WebVerifier) { this.state = state; this.bot = bot; this.cri_api = cri_api; this.rules = rules; this.emailer = emailer; this.verifier = verifier; } /** * Launches main loops. */ async serve() { await this.update_all_users_roles(); // for all received associations, trigger the association procedure. (async () => { for await (const req of this.bot.receive_associations()) { if (req.kind === "associate") this.association_procedure(req.discord_user_id, req.cri_login).then(req.callback); if (req.kind === "dissociate") this.dissociation_procedure(req.discord_user_id).then(req.callback); } })(); // for all links that must be sent, trigger mail sending. (async () => { for await (const { cri_login, discord_id, link } of this.verifier.links_to_send()) { const username = await this.bot.get_username(discord_id); this.emailer.send_confirmation_mail(username ?? "", cri_login + "@epita.fr", link); } })(); // for all updates to rule set, reassign all roles. (async () => { for await (const _ of this.rules.receive_rule_update()) { await this.update_all_users_roles(); } })(); } async update_all_users_roles() { const promises = [] as Promise[]; for await (const { cri_login, discord_user_id } of this.state.users()) { promises.push(this.update_user_roles(cri_login, discord_user_id)); promises.push(this.update_user_name(cri_login, discord_user_id)); } await Promise.all(promises); } async update_user_roles(cri_login: string, discord_user_id: string) { const groups = await this.cri_api.groups_of(cri_login); // log("found groups", groups); const roles = [] as TargetRole[]; for (const group of groups.current) roles.push(...this.rules.roles_for_group(group, false)); for (const group of groups.history) roles.push(...this.rules.roles_for_group(group, true)); // log("found setting roles", roles); for (const { guild_id, role_id } of roles) await this.bot.assign_role(discord_user_id, guild_id, role_id); } async update_user_name(cri_login: string, discord_user_id: string) { const { first_name, last_name } = await this.cri_api.get_user_name(cri_login); const suffix = ` [${first_name} .${last_name[0] ?? ""}]`; await this.bot.update_user_name(discord_user_id, first_name, suffix); } async association_procedure(discord_user_id: string, cri_login: string): Promise { try { if (!await this.cri_api.user_exists(cri_login)) return "No such login."; const res = await this.verifier.verification(discord_user_id, cri_login); if (res !== true) return res; await this.state.set_user(discord_user_id, cri_login); await this.update_user_roles(cri_login, discord_user_id); } catch (error) { console.error(error); return `${error}`; } return true; } async dissociation_procedure(discord_user_id: string): Promise { try { log("dissociating user", discord_user_id); await this.state.remove_user(discord_user_id); } catch (error) { console.error(error); return `${error}`; } return true; } } // bot.assign_role_to_member("358338548174159873", "871777993922588712", "1202346358867238952"); main();