From 8dc982e509e9a0a639c24ea594ed63fcc352be3e Mon Sep 17 00:00:00 2001 From: Purexo <5005154+Purexo@users.noreply.github.com> Date: Sat, 20 Dec 2025 14:42:44 +0100 Subject: [PATCH 1/2] chore: add `jdx/mise` support --- .node-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .node-version diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..6daa2a2 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v22.17.1 \ No newline at end of file From 6e1faf043fdc9e9d8129e1cb1f42e9ae1ce0511e Mon Sep 17 00:00:00 2001 From: Purexo <5005154+Purexo@users.noreply.github.com> Date: Sat, 20 Dec 2025 15:17:29 +0100 Subject: [PATCH 2/2] feat: add `DavidRevoy` rss feed --- src/crons/DavidRevoy.ts | 77 +++++++++++++++++++++++++++++++++++ tests/cron/DavidRevoy.spec.ts | 20 +++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/crons/DavidRevoy.ts create mode 100644 tests/cron/DavidRevoy.spec.ts diff --git a/src/crons/DavidRevoy.ts b/src/crons/DavidRevoy.ts new file mode 100644 index 0000000..aaa8fc9 --- /dev/null +++ b/src/crons/DavidRevoy.ts @@ -0,0 +1,77 @@ +import { Cron, findTextChannelByName } from '../framework/index.ts'; +import got from 'got'; +import { parse } from 'node-html-parser'; +import { decode } from 'html-entities'; +import { KeyValue } from '../database/index.ts'; +import { EmbedBuilder, SnowflakeUtil } from 'discord.js'; + +export default new Cron({ + enabled: true, + name: 'David Revoy', + description: + 'Vérifie toutes les 30 minutes si un nouveau strip de David Revoy est sorti', + schedule: '5,35 * * * *', + async handle(context) { + const strip = await getLastDavidRevoyStrip(); + + // vérifie le strip trouvé avec la dernière entrée + const lastStrip = await KeyValue.get('Last-Cron-DavidRevoy'); + const stripStoreIdentity = strip?.id ?? null; + if (lastStrip === stripStoreIdentity) return; // skip si identique + + await KeyValue.set('Last-Cron-DavidRevoy', stripStoreIdentity); // met à jour sinon + + if (!strip) return; // skip si pas de strip + + context.logger.info(`Found a new David Revoy strip`, strip); + + const channel = findTextChannelByName(context.client.channels, 'gif'); + + await channel.send({ + embeds: [ + new EmbedBuilder() + .setURL(strip.link) + .setTitle(strip.title) + .setImage(strip.imageUrl) + .setTimestamp(strip.date), + ], + enforceNonce: true, + nonce: SnowflakeUtil.generate().toString(), + }); + }, +}); + +interface IDavidRevoyStrip { + id: string; + link: string; + title: string; + date: Date; + imageUrl: string; +} + +export async function getLastDavidRevoyStrip(): Promise { + const { body } = await got( + 'https://www.davidrevoy.com/feed/rss/categorie2/webcomics/', + ); + const rss = parse(body, { + blockTextElements: { + // link tag in XML RSS is a block with text content + link: true, + }, + }); + + const item = rss.querySelector('item'); + if (!item) return null; + + const rawDescription = item.querySelector('description')?.textContent ?? ''; + const description = parse(decode(rawDescription)); + const img = description.querySelector('img'); + + return { + id: item.querySelector('guid')?.textContent?.trim() ?? '', + link: item.querySelector('link')?.textContent?.trim() ?? '', + title: item.querySelector('title')?.textContent?.trim() ?? '', + date: new Date(item.querySelector('pubDate')?.textContent ?? new Date()), + imageUrl: img?.parentNode?.getAttribute('href') ?? '', + }; +} diff --git a/tests/cron/DavidRevoy.spec.ts b/tests/cron/DavidRevoy.spec.ts new file mode 100644 index 0000000..bc1608d --- /dev/null +++ b/tests/cron/DavidRevoy.spec.ts @@ -0,0 +1,20 @@ +import { test, expect } from 'vitest'; + +import { getLastDavidRevoyStrip } from '../../src/crons/DavidRevoy.ts'; + +test('getLastDavidRevoyStrip', async () => { + const strip = await getLastDavidRevoyStrip(); + if (!strip) return; + + expect(strip.id).toBeTruthy(); + expect(typeof strip.id).toBe('string'); + + expect(strip.link).toBeTruthy(); + expect(typeof strip.link).toBe('string'); + + expect(strip.title).toBeTruthy(); + expect(typeof strip.title).toBe('string'); + + expect(strip.imageUrl).toBeTruthy(); + expect(typeof strip.imageUrl).toBe('string'); +});