add API comments

This commit is contained in:
Matthieu Jolimaitre 2024-02-02 13:52:05 +01:00
parent fe34c8a91c
commit d62bf01b4a
7 changed files with 85 additions and 0 deletions

View file

@ -12,6 +12,9 @@ import {
import { channel, log_from, SimpleResult, split_promise } from "./utils.ts";
const log = (...args: unknown[]) => log_from(import.meta.url, ...args);
/**
* Wraps a discord bot and implements required actions.
*/
export class EpitlsBot {
private bot;
private token;
@ -29,6 +32,10 @@ export class EpitlsBot {
>();
}
/**
* Connects to discord API server and registers slash commands.\
* Needs to be run after construction as it is an asynchronous operation.
*/
public async start() {
const { promise, resolver } = split_promise<void>();
this.bot.on("ready", () => resolver());
@ -37,6 +44,9 @@ export class EpitlsBot {
await this.register_commands();
}
/**
* Assigns a discord role to a discord user.
*/
public async assign_role(user_id: string, guild_id: string, role_id: string) {
const guild = await this.bot.guilds.fetch(guild_id);
const member = await guild.members.fetch({ user: user_id });
@ -46,10 +56,16 @@ export class EpitlsBot {
log(`Assigned role '${role.name}' to user '${member.displayName}'.`);
}
/**
* Pull-style API to wait for and receive account association requests.
*/
public async *receive_associations() {
while (true) yield await this.assoc_channel.receive();
}
/**
* Connected bot name accessor.
*/
public bot_name() {
return this.bot.user?.displayName;
}

View file

@ -1,5 +1,8 @@
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
/**
* Wraps the CRI API.
*/
export class CriApi {
private token;
@ -7,6 +10,9 @@ export class CriApi {
this.token = token;
}
/**
* Fetches an array of the groups an user has been part of.
*/
public async groups_of(login: string) {
const response = await fetch(`https://cri.epita.fr/api/v2/users/${login}/`, {
headers: {
@ -32,6 +38,9 @@ export class CriApi {
return Array.from(result.values());
}
/**
* Tests wether a given login exists within the CRI registry.
*/
public async user_exists(login: string) {
const response = await fetch(`https://cri.epita.fr/api/v2/users/${login}/`, {
headers: {

View file

@ -7,6 +7,9 @@ export type EmailerConfig = {
password: string;
};
/**
* Wraps emailing process.
*/
export class Emailer {
private client;
private config;

View file

@ -22,6 +22,9 @@ async function main() {
await service.serve();
}
/**
* Context of the service.
*/
class Service {
state;
bot;
@ -35,6 +38,9 @@ class Service {
this.rules = rules;
}
/**
* Main loops.
*/
async serve() {
await this.update_all_users_roles();

View file

@ -1,6 +1,9 @@
export type TargetRole = { guild_id: string; role_id: string };
type SerializedRule = { group_id: string; target_role: TargetRole };
/**
* A set of rules for associating CRI groups with Discord roles.
*/
export class RuleSet {
private rules;
@ -8,6 +11,9 @@ export class RuleSet {
this.rules = new Map<string, TargetRole[]>();
}
/**
* Reads a RuleSet from a JSON serialized file.
*/
public static async from_file(path: string) {
const result = new RuleSet();
const file_content = await Deno.readTextFile(path);
@ -16,10 +22,16 @@ export class RuleSet {
return result;
}
/**
* Gets which Discord roles must be assigned to a user which is part of a given CRI group.
*/
public roles_for_group(group_id: string) {
return this.rules.get(group_id) ?? [];
}
/**
* Number of managed Discord roles.
*/
public size() {
let result = 0;
for (const roles of this.rules.values()) result += roles.length;

View file

@ -1,29 +1,49 @@
import { v1 as uuid } from "https://deno.land/std@0.213.0/uuid/mod.ts";
export type StoredUser = { discord_user_id: string; cri_login: string };
/**
* Wraps the persistent state, containing user associaitons.
*/
export class State {
/**
* note : We are using a Deno.Kv as storage.\
* Its API is comparable to a Key-Value database.
*/
kv;
constructor(kv: Deno.Kv) {
this.kv = kv;
}
/**
* Creates a persistent State that is stored in the specified directory.
*/
public static async from_dir(local_path: string) {
await Deno.mkdir(local_path, { recursive: true });
const kv = await Deno.openKv(local_path + "/kv");
return new State(kv);
}
/**
* Generates all stored users associations.
*/
public async *users() {
const query = this.kv.list({ prefix: ["users/"] });
for await (const { value } of query) yield value as StoredUser;
}
/**
* Count stored users associations.
*/
public async users_count() {
let result = 0;
for await (const _ of this.users()) result += 1;
return result;
}
/**
* Get the association of a user given its discord user id.
*/
public async get_user(discord_user_id: string) {
const { value: user_uuid } = await this.kv.get(["users_by_discord_id/", discord_user_id]);
if (user_uuid === null) return undefined;
@ -31,6 +51,9 @@ export class State {
return stored as StoredUser;
}
/**
* Appends a new user association.
*/
public async set_user(discord_user_id: string, cri_login: string) {
await this.remove_user(discord_user_id);
await this.add_user(discord_user_id, cri_login);

View file

@ -1,11 +1,18 @@
import * as path from "https://deno.land/std@0.213.0/path/mod.ts";
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
/**
* Gets the root path of the clonned project.
*/
export function root_path() {
const this_path = new URL(import.meta.url).pathname;
return path.resolve(this_path, "..", "..");
}
/**
* Creates handles to a channel.\
* c.f; https://en.wikipedia.org/wiki/Channel_(programming)
*/
export function channel<T>() {
const inner = {
items: [] as T[],
@ -29,6 +36,9 @@ export function channel<T>() {
const resolves_to = <T>(item: T) => new Promise<T>((r) => r(item));
/**
* Returns both a promise and its resolver.
*/
export function split_promise<T>() {
let resolver: null | ((item: T) => void) = null;
const promise = new Promise<T>((r) => resolver = r);
@ -36,12 +46,18 @@ export function split_promise<T>() {
return { promise, resolver };
}
/**
* Logging function factory.
*/
export function log_from(url: string, ...args: unknown[]) {
const date = new Date().toLocaleString("fr-FR");
const file = path.basename(new URL(url).pathname);
console.log(`[${date}][epitls][${file}]`, ...args);
}
/**
* Reads and parse a configuration file containing the current instance' secrets.
*/
export async function read_secrets(path: string) {
const content = await Deno.readTextFile(path);
return z.object({