'use strict'; const normalize = value => String(value || '').normalize('NFKD').toLowerCase().replace(/[^a-z0-9]+/g, ''); function seriesLink(value) { let url; try { url = new URL(String(value || '')); } catch { return null; } if (url.protocol !== 'https:') return null; const weeb = /^\/series\/([A-Z0-9]{26})(?:\/[^/?#]+)?\/?$/i.exec(url.pathname); if (url.hostname === 'weebcentral.com' && weeb) return { source: 'weebcentral', label: 'WeebCentral', seriesId: weeb[1].toUpperCase(), url: `https://weebcentral.com${url.pathname.replace(/\/$/, '')}`, }; const dex = /^\/title\/([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})(?:\/[^/?#]+)?\/?$/i.exec(url.pathname); if (['mangadex.org', 'www.mangadex.org'].includes(url.hostname) && dex) return { source: 'mangadex', label: 'MangaDex', seriesId: dex[1].toLowerCase(), url: `https://mangadex.org/title/${dex[1].toLowerCase()}`, }; return null; } const keyOf = link => `${link.source}:${link.seriesId}`; function hosterLinks(entry, owned, queue = []) { const names = new Set([entry.title, ...(entry.aliases || [])].map(normalize)); const known = new Map(); for (const source of owned?.meta?.sources || []) { const link = seriesLink(source.url); if (link) known.set(keyOf(link), { ...link, origin: 'library' }); } const jobs = []; for (const item of queue) { const link = seriesLink(item.url); if (!link) continue; if (item.libraryMangaId && owned && item.libraryMangaId !== owned.id) continue; if (!(owned && item.libraryMangaId === owned.id) && !names.has(normalize(item.title)) && !known.has(keyOf(link))) continue; if (!known.has(keyOf(link))) known.set(keyOf(link), { ...link, origin: 'queue title match' }); jobs.push({ id: item.id, source: link.source, seriesId: link.seriesId, kind: item.kind, status: item.status }); } return { links: [...known.values()], jobs }; } module.exports = { seriesLink, hosterLinks };