add date() formatter, add PersistentMessage feature, refactor updates to use PersistentMessage
This commit is contained in:
parent
c26d2047a9
commit
63e3a63a7c
4 changed files with 149 additions and 16 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -20,7 +20,7 @@ data/leaves.json
|
||||||
data/sessionPreferences.json
|
data/sessionPreferences.json
|
||||||
data/tg-history/
|
data/tg-history/
|
||||||
data/updates/.message-ids.json
|
data/updates/.message-ids.json
|
||||||
|
data/.message-ids/
|
||||||
# Emoji data
|
# Emoji data
|
||||||
emoji-uploads/
|
emoji-uploads/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,18 @@ function wrankFull(entry: WRankEntry, options: WRankDisplayOptions): string {
|
||||||
return `${dash} (${dash}${zero})`; // "— ( — 0 )" when others have delta
|
return `${dash} (${dash}${zero})`; // "— ( — 0 )" when others have delta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Date formatters ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function date(date: Date | string, fmt: string = "dd/MM/YYYY"): string {
|
||||||
|
const d = typeof date === "string" ? new Date(date) : date;
|
||||||
|
return fmt
|
||||||
|
.replace("dd", String(d.getDate()).padStart(2, "0"))
|
||||||
|
.replace("MM", String(d.getMonth() + 1).padStart(2, "0"))
|
||||||
|
.replace("YYYY", String(d.getFullYear()))
|
||||||
|
.replace("HH", String(d.getHours()).padStart(2, "0"))
|
||||||
|
.replace("mm", String(d.getMinutes()).padStart(2, "0"));
|
||||||
|
},
|
||||||
|
|
||||||
// ─── Bringer formatters ────────────────────────────────────────────────────────
|
// ─── Bringer formatters ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function bringerDisplay(n: Nation): string {
|
function bringerDisplay(n: Nation): string {
|
||||||
|
|
@ -148,6 +160,7 @@ export const format = {
|
||||||
nation,
|
nation,
|
||||||
score,
|
score,
|
||||||
emoji,
|
emoji,
|
||||||
|
date,
|
||||||
wrank: {
|
wrank: {
|
||||||
rank: wrankRank,
|
rank: wrankRank,
|
||||||
delta: wrankDelta,
|
delta: wrankDelta,
|
||||||
|
|
|
||||||
131
src/systems/persistent-message.ts
Normal file
131
src/systems/persistent-message.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* PersistentMessage — manages Discord messages that need to be edited in place.
|
||||||
|
* Each store maps to a separate file in data/.message-ids/
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* import { PersistentMessage } from "@systems/persistent-message";
|
||||||
|
*
|
||||||
|
* await PersistentMessage.post({
|
||||||
|
* store: "leaderboard",
|
||||||
|
* key: "2026-W24",
|
||||||
|
* channelId: "123456",
|
||||||
|
* embeds,
|
||||||
|
* client,
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* PersistentMessage.get({ store: "leaderboard", key: "2026-W24" }) // messageId | null
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from "path";
|
||||||
|
import { Client, EmbedBuilder, TextChannel } from "discord.js";
|
||||||
|
import { Store } from "@systems/store";
|
||||||
|
import { Paths } from "@paths";
|
||||||
|
import { Logger } from "@systems/logger";
|
||||||
|
|
||||||
|
const log = Logger.for("persistent-message");
|
||||||
|
|
||||||
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export type MessageStore = "updates" | "leaderboard" | "results";
|
||||||
|
|
||||||
|
interface PostParams {
|
||||||
|
store: MessageStore;
|
||||||
|
key: string;
|
||||||
|
channelId: string;
|
||||||
|
embeds: EmbedBuilder[];
|
||||||
|
client: Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetParams {
|
||||||
|
store: MessageStore;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetParams {
|
||||||
|
store: MessageStore;
|
||||||
|
key: string;
|
||||||
|
messageId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteParams {
|
||||||
|
store: MessageStore;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function storePath(store: MessageStore): string {
|
||||||
|
return Paths.data(".message-ids", `${store}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readStore(store: MessageStore): Record<string, string> {
|
||||||
|
return Store.readOrDefault<Record<string, string>>(storePath(store), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeStore(store: MessageStore, data: Record<string, string>): void {
|
||||||
|
Store.write(storePath(store), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Namespace ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const PersistentMessage = {
|
||||||
|
/**
|
||||||
|
* Get the stored messageId for a key in a store.
|
||||||
|
*/
|
||||||
|
get({ store, key }: GetParams): string | null {
|
||||||
|
return readStore(store)[key] ?? null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a messageId for a key.
|
||||||
|
*/
|
||||||
|
set({ store, key, messageId }: SetParams): void {
|
||||||
|
const data = readStore(store);
|
||||||
|
data[key] = messageId;
|
||||||
|
writeStore(store, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a stored messageId.
|
||||||
|
*/
|
||||||
|
delete({ store, key }: DeleteParams): void {
|
||||||
|
const data = readStore(store);
|
||||||
|
delete data[key];
|
||||||
|
writeStore(store, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post or edit a persistent message.
|
||||||
|
* If a messageId exists for the key, edits the existing message.
|
||||||
|
* If not, posts a new message and stores the messageId.
|
||||||
|
*/
|
||||||
|
async post({ store, key, channelId, embeds, client }: PostParams): Promise<void> {
|
||||||
|
const channel = await client.channels.fetch(channelId) as TextChannel;
|
||||||
|
const messageId = PersistentMessage.get({ store, key });
|
||||||
|
|
||||||
|
log.debug(`post: store=${store} key=${key} messageId=${messageId}`);
|
||||||
|
|
||||||
|
if (messageId) {
|
||||||
|
try {
|
||||||
|
const msg = await channel.messages.fetch(messageId);
|
||||||
|
await msg.edit({ embeds });
|
||||||
|
log.info(`Edited ${store}/${key} (${messageId})`);
|
||||||
|
return;
|
||||||
|
} catch (err: any) {
|
||||||
|
log.warn(`Could not edit ${messageId}, posting new: ${err.message}`);
|
||||||
|
PersistentMessage.delete({ store, key });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = await channel.send({ embeds });
|
||||||
|
PersistentMessage.set({ store, key, messageId: msg.id });
|
||||||
|
log.info(`Posted ${store}/${key} (${msg.id})`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all keys in a store.
|
||||||
|
*/
|
||||||
|
list({ store }: { store: MessageStore }): string[] {
|
||||||
|
return Object.keys(readStore(store));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
import { Nation, VoteEntry, PollState } from "@types";
|
import { Nation, VoteEntry, PollState } from "@types";
|
||||||
import { WRank } from "@systems/wrank";
|
import { WRank } from "@systems/wrank";
|
||||||
import { Leaves } from "@systems/leaves";
|
import { Leaves } from "@systems/leaves";
|
||||||
|
import { PersistentMessage } from "@systems/persistent-message";
|
||||||
|
|
||||||
const log = Logger.for("updates");
|
const log = Logger.for("updates");
|
||||||
|
|
||||||
|
|
@ -96,19 +97,6 @@
|
||||||
return path.join(updatesDir(), ".message-ids.json");
|
return path.join(updatesDir(), ".message-ids.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Message ID helpers ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function getMessageId(version: string): string | null {
|
|
||||||
const ids = Store.readOrDefault<Record<string, string>>(messageIdsPath(), {});
|
|
||||||
return ids[version] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveMessageId(version: string, messageId: string): void {
|
|
||||||
const ids = Store.readOrDefault<Record<string, string>>(messageIdsPath(), {});
|
|
||||||
ids[version] = messageId;
|
|
||||||
Store.write(messageIdsPath(), ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Versions cache ───────────────────────────────────────────────────────────
|
// ─── Versions cache ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let _versionsCache: string[] | null = null;
|
let _versionsCache: string[] | null = null;
|
||||||
|
|
@ -257,7 +245,7 @@
|
||||||
|
|
||||||
const channel = await client.channels.fetch(channelId) as TextChannel;
|
const channel = await client.channels.fetch(channelId) as TextChannel;
|
||||||
const embeds = Updates.buildEmbeds(entry);
|
const embeds = Updates.buildEmbeds(entry);
|
||||||
const messageId = getMessageId(version);
|
const messageId = PersistentMessage.get({ store: "updates", key: version });
|
||||||
|
|
||||||
log.debug(`post: version=${version} messageId=${messageId} idsPath=${messageIdsPath()}`);
|
log.debug(`post: version=${version} messageId=${messageId} idsPath=${messageIdsPath()}`);
|
||||||
|
|
||||||
|
|
@ -269,11 +257,12 @@
|
||||||
return;
|
return;
|
||||||
} catch {
|
} catch {
|
||||||
log.warn(`Could not edit ${messageId}, posting new`);
|
log.warn(`Could not edit ${messageId}, posting new`);
|
||||||
|
PersistentMessage.delete({ store: "updates", key: version });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = await channel.send({ embeds });
|
const msg = await channel.send({ embeds });
|
||||||
saveMessageId(version, msg.id);
|
PersistentMessage.set({ store: "updates", key: version, messageId: msg.id });
|
||||||
log.info(`Posted ${version} (${msg.id})`);
|
log.info(`Posted ${version} (${msg.id})`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue