fix(surveillance): détection des nouveaux épisodes par la date (#152)
This commit is contained in:
1 parent
e8dc76e94a
commit
ddc6d665b6
1 file changed
+76
-57
+76
-57
@@ -1,4 +1,4 @@
|
||||
/** Surveillance : envoie au client les épisodes ajoutés au catalogue après activation (toutes les 6 h). */
|
||||
/** Surveillance : envoie au client les épisodes dont le torrent est publié après activation (toutes les 6 h). */
|
||||
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
@@ -136,6 +136,44 @@ function getOrganizedEpisodeIds(sd: any, organized: Record<string, Record<string
|
||||
return ids
|
||||
}
|
||||
|
||||
// ── Torrents publiés ──────────────────────────────────────────
|
||||
|
||||
// pub_date nyaa : « AAAA-MM-JJ HH:MM » en UTC
|
||||
function pubTime(t: any): number {
|
||||
return typeof t.pub_date === 'string' ? Date.parse(`${t.pub_date.replace(' ', 'T')}Z`) : NaN
|
||||
}
|
||||
|
||||
interface ReleaseEpisode { key: string; name: string; pathEntry: any }
|
||||
interface Release { torrent: any; episodes: ReleaseEpisode[] }
|
||||
|
||||
// Torrents Fankai de la série et épisodes qu'ils contiennent
|
||||
function listReleases(sd: any): Release[] {
|
||||
const byHash = new Map<string, Release>()
|
||||
const add = (t: any) => {
|
||||
const hash = t.infohash?.toLowerCase()
|
||||
if (t.fankai && hash && (t.torrent_url || t.magnet) && !byHash.has(hash)) byHash.set(hash, { torrent: t, episodes: [] })
|
||||
}
|
||||
for (const t of sd.torrents ?? []) add(t)
|
||||
for (const season of sd.seasons ?? []) {
|
||||
for (const t of season.torrents ?? []) add(t)
|
||||
for (const ep of season.episodes ?? []) for (const t of ep.torrents ?? []) add(t)
|
||||
}
|
||||
|
||||
for (const season of sd.seasons ?? []) {
|
||||
for (const ep of season.episodes ?? []) {
|
||||
const key = `${season.season_number}:${ep.episode_number}`
|
||||
const name = `S${String(season.season_number).padStart(2, '0')}E${String(ep.episode_number).padStart(2, '0')}`
|
||||
const hashes = new Set<string>([...(ep.paths ?? []), ...(ep.torrents ?? [])].map((x: any) => x?.infohash?.toLowerCase()).filter(Boolean))
|
||||
for (const hash of hashes) {
|
||||
const release = byHash.get(hash)
|
||||
if (!release || release.episodes.some(e => e.key === key)) continue
|
||||
release.episodes.push({ key, name, pathEntry: (ep.paths ?? []).find((p: any) => p?.infohash?.toLowerCase() === hash) ?? null })
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...byHash.values()]
|
||||
}
|
||||
|
||||
// ── Vérification des séries surveillées ───────────────────────
|
||||
|
||||
export async function runRssSync(): Promise<{ sent: number; skipped: number; errors: number }> {
|
||||
@@ -172,76 +210,56 @@ export async function runRssSync(): Promise<{ sent: number; skipped: number; err
|
||||
continue
|
||||
}
|
||||
|
||||
const title = sd.title ?? sd.show_title ?? String(serieId)
|
||||
const title = sd.title ?? sd.show_title ?? String(serieId)
|
||||
const activatedAt = new Date(syncedEntry.addedAt)
|
||||
const organizedIds = getOrganizedEpisodeIds(sd, organized)
|
||||
const releases = listReleases(sd)
|
||||
|
||||
logger.info('rss-sync', `Vérification de « ${title} » (surveillée depuis le ${activatedAt.toLocaleDateString('fr-FR')})`)
|
||||
|
||||
const candidateIds = new Set<number>()
|
||||
for (const season of sd.seasons ?? []) {
|
||||
for (const ep of season.episodes ?? []) {
|
||||
if (!ep.date_added) continue
|
||||
const epDate = new Date(ep.date_added)
|
||||
if (epDate <= activatedAt) continue
|
||||
if (organizedIds.has(ep.id)) continue
|
||||
const hasTorrent = (ep.torrents ?? []).some((t: any) => t.fankai && (t.torrent_url || t.magnet))
|
||||
if (!hasTorrent) continue
|
||||
candidateIds.add(ep.id)
|
||||
}
|
||||
}
|
||||
// Clé saison:épisode : les variantes x264/x265 d'un même épisode comptent pour un
|
||||
const organizedKeys = new Set<string>()
|
||||
for (const season of sd.seasons ?? [])
|
||||
for (const ep of season.episodes ?? [])
|
||||
if (organizedIds.has(ep.id)) organizedKeys.add(`${season.season_number}:${ep.episode_number}`)
|
||||
|
||||
if (candidateIds.size === 0) {
|
||||
// Nouvel épisode
|
||||
const firstPub = new Map<string, number>()
|
||||
for (const { torrent, episodes } of releases) {
|
||||
const time = pubTime(torrent)
|
||||
for (const { key } of episodes)
|
||||
firstPub.set(key, Math.min(firstPub.get(key) ?? Infinity, Number.isNaN(time) ? -Infinity : time))
|
||||
}
|
||||
const candidates = new Set([...firstPub].filter(([key, time]) => time > activatedAt.getTime() && !organizedKeys.has(key)).map(([key]) => key))
|
||||
|
||||
if (candidates.size === 0) {
|
||||
logger.info('rss-sync', `« ${title} » : aucun nouvel épisode depuis l'activation`)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.info('rss-sync', `« ${title} » : ${candidateIds.size} épisode(s) à télécharger`)
|
||||
logger.info('rss-sync', `« ${title} » : ${candidates.size} épisode(s) à télécharger`)
|
||||
|
||||
const toDownload: { url: string; file_index?: number | null; file_path?: string | null; infohash?: string | null; label: string }[] = []
|
||||
const coveredIds = new Set<number>()
|
||||
const toDownload: { url: string; magnet: string | null; file_index: number | null; file_path: string | null; infohash: string; label: string }[] = []
|
||||
const covered = new Set<string>()
|
||||
const missing = (r: Release) => r.episodes.filter(e => candidates.has(e.key) && !covered.has(e.key))
|
||||
|
||||
for (const season of sd.seasons ?? []) {
|
||||
for (const t of season.torrents ?? []) {
|
||||
if (!t.fankai) continue
|
||||
const url = t.torrent_url ?? t.magnet
|
||||
if (!url) continue
|
||||
const hash = t.infohash?.toLowerCase()
|
||||
if (hash && activeHashes.has(hash)) { skipped++; continue }
|
||||
for (const release of [...releases].sort((a, b) => missing(b).length - missing(a).length)) {
|
||||
const eps = missing(release)
|
||||
if (eps.length === 0) continue
|
||||
for (const e of eps) covered.add(e.key)
|
||||
|
||||
const coversSomeCandidate = (season.episodes ?? []).some((ep: any) =>
|
||||
candidateIds.has(ep.id) && !coveredIds.has(ep.id)
|
||||
)
|
||||
if (!coversSomeCandidate) { skipped++; continue }
|
||||
const hash = release.torrent.infohash.toLowerCase()
|
||||
if (activeHashes.has(hash)) { skipped++; continue }
|
||||
|
||||
toDownload.push({ url, infohash: hash ?? null, label: `S${season.season_number} ${title}` })
|
||||
for (const ep of season.episodes ?? []) coveredIds.add(ep.id)
|
||||
}
|
||||
}
|
||||
|
||||
for (const season of sd.seasons ?? []) {
|
||||
for (const ep of season.episodes ?? []) {
|
||||
if (!candidateIds.has(ep.id) || coveredIds.has(ep.id)) continue
|
||||
|
||||
for (const t of ep.torrents ?? []) {
|
||||
if (!t.fankai) continue
|
||||
const url = t.torrent_url ?? t.magnet
|
||||
if (!url) continue
|
||||
const hash = t.infohash?.toLowerCase()
|
||||
if (hash && activeHashes.has(hash)) { skipped++; continue }
|
||||
|
||||
const pathEntry = (ep.paths ?? []).find((p: any) => typeof p === 'object' && p.infohash?.toLowerCase() === hash)
|
||||
toDownload.push({
|
||||
url,
|
||||
infohash : hash ?? null,
|
||||
file_index: pathEntry?.file_index ?? null,
|
||||
file_path : pathEntry?.path ?? null,
|
||||
label : `S${season.season_number}E${ep.episode_number} ${title}`,
|
||||
})
|
||||
coveredIds.add(ep.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
const single = eps.length === 1 ? eps[0] : null
|
||||
toDownload.push({
|
||||
url : release.torrent.torrent_url ?? release.torrent.magnet,
|
||||
magnet : release.torrent.magnet ?? null,
|
||||
infohash : hash,
|
||||
file_index: single?.pathEntry?.file_index ?? null,
|
||||
file_path : single?.pathEntry?.path ?? null,
|
||||
label : `« ${title} » ${single ? single.name : `(${eps.length} épisodes)`}`,
|
||||
})
|
||||
}
|
||||
|
||||
for (const dl of toDownload) {
|
||||
@@ -250,6 +268,7 @@ export async function runRssSync(): Promise<{ sent: number; skipped: number; err
|
||||
file_index: dl.file_index,
|
||||
file_path : dl.file_path,
|
||||
infohash : dl.infohash,
|
||||
magnet : dl.magnet,
|
||||
})
|
||||
if (results.some((r: any) => r.ok)) {
|
||||
logger.info('rss-sync', `Envoyé au client : ${dl.label}`)
|
||||
|
||||
Reference in new issue
Block a user