refactor split board update and notifications to their modules
This commit is contained in:
parent
20160611fd
commit
ba1479acbe
5 changed files with 141 additions and 123 deletions
126
src/bot.ts
126
src/bot.ts
|
@ -1,8 +1,5 @@
|
||||||
#!/bin/env -S deno run -A --unstable-kv
|
#!/bin/env -S deno run -A --unstable-kv
|
||||||
|
|
||||||
import { assertExists } from "https://deno.land/std@0.223.0/assert/assert_exists.ts";
|
|
||||||
import { assert } from "https://deno.land/std@0.223.0/assert/assert.ts";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AutocompleteInteraction,
|
AutocompleteInteraction,
|
||||||
ChatInputCommandInteraction,
|
ChatInputCommandInteraction,
|
||||||
|
@ -12,9 +9,10 @@ import {
|
||||||
TextChannel,
|
TextChannel,
|
||||||
} from "npm:discord.js";
|
} from "npm:discord.js";
|
||||||
|
|
||||||
import { Channel, channel, collect, days_to_ms, log_from, wait } from "./lib/utils.ts";
|
|
||||||
import { Storage } from "./lib/storage.ts";
|
import { Storage } from "./lib/storage.ts";
|
||||||
import { Devoir } from "./lib/storage.ts";
|
import { _1d, _1min, Channel, channel, collect, days_to_ms, log_from, trimmed } from "./lib/utils.ts";
|
||||||
|
import { update_loop } from "./lib/board.ts";
|
||||||
|
import { notification_loop } from "./lib/notification.ts";
|
||||||
|
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
|
@ -64,13 +62,6 @@ async function main() {
|
||||||
log("Oki.");
|
log("Oki.");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetch_feed_channel(bot: Client<boolean>, feed: { channel_id: string }) {
|
|
||||||
const feed_channel = await bot.channels.fetch(feed.channel_id);
|
|
||||||
assertExists(feed_channel);
|
|
||||||
assert(feed_channel instanceof TextChannel);
|
|
||||||
return feed_channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
function build_api_commands(subjects: Set<string>) {
|
function build_api_commands(subjects: Set<string>) {
|
||||||
const subjects_as_choices = [...subjects.values()].map((name) => ({ name, value: name }));
|
const subjects_as_choices = [...subjects.values()].map((name) => ({ name, value: name }));
|
||||||
const devoir_command = new SlashCommandBuilder()
|
const devoir_command = new SlashCommandBuilder()
|
||||||
|
@ -244,115 +235,4 @@ async function handle_autocomplete(interaction: AutocompleteInteraction, storage
|
||||||
log("Unknown command", interaction.commandName);
|
log("Unknown command", interaction.commandName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update_loop(bot: Client, storage: Storage, update_display: Channel) {
|
|
||||||
log("Waiting for updates.");
|
|
||||||
while (true) {
|
|
||||||
const _trigger = await update_display.receive();
|
|
||||||
log("Updating board.");
|
|
||||||
for await (const [_, feed] of storage.feeds.list()) {
|
|
||||||
const feed_channel = await fetch_feed_channel(bot, feed);
|
|
||||||
const embed = new EmbedBuilder().setTitle("`📚` Devoirs")
|
|
||||||
.setFooter({ text: "Mise à jour " + Date.now() });
|
|
||||||
const sorted_devoirs = (await (collect(storage.devoirs.list())))
|
|
||||||
.map(([_, d]) => d).toSorted((a, b) => a.date > b.date ? 1 : -1);
|
|
||||||
for (const devoir of sorted_devoirs) {
|
|
||||||
embed.addFields({ name: format_devoir_title(devoir), value: devoir.description });
|
|
||||||
}
|
|
||||||
const board_message = await feed_channel.messages.fetch(feed.board_message_id);
|
|
||||||
board_message.edit({ embeds: [embed], content: "" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _1min = 60 * 1000;
|
|
||||||
const _24h = 24 * 60 * _1min;
|
|
||||||
const _7d = 7 * _24h;
|
|
||||||
|
|
||||||
function format_devoir_title(devoir: Devoir) {
|
|
||||||
const date = new Date(devoir.date);
|
|
||||||
return `${date.getDate()}/${date.getMonth() + 1} - ${devoir.subject}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notification_loop(bot: Client, storage: Storage) {
|
|
||||||
while (true) {
|
|
||||||
// get all devoirs to notify
|
|
||||||
const devoirs_to_notify = new Set<string>();
|
|
||||||
const notification_threshold = _7d;
|
|
||||||
for await (const [devoir_id, devoir] of storage.devoirs.list()) {
|
|
||||||
const time_left = devoir.date - Date.now();
|
|
||||||
if (time_left < 0) continue; // TODO : delete devoir.
|
|
||||||
if (time_left > notification_threshold) continue;
|
|
||||||
devoirs_to_notify.add(devoir_id.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete all obsolete notifications
|
|
||||||
for await (const [notification_id, notification] of storage.notifications.list()) {
|
|
||||||
if (devoirs_to_notify.has(notification.devoir_id)) continue;
|
|
||||||
const feed = await storage.feeds.get({ id: notification.feed_id });
|
|
||||||
if (feed === null) continue;
|
|
||||||
const feed_channel = await fetch_feed_channel(bot, feed);
|
|
||||||
try {
|
|
||||||
const message = await feed_channel.messages.fetch(notification.message_id);
|
|
||||||
await message.delete();
|
|
||||||
} catch (_) { /* . */ }
|
|
||||||
await storage.notifications.delete(notification_id);
|
|
||||||
await storage.feeds.update({ id: notification.feed_id }, (f) => f.notification_ids.delete(notification_id.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// create missing messages.
|
|
||||||
for await (const [feed_id, feed] of storage.feeds.list()) {
|
|
||||||
const feed_channel = await fetch_feed_channel(bot, feed);
|
|
||||||
// find devoirs needing to create a notification for
|
|
||||||
const devoirs_to_notify_in_feed = new Set(devoirs_to_notify.values());
|
|
||||||
for (const existing_notification_id of feed.notification_ids.values()) {
|
|
||||||
const notification = await storage.notifications.get({ id: existing_notification_id });
|
|
||||||
assertExists(notification);
|
|
||||||
devoirs_to_notify_in_feed.delete(notification.devoir_id);
|
|
||||||
}
|
|
||||||
// create notifications
|
|
||||||
for (const devoir_id of devoirs_to_notify_in_feed.values()) {
|
|
||||||
const devoir = await storage.devoirs.get({ id: devoir_id });
|
|
||||||
if (devoir === null) continue;
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle(format_devoir_title(devoir))
|
|
||||||
.setDescription(devoir.description);
|
|
||||||
const message = await feed_channel.send({ embeds: [embed] });
|
|
||||||
const notification_id = await storage.notifications.add({
|
|
||||||
devoir_id: devoir_id,
|
|
||||||
message_id: message.id,
|
|
||||||
feed_id: feed_id.id,
|
|
||||||
});
|
|
||||||
storage.feeds.update(feed_id, (f) => f.notification_ids.add(notification_id.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each notification, update.
|
|
||||||
for await (const [_, notification] of storage.notifications.list()) {
|
|
||||||
const devoir = await storage.devoirs.get({ id: notification.devoir_id });
|
|
||||||
if (devoir === null) continue;
|
|
||||||
const feed = await storage.feeds.get({ id: notification.feed_id });
|
|
||||||
if (feed === null) continue;
|
|
||||||
const feed_channel = await fetch_feed_channel(bot, feed);
|
|
||||||
const message = await feed_channel.messages.fetch(notification.message_id);
|
|
||||||
const ms_left = devoir.date - Date.now();
|
|
||||||
const days_left = Math.floor(ms_left / _24h);
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle(`${format_devoir_title(devoir)} (${days_left} jours)`)
|
|
||||||
.setDescription(devoir.description)
|
|
||||||
.setColor(0x8888ff);
|
|
||||||
if (days_left <= 5) embed.setColor(0xffd726);
|
|
||||||
if (days_left <= 3) embed.setColor(0xff8b26);
|
|
||||||
if (days_left <= 1) embed.setColor(0xff2626);
|
|
||||||
await message.edit({ embeds: [embed], content: "" });
|
|
||||||
}
|
|
||||||
|
|
||||||
await wait(15 * _1min);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function trimmed(text: string, width: number) {
|
|
||||||
if (text.length < width) return text;
|
|
||||||
else return text.slice(0, width - 4) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.main) await main();
|
if (import.meta.main) await main();
|
||||||
|
|
27
src/lib/board.ts
Normal file
27
src/lib/board.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { Client, EmbedBuilder } from "npm:discord.js";
|
||||||
|
|
||||||
|
import { fetch_feed_channel, format_devoir_title } from "./lib.ts";
|
||||||
|
import { Storage } from "./storage.ts";
|
||||||
|
import { Channel, collect, log_from } from "./utils.ts";
|
||||||
|
|
||||||
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
|
export async function update_loop(bot: Client, storage: Storage, update_display: Channel) {
|
||||||
|
log("Waiting for updates.");
|
||||||
|
while (true) {
|
||||||
|
const _trigger = await update_display.receive();
|
||||||
|
log("Updating board.");
|
||||||
|
for await (const [_, feed] of storage.feeds.list()) {
|
||||||
|
const feed_channel = await fetch_feed_channel(bot, feed);
|
||||||
|
const embed = new EmbedBuilder().setTitle("`📚` Devoirs")
|
||||||
|
.setFooter({ text: "Mise à jour " + Date.now() });
|
||||||
|
const sorted_devoirs = (await (collect(storage.devoirs.list())))
|
||||||
|
.map(([_, d]) => d).toSorted((a, b) => a.date > b.date ? 1 : -1);
|
||||||
|
for (const devoir of sorted_devoirs) {
|
||||||
|
embed.addFields({ name: format_devoir_title(devoir), value: devoir.description });
|
||||||
|
}
|
||||||
|
const board_message = await feed_channel.messages.fetch(feed.board_message_id);
|
||||||
|
board_message.edit({ embeds: [embed], content: "" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/lib/lib.ts
Normal file
18
src/lib/lib.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { assert } from "https://deno.land/std@0.223.0/assert/assert.ts";
|
||||||
|
import { assertExists } from "https://deno.land/std@0.223.0/assert/assert_exists.ts";
|
||||||
|
|
||||||
|
import { Client, TextChannel } from "npm:discord.js";
|
||||||
|
|
||||||
|
import { Devoir } from "./storage.ts";
|
||||||
|
|
||||||
|
export async function fetch_feed_channel(bot: Client, feed: { channel_id: string }) {
|
||||||
|
const feed_channel = await bot.channels.fetch(feed.channel_id);
|
||||||
|
assertExists(feed_channel);
|
||||||
|
assert(feed_channel instanceof TextChannel);
|
||||||
|
return feed_channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function format_devoir_title(devoir: Devoir) {
|
||||||
|
const date = new Date(devoir.date);
|
||||||
|
return `${date.getDate()}/${date.getMonth() + 1} - ${devoir.subject}`;
|
||||||
|
}
|
84
src/lib/notification.ts
Normal file
84
src/lib/notification.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { assertExists } from "https://deno.land/std@0.223.0/assert/assert_exists.ts";
|
||||||
|
|
||||||
|
import { Client, EmbedBuilder } from "npm:discord.js";
|
||||||
|
|
||||||
|
import { _1d, _1min, wait } from "./utils.ts";
|
||||||
|
import { fetch_feed_channel, format_devoir_title } from "./lib.ts";
|
||||||
|
import { Storage } from "./storage.ts";
|
||||||
|
|
||||||
|
export async function notification_loop(bot: Client, storage: Storage) {
|
||||||
|
while (true) {
|
||||||
|
// get all devoirs to notify
|
||||||
|
const devoirs_to_notify = new Set<string>();
|
||||||
|
const notification_threshold = 7 * _1d;
|
||||||
|
for await (const [devoir_id, devoir] of storage.devoirs.list()) {
|
||||||
|
const time_left = devoir.date - Date.now();
|
||||||
|
if (time_left < 0) continue; // TODO : delete devoir.
|
||||||
|
if (time_left > notification_threshold) continue;
|
||||||
|
devoirs_to_notify.add(devoir_id.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete all obsolete notifications
|
||||||
|
for await (const [notification_id, notification] of storage.notifications.list()) {
|
||||||
|
if (devoirs_to_notify.has(notification.devoir_id)) continue;
|
||||||
|
const feed = await storage.feeds.get({ id: notification.feed_id });
|
||||||
|
if (feed === null) continue;
|
||||||
|
const feed_channel = await fetch_feed_channel(bot, feed);
|
||||||
|
try {
|
||||||
|
const message = await feed_channel.messages.fetch(notification.message_id);
|
||||||
|
await message.delete();
|
||||||
|
} catch (_) { /* . */ }
|
||||||
|
await storage.notifications.delete(notification_id);
|
||||||
|
await storage.feeds.update({ id: notification.feed_id }, (f) => f.notification_ids.delete(notification_id.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// create missing messages.
|
||||||
|
for await (const [feed_id, feed] of storage.feeds.list()) {
|
||||||
|
const feed_channel = await fetch_feed_channel(bot, feed);
|
||||||
|
// find devoirs needing to create a notification for
|
||||||
|
const devoirs_to_notify_in_feed = new Set(devoirs_to_notify.values());
|
||||||
|
for (const existing_notification_id of feed.notification_ids.values()) {
|
||||||
|
const notification = await storage.notifications.get({ id: existing_notification_id });
|
||||||
|
assertExists(notification);
|
||||||
|
devoirs_to_notify_in_feed.delete(notification.devoir_id);
|
||||||
|
}
|
||||||
|
// create notifications
|
||||||
|
for (const devoir_id of devoirs_to_notify_in_feed.values()) {
|
||||||
|
const devoir = await storage.devoirs.get({ id: devoir_id });
|
||||||
|
if (devoir === null) continue;
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(format_devoir_title(devoir))
|
||||||
|
.setDescription(devoir.description);
|
||||||
|
const message = await feed_channel.send({ embeds: [embed] });
|
||||||
|
const notification_id = await storage.notifications.add({
|
||||||
|
devoir_id: devoir_id,
|
||||||
|
message_id: message.id,
|
||||||
|
feed_id: feed_id.id,
|
||||||
|
});
|
||||||
|
storage.feeds.update(feed_id, (f) => f.notification_ids.add(notification_id.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each notification, update.
|
||||||
|
for await (const [_, notification] of storage.notifications.list()) {
|
||||||
|
const devoir = await storage.devoirs.get({ id: notification.devoir_id });
|
||||||
|
if (devoir === null) continue;
|
||||||
|
const feed = await storage.feeds.get({ id: notification.feed_id });
|
||||||
|
if (feed === null) continue;
|
||||||
|
const feed_channel = await fetch_feed_channel(bot, feed);
|
||||||
|
const message = await feed_channel.messages.fetch(notification.message_id);
|
||||||
|
const ms_left = devoir.date - Date.now();
|
||||||
|
const days_left = Math.floor(ms_left / _1d);
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(format_devoir_title(devoir) + ` (${days_left} jours)`)
|
||||||
|
.setDescription(devoir.description)
|
||||||
|
.setColor(0x8888ff);
|
||||||
|
if (days_left <= 5) embed.setColor(0xffd726);
|
||||||
|
if (days_left <= 3) embed.setColor(0xff8b26);
|
||||||
|
if (days_left <= 1) embed.setColor(0xff2626);
|
||||||
|
await message.edit({ embeds: [embed], content: "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await wait(15 * _1min);
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,3 +47,12 @@ export function channel<T = void>() {
|
||||||
export async function wait(ms: number) {
|
export async function wait(ms: number) {
|
||||||
await new Promise((resolver) => setTimeout(resolver, ms));
|
await new Promise((resolver) => setTimeout(resolver, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function trimmed(text: string, width: number) {
|
||||||
|
if (text.length < width) return text;
|
||||||
|
else return text.slice(0, width - 4) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const _1min = 60 * 1000;
|
||||||
|
export const _1h = 60 * _1min;
|
||||||
|
export const _1d = 24 * _1h;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue