From 9dfc790200eace9bf12c8bbde08f55dca6d4691b Mon Sep 17 00:00:00 2001 From: pfych Date: Sun, 20 Apr 2025 15:30:05 +1000 Subject: [PATCH] Allow hot-reload of config.json --- README.md | 3 +++ src/index.ts | 42 +++++++++++---------------------- src/mangadex/chapters.ts | 8 ++++--- src/mangadex/cover.ts | 8 ++++--- src/mangadex/manga.ts | 8 ++++--- src/{types.d.ts => types.ts} | 13 +++++----- src/utils/get-manga-title.ts | 17 +++++++++++++ src/utils/get-manga-to-fetch.ts | 30 +++++++++++++++++++++++ src/utils/user-agent.ts | 6 ----- src/utils/webhook.ts | 6 +++-- 10 files changed, 90 insertions(+), 51 deletions(-) rename src/{types.d.ts => types.ts} (57%) create mode 100644 src/utils/get-manga-title.ts create mode 100644 src/utils/get-manga-to-fetch.ts delete mode 100644 src/utils/user-agent.ts diff --git a/README.md b/README.md index c8df481..5b1aff2 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,12 @@ https://mangadex.org/title// The Check Interval is how often the script will check for updates, in seconds. +MangaDex requires setting a `userAgent` when making requests to their API, Please set this to something unique to you. + ```json { "checkInterval": 900, + "userAgent": "https://git.pfy.ch/pfych/chapter-tracker (Run by your-email@example.com)", "mangaByWebhook": { "https://discord.com/api/webhooks/id/token": [ "9d3d3403-1a87-4737-9803-bc3d99db1424", diff --git a/src/index.ts b/src/index.ts index 9c6fe5c..b9f97da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,46 +3,35 @@ import { getManga } from './mangadex/manga'; import { getCover } from './mangadex/cover'; import { lazyKv } from './db/lazyKv'; import { sendWebhook } from './utils/webhook'; -import { flatten, uniq } from 'lodash-es'; -import { ChapterId, MangaByWebhook } from './types'; +import { ChapterId } from './types'; +import { getMangaToFetch } from './utils/get-manga-to-fetch'; +import { getMangaTitle } from './utils/get-manga-title'; const config = new lazyKv('./config.json'); const mangaHistory = new lazyKv('./mangaHistory.json'); void (async () => { const checkInterval = await config.get('checkInterval', 900); - const mangaByWebhook = await config.get('mangaByWebhook', {}); - - const uniqueMangaIds = uniq(flatten(Object.values(mangaByWebhook))); - const mangaIdsToWebhooks = uniqueMangaIds.reduce((acc, mangaId) => { - return { - ...acc, - [mangaId]: Object.keys(mangaByWebhook).filter((webhookUrl) => - mangaByWebhook[webhookUrl].includes(mangaId), - ), - }; - }, {} as MangaByWebhook); - - console.log( - `Config loaded (${uniqueMangaIds.length} Manga, ${Object.keys(mangaByWebhook).length} Webhooks) `, + const userAgent = await config.get( + 'userAgent', + 'https://git.pfy.ch/pfych/chapter-tracker (Missing custom userAgent? User Miss-configured!)', ); const checkForUpdates = async () => { - console.log('\nChecking for updates...'); + const { uniqueMangaIds, mangaIdsToWebhooks } = + await getMangaToFetch(config); for (const mangaId of uniqueMangaIds) { const lastChapterId = await mangaHistory.get(mangaId); - const manga = await getManga(mangaId); - const chapters = await getAllChapters(mangaId); + const manga = await getManga(mangaId, userAgent); + const chapters = await getAllChapters(mangaId, userAgent); const latestChapter = getLatestChapter(chapters); - const cover = await getCover(manga); + const cover = await getCover(manga, userAgent); + const title = getMangaTitle(manga); if (lastChapterId !== latestChapter.id) { - console.log( - 'Update found for manga:', - manga.attributes.title.en || `MISSING ENGLISH TITLE (${mangaId})`, - ); + console.log('Update found for manga:', title); const webhooksForManga = mangaIdsToWebhooks[mangaId]; @@ -60,10 +49,7 @@ void (async () => { await mangaHistory.set(mangaId, latestChapter.id); } else { - console.log( - 'No Updates found for manga:', - manga.attributes.title.en || `MISSING ENGLISH TITLE (${mangaId})`, - ); + console.log('No Updates found for manga:', title); } } }; diff --git a/src/mangadex/chapters.ts b/src/mangadex/chapters.ts index 4fe3a1b..1802bc9 100644 --- a/src/mangadex/chapters.ts +++ b/src/mangadex/chapters.ts @@ -1,9 +1,11 @@ import axios from 'axios'; import { sleep } from '../utils/sleep'; import { Chapter } from '../types'; -import { getUserAgent } from '../utils/user-agent'; -export const getAllChapters = async (id: string): Promise => { +export const getAllChapters = async ( + id: string, + userAgent: string, +): Promise => { let nextPage = false; let offset = 0; @@ -20,7 +22,7 @@ export const getAllChapters = async (id: string): Promise => { offset, }, headers: { - 'User-Agent': getUserAgent(), + 'User-Agent': userAgent, }, }); diff --git a/src/mangadex/cover.ts b/src/mangadex/cover.ts index bfda9d1..0011834 100644 --- a/src/mangadex/cover.ts +++ b/src/mangadex/cover.ts @@ -1,8 +1,10 @@ import axios from 'axios'; import { Manga } from '../types'; -import { getUserAgent } from '../utils/user-agent'; -export const getCover = async (manga: Manga): Promise => { +export const getCover = async ( + manga: Manga, + userAgent: string, +): Promise => { const coverId = Object.values(manga.relationships).find( (relationship) => relationship.type === 'cover_art', )?.id; @@ -15,7 +17,7 @@ export const getCover = async (manga: Manga): Promise => { data: { attributes: { fileName: string } }; }>(`https://api.mangadex.org/cover/${coverId}`, { headers: { - 'User-Agent': getUserAgent(), + 'User-Agent': userAgent, }, }); diff --git a/src/mangadex/manga.ts b/src/mangadex/manga.ts index 09ea0f6..e7f82a7 100644 --- a/src/mangadex/manga.ts +++ b/src/mangadex/manga.ts @@ -1,8 +1,10 @@ import axios from 'axios'; import { Manga } from '../types'; -import { getUserAgent } from '../utils/user-agent'; -export const getManga = async (id: string): Promise => { +export const getManga = async ( + id: string, + userAgent: string, +): Promise => { const response = await axios.get<{ data: Manga; limit: number; @@ -10,7 +12,7 @@ export const getManga = async (id: string): Promise => { total: number; }>(`https://api.mangadex.org/manga/${id}`, { headers: { - 'User-Agent': getUserAgent(), + 'User-Agent': userAgent, }, }); diff --git a/src/types.d.ts b/src/types.ts similarity index 57% rename from src/types.d.ts rename to src/types.ts index ccb790c..17ad0af 100644 --- a/src/types.d.ts +++ b/src/types.ts @@ -1,9 +1,10 @@ -type MangaId = string; -type ChapterId = string; -type WebhookUrl = string; -type MangaByWebhook = Record; -type RelationshipType = 'author' | 'artist' | 'cover_art' | 'creator'; -type Language = 'en' | 'ja' | 'zh'; +export type MangaId = string; +export type ChapterId = string; +export type WebhookUrl = string; +export type MangaByWebhook = Record; +export type RelationshipType = 'author' | 'artist' | 'cover_art' | 'creator'; +export const languages = ['en', 'ja-ro', 'ja', 'zh-ro', 'zh'] as const; // Preferred title based off order of this array! +export type Language = (typeof languages)[number]; export interface Chapter { id: ChapterId; diff --git a/src/utils/get-manga-title.ts b/src/utils/get-manga-title.ts new file mode 100644 index 0000000..431d8d6 --- /dev/null +++ b/src/utils/get-manga-title.ts @@ -0,0 +1,17 @@ +import { languages, Manga } from '../types'; + +export const getMangaTitle = (manga: Manga) => { + const languageToUse = languages.find((lang) => manga.attributes.title[lang]); + + if (languageToUse) { + return manga.attributes.title[languageToUse]; + } + + // If no language is found, use the first one returned by the API + const fallbackLanguage = Object.keys(manga.attributes.title)?.[0]; + if (fallbackLanguage) { + return manga.attributes.title[fallbackLanguage] as string; + } + + return 'UNKNOWN TITLE'; +}; diff --git a/src/utils/get-manga-to-fetch.ts b/src/utils/get-manga-to-fetch.ts new file mode 100644 index 0000000..31cbf71 --- /dev/null +++ b/src/utils/get-manga-to-fetch.ts @@ -0,0 +1,30 @@ +import { lazyKv } from '../db/lazyKv'; +import { MangaByWebhook, MangaId } from '../types'; +import { flatten, uniq } from 'lodash-es'; + +export const getMangaToFetch = async ( + config: lazyKv, +): Promise<{ + uniqueMangaIds: MangaId[]; + mangaIdsToWebhooks: MangaByWebhook; +}> => { + const mangaByWebhook = await config.get('mangaByWebhook', {}); + const uniqueMangaIds = uniq(flatten(Object.values(mangaByWebhook))); + const mangaIdsToWebhooks = uniqueMangaIds.reduce((acc, mangaId) => { + return { + ...acc, + [mangaId]: Object.keys(mangaByWebhook).filter((webhookUrl) => + mangaByWebhook[webhookUrl].includes(mangaId), + ), + }; + }, {} as MangaByWebhook); + + console.log( + `Config loaded (${uniqueMangaIds.length} Manga, ${Object.keys(mangaByWebhook).length} Webhooks) `, + ); + + return { + uniqueMangaIds, + mangaIdsToWebhooks, + }; +}; diff --git a/src/utils/user-agent.ts b/src/utils/user-agent.ts deleted file mode 100644 index b83efbb..0000000 --- a/src/utils/user-agent.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const getUserAgent = () => { - return ( - process.env.USER_AGENT || - 'Personal Chapter Update Tracker (https://git.pfy.ch/pfych/chapter-tracker)' - ); -}; diff --git a/src/utils/webhook.ts b/src/utils/webhook.ts index e50c813..bd6dbcd 100644 --- a/src/utils/webhook.ts +++ b/src/utils/webhook.ts @@ -1,6 +1,7 @@ /* eslint-disable camelcase */ import axios from 'axios'; import { Chapter, Manga } from '../types'; +import { getMangaTitle } from './get-manga-title'; export const sendWebhook = async (args: { webhookUrl: string; @@ -9,16 +10,17 @@ export const sendWebhook = async (args: { cover: string; }) => { const { webhookUrl, manga, latestChapter, cover } = args; + const title = getMangaTitle(manga); await axios.post(webhookUrl, { username: 'Manga Updates', avatar_url: 'https://assets.pfy.ch/icons/manga.png', - content: `[New chapter for ${manga.attributes.title.en || 'MISSING ENGLISH TITLE'}](https://mangadex.org/chapter/${latestChapter.id})`, + content: `[New chapter for ${title}](https://mangadex.org/chapter/${latestChapter.id})`, embeds: [ { author: { icon_url: 'https://assets.pfy.ch/icons/manga.png', - name: manga.attributes.title.en || 'MISSING ENGLISH TITLE', + name: title, }, thumbnail: { url: cover,