feat: add organize all button
fix: organizer reuse the wrong name for gitlab path
This commit is contained in:
1 parent
d7eb932ed3
commit
84ef0333f3
3 files changed
+93
-13
No files matched your search
+37
-11
@@ -67,12 +67,20 @@ function findTorrentByHash(hash: string, seriesData: any[]): { torrent: any; ser
|
||||
if (t.infohash?.toLowerCase() === hash.toLowerCase())
|
||||
return { torrent: { ...t, _season: season }, serieData: sd }
|
||||
}
|
||||
// Niveau épisode
|
||||
for (const ep of season.episodes ?? []) {
|
||||
for (const t of ep.torrents ?? []) {
|
||||
if (t.infohash?.toLowerCase() === hash.toLowerCase())
|
||||
return { torrent: { ...t, _episode: ep, _season: season }, serieData: sd }
|
||||
}
|
||||
// Niveau épisode — compter combien d'épisodes partagent ce torrent
|
||||
const matchingEpisodes = season.episodes?.filter((ep: any) =>
|
||||
ep.torrents?.some((t: any) => t.infohash?.toLowerCase() === hash.toLowerCase())
|
||||
) ?? []
|
||||
|
||||
if (matchingEpisodes.length === 1) {
|
||||
// Torrent propre à un seul épisode → cas épisode unique
|
||||
const ep = matchingEpisodes[0]
|
||||
const t = ep.torrents.find((t: any) => t.infohash?.toLowerCase() === hash.toLowerCase())
|
||||
return { torrent: { ...t, _episode: ep, _season: season }, serieData: sd }
|
||||
} else if (matchingEpisodes.length > 1) {
|
||||
// Même torrent partagé entre plusieurs épisodes → traiter comme pack_saison
|
||||
const t = matchingEpisodes[0].torrents.find((t: any) => t.infohash?.toLowerCase() === hash.toLowerCase())
|
||||
return { torrent: { ...t, _season: season }, serieData: sd }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,6 +143,23 @@ function isInExcludedFolder(filePath: string[]): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
// ─── Map correction titres API → titres GitLab ───────────────
|
||||
const SERIE_TITLE_GITLAB_MAP: Record<string, string> = {
|
||||
'Enfer Et Paradis Henshū' : 'Enfer et Paradis Henshū',
|
||||
'Hajime No Ippo Henshū' : 'Hajime no Ippo Henshū',
|
||||
'Hikaru No Go Henshū' : 'Hikaru no Go Henshū',
|
||||
'Hokuto No Ken Kaï' : 'Hokuto no Ken Kaï',
|
||||
'Kaguya-sama : Love is War Henshū' : 'Kaguya-sama - Love is War Henshū',
|
||||
'Kenshin le Vagabond Henshū' : 'Kenshin le vagabond Henshū',
|
||||
'Kuroko No Basket Henshū' : 'Kuroko no Basket Henshū',
|
||||
'Shingeki No Kyojin Henshū' : 'Shingeki no Kyojin Henshū',
|
||||
'Tower Of God Henshū' : 'Tower of God Henshū',
|
||||
}
|
||||
|
||||
function getGitlabSerieTitle(serieTitle: string): string {
|
||||
return SERIE_TITLE_GITLAB_MAP[serieTitle] ?? serieTitle
|
||||
}
|
||||
|
||||
// ─── GitLab NFO downloader ────────────────────────────────────
|
||||
const GITLAB_API = 'https://gitlab.com/api/v4/projects/ElPouki%2Ffankai_pack/repository'
|
||||
const GITLAB_RAW_BASE = 'https://gitlab.com/ElPouki/fankai_pack/-/raw/main/pack'
|
||||
@@ -154,8 +179,9 @@ async function fetchBinary(url: string): Promise<Buffer> {
|
||||
}
|
||||
|
||||
async function downloadGitlabFolder(serieTitle: string, destRoot: string): Promise<void> {
|
||||
const folderPath = `pack/${serieTitle}`
|
||||
log(`[nfo] Récupération dossier GitLab: ${serieTitle}`)
|
||||
const gitlabTitle = getGitlabSerieTitle(serieTitle)
|
||||
const folderPath = `pack/${gitlabTitle}`
|
||||
log(`[nfo] Récupération dossier GitLab: ${gitlabTitle}`)
|
||||
|
||||
let files: any[] = []
|
||||
try {
|
||||
@@ -180,12 +206,12 @@ async function downloadGitlabFolder(serieTitle: string, destRoot: string): Promi
|
||||
log(`[nfo] ${fileEntries.length} fichiers à télécharger`)
|
||||
|
||||
for (const entry of fileEntries) {
|
||||
const relativePath = entry.path.replace(`pack/${serieTitle}/`, '')
|
||||
const relativePath = entry.path.replace(`pack/${gitlabTitle}/`, '')
|
||||
const destPath = path.join(destRoot, relativePath)
|
||||
if (fs.existsSync(destPath)) continue
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(destPath), { recursive: true })
|
||||
const rawUrl = `${GITLAB_RAW_BASE}/${encodeURIComponent(serieTitle)}/${relativePath.split('/').map(encodeURIComponent).join('/')}`
|
||||
const rawUrl = `${GITLAB_RAW_BASE}/${encodeURIComponent(gitlabTitle)}/${relativePath.split('/').map(encodeURIComponent).join('/')}`
|
||||
const data = await fetchBinary(rawUrl)
|
||||
fs.writeFileSync(destPath, data)
|
||||
log(`[nfo] ✓ ${relativePath}`)
|
||||
@@ -193,7 +219,7 @@ async function downloadGitlabFolder(serieTitle: string, destRoot: string): Promi
|
||||
warn(`[nfo] ✗ ${relativePath}: ${err instanceof Error ? err.message : err}`)
|
||||
}
|
||||
}
|
||||
log(`[nfo] GitLab synchronisé pour "${serieTitle}"`)
|
||||
log(`[nfo] GitLab synchronisé pour "${gitlabTitle}"`)
|
||||
}
|
||||
|
||||
// ─── Filesystem ops ───────────────────────────────────────────
|
||||
|
||||
+13
-1
@@ -154,7 +154,19 @@ export async function autoOrganizeAll(
|
||||
|
||||
for (const t of torrents) _prevStates.set(t.hash, t.state)
|
||||
|
||||
if (!isFirstRun && newlySeeding.length === 0) return
|
||||
// Charger organized.json pour vérifier s'il reste des torrents non organisés
|
||||
let organized: Record<string, Record<string, string>> = {}
|
||||
try {
|
||||
const orgPath = path.join(DATA_DIR, 'organized.json')
|
||||
if (fs.existsSync(orgPath))
|
||||
organized = JSON.parse(fs.readFileSync(orgPath, 'utf-8'))
|
||||
} catch {}
|
||||
|
||||
const hasUnorganized = torrents.some(t =>
|
||||
t.state === 'seeding' && !organized[t.hash?.toLowerCase()]
|
||||
)
|
||||
|
||||
if (!isFirstRun && newlySeeding.length === 0 && !hasUnorganized) return
|
||||
if (seedingCount === 0) return
|
||||
|
||||
if (newlySeeding.length > 0 && !isFirstRun) {
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<h1 class="text-xl font-black text-white font-sans">Téléchargements</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
@click="organizeAll"
|
||||
:disabled="organizingAll"
|
||||
class="border border-[#1e2d3d] text-[#5a7a94] px-4 py-1.5 text-xs tracking-widest hover:border-green-500 hover:text-green-500 transition-colors cursor-pointer disabled:opacity-50">
|
||||
{{ organizingAll ? '...' : 'ORGANISER TOUT' }}
|
||||
</button>
|
||||
<div class="flex items-center gap-2 text-[10px] text-[#5a7a94] tracking-widest">
|
||||
<div class="w-1.5 h-1.5 rounded-full" :class="polling ? 'bg-green-500 animate-pulse' : 'bg-[#1e2d3d]'"/>
|
||||
{{ polling ? 'EN DIRECT' : 'PAUSE' }}
|
||||
@@ -190,6 +196,7 @@ const loading = ref(true)
|
||||
const polling = ref(true)
|
||||
const noClients = ref(false)
|
||||
const organizing = ref<Record<string, boolean>>({})
|
||||
const organizingAll = ref(false)
|
||||
const activeTab = ref<'active' | 'done'>('active')
|
||||
const hideOrganized = ref(false)
|
||||
|
||||
@@ -251,7 +258,7 @@ function togglePolling() {
|
||||
onMounted(startPolling)
|
||||
onUnmounted(stopPolling)
|
||||
|
||||
// ── Organiser ──────────────────────────────────────────────────
|
||||
// ── Organiser un torrent ───────────────────────────────────────
|
||||
async function organize(torrent: any) {
|
||||
organizing.value[torrent.hash] = true
|
||||
try {
|
||||
@@ -263,6 +270,7 @@ async function organize(torrent: any) {
|
||||
})
|
||||
if (res.ok) {
|
||||
toast(`${torrent.name} organisé ✓`, 'success')
|
||||
await fetchTorrents()
|
||||
} else {
|
||||
const { error } = await res.json()
|
||||
toast(error ?? 'Erreur lors de l\'organisation', 'error')
|
||||
@@ -274,6 +282,40 @@ async function organize(torrent: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Organiser tout ─────────────────────────────────────────────
|
||||
async function organizeAll() {
|
||||
const toOrganize = doneTorrents.value.filter(t => t.organizeState !== 'done')
|
||||
if (toOrganize.length === 0) {
|
||||
toast('Tous les torrents sont déjà organisés', 'success')
|
||||
return
|
||||
}
|
||||
|
||||
organizingAll.value = true
|
||||
let done = 0
|
||||
let errors = 0
|
||||
|
||||
for (const t of toOrganize) {
|
||||
try {
|
||||
const res = await fetch('/api/organize', {
|
||||
method : 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ hash: t.hash, save_path: t.save_path, name: t.name })
|
||||
})
|
||||
if (res.ok) done++
|
||||
else errors++
|
||||
} catch {
|
||||
errors++
|
||||
}
|
||||
}
|
||||
|
||||
organizingAll.value = false
|
||||
await fetchTorrents()
|
||||
|
||||
if (errors === 0) toast(`${done} torrent(s) organisé(s) ✓`, 'success')
|
||||
else toast(`${done} OK, ${errors} erreur(s)`, 'error')
|
||||
}
|
||||
|
||||
// ── Helpers affichage ──────────────────────────────────────────
|
||||
function stateBadge(state: string) {
|
||||
const map: Record<string, { label: string; class: string }> = {
|
||||
|
||||
Reference in new issue
Block a user