Compare commits

...
6 Commits
5 changed files with 50 additions and 40 deletions

No files matched your search

+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## [3.1.1](https://github.com/Masutayunikon/FanKarr/compare/v3.1.0...v3.1.1) (2026-03-28)
### Bug Fixes
* remove plex settings ([8744ade](https://github.com/Masutayunikon/FanKarr/commit/8744adeff5f2db1ab648412d50495690d9e68d0b))
* torrent for multiple seasons is now working ([2514008](https://github.com/Masutayunikon/FanKarr/commit/25140086c13ce5a497500659bdd6cea2aeff96ed))
## [3.1.0](https://github.com/Masutayunikon/FanKarr/compare/v3.0.0...v3.1.0) (2026-03-27) ## [3.1.0](https://github.com/Masutayunikon/FanKarr/compare/v3.0.0...v3.1.0) (2026-03-27)
+2 -2
View File
@@ -41,8 +41,8 @@ app.get('/api/settings', requireAuth, (_req, res) => {
res.json(readSettings()) res.json(readSettings())
}) })
app.post('/api/settings', requireAuth, (req, res) => { app.post('/api/settings', requireAuth, (req, res) => {
const { mediaPath, completePath, organizeMode, category, nfoSupport, autoImport, usePlexTitles } = req.body const { mediaPath, completePath, organizeMode, category, nfoSupport, autoImport } = req.body
const updated = writeSettings({ mediaPath, completePath, organizeMode, category, nfoSupport, autoImport, usePlexTitles }) const updated = writeSettings({ mediaPath, completePath, organizeMode, category, nfoSupport, autoImport })
logger.info('api', 'Paramètres mis à jour') logger.info('api', 'Paramètres mis à jour')
res.json(updated) res.json(updated)
}) })
+28 -17
View File
@@ -20,12 +20,12 @@ function error(msg: string) { parentPort?.postMessage({ type: 'log', level: 'err
function debug(msg: string) { parentPort?.postMessage({ type: 'log', level: 'debug', msg }) } function debug(msg: string) { parentPort?.postMessage({ type: 'log', level: 'debug', msg }) }
// ─── Settings ───────────────────────────────────────────────── // ─── Settings ─────────────────────────────────────────────────
function readSettings(): { mediaPath: string; completePath: string; organizeMode: string; nfoSupport: boolean; usePlexTitles: boolean } { function readSettings(): { mediaPath: string; completePath: string; organizeMode: string; nfoSupport: boolean } {
try { try {
const p = path.join(DATA_DIR, 'settings.json') const p = path.join(DATA_DIR, 'settings.json')
if (!fs.existsSync(p)) return { mediaPath: '', completePath: '', organizeMode: 'hardlink', nfoSupport: false, usePlexTitles: false } if (!fs.existsSync(p)) return { mediaPath: '', completePath: '', organizeMode: 'hardlink', nfoSupport: false }
return JSON.parse(fs.readFileSync(p, 'utf-8')) return JSON.parse(fs.readFileSync(p, 'utf-8'))
} catch { return { mediaPath: '', completePath: '', organizeMode: 'hardlink', nfoSupport: false, usePlexTitles: false } } } catch { return { mediaPath: '', completePath: '', organizeMode: 'hardlink', nfoSupport: false } }
} }
// ─── Organized log ──────────────────────────────────────────── // ─── Organized log ────────────────────────────────────────────
@@ -49,26 +49,42 @@ function isOrganized(hash: string, filename: string): boolean {
// ─── Lookup torrent dans les seriesData ─────────────────────── // ─── Lookup torrent dans les seriesData ───────────────────────
function findTorrentByHash(hash: string, seriesData: any[]): { torrent: any; serieData: any } | null { function findTorrentByHash(hash: string, seriesData: any[]): { torrent: any; serieData: any } | null {
const h = hash.toLowerCase()
for (const sd of seriesData) { for (const sd of seriesData) {
// Niveau série (pack intégrale)
for (const t of sd.torrents ?? []) { for (const t of sd.torrents ?? []) {
if (t.infohash?.toLowerCase() === hash.toLowerCase()) if (t.infohash?.toLowerCase() === h)
return { torrent: t, serieData: sd } return { torrent: t, serieData: sd }
} }
// Niveau saison — vérifier si le même hash apparaît dans plusieurs saisons
// Si oui, pas de _season pour que buildFileMap scanne toutes les saisons
const seasonsWithHash = (sd.seasons ?? []).filter((s: any) =>
s.torrents?.some((t: any) => t.infohash?.toLowerCase() === h)
)
if (seasonsWithHash.length === 1) {
// Torrent propre à une seule saison → on filtre sur cette saison
const season = seasonsWithHash[0]
const t = season.torrents.find((t: any) => t.infohash?.toLowerCase() === h)
return { torrent: { ...t, _season: season }, serieData: sd }
} else if (seasonsWithHash.length > 1) {
// Même torrent dans plusieurs saisons → pas de filtre saison
const t = seasonsWithHash[0].torrents.find((t: any) => t.infohash?.toLowerCase() === h)
return { torrent: t, serieData: sd }
}
// Niveau épisode
for (const season of sd.seasons ?? []) { for (const season of sd.seasons ?? []) {
for (const t of season.torrents ?? []) {
if (t.infohash?.toLowerCase() === hash.toLowerCase())
return { torrent: { ...t, _season: season }, serieData: sd }
}
const matchingEpisodes = season.episodes?.filter((ep: any) => const matchingEpisodes = season.episodes?.filter((ep: any) =>
ep.torrents?.some((t: any) => t.infohash?.toLowerCase() === hash.toLowerCase()) ep.torrents?.some((t: any) => t.infohash?.toLowerCase() === h)
) ?? [] ) ?? []
if (matchingEpisodes.length === 1) { if (matchingEpisodes.length === 1) {
const ep = matchingEpisodes[0] const ep = matchingEpisodes[0]
const t = ep.torrents.find((t: any) => t.infohash?.toLowerCase() === hash.toLowerCase()) const t = ep.torrents.find((t: any) => t.infohash?.toLowerCase() === h)
return { torrent: { ...t, _episode: ep, _season: season }, serieData: sd } return { torrent: { ...t, _episode: ep, _season: season }, serieData: sd }
} else if (matchingEpisodes.length > 1) { } else if (matchingEpisodes.length > 1) {
const t = matchingEpisodes[0].torrents.find((t: any) => t.infohash?.toLowerCase() === hash.toLowerCase()) const t = matchingEpisodes[0].torrents.find((t: any) => t.infohash?.toLowerCase() === h)
return { torrent: { ...t, _season: season }, serieData: sd } return { torrent: { ...t, _season: season }, serieData: sd }
} }
} }
@@ -255,13 +271,8 @@ async function organizeTorrent(hash: string, name: string, savePath: string, ser
} }
const { torrent, serieData } = found const { torrent, serieData } = found
const { usePlexTitles } = readSettings()
const rawTitle = serieData.title ?? serieData.show_title ?? name const rawTitle = serieData.title ?? serieData.show_title ?? name
const serieTitle = sanitizeDirName( const serieTitle = sanitizeDirName(rawTitle)
usePlexTitles && serieData.title_for_plex
? serieData.title_for_plex
: rawTitle
)
debug(`Série identifiée : "${serieTitle}" (mode: ${organizeMode}${nfoSupport ? ', NFO' : ''})`) debug(`Série identifiée : "${serieTitle}" (mode: ${organizeMode}${nfoSupport ? ', NFO' : ''})`)
+12 -14
View File
@@ -5,23 +5,21 @@ import { DATA_DIR } from './config.js'
const DATA_PATH = path.join(DATA_DIR, 'settings.json') const DATA_PATH = path.join(DATA_DIR, 'settings.json')
export interface Settings { export interface Settings {
mediaPath : string mediaPath : string
completePath : string completePath : string
organizeMode : 'hardlink' | 'move' organizeMode : 'hardlink' | 'move'
category : string category : string
nfoSupport : boolean nfoSupport : boolean
autoImport : boolean autoImport : boolean
usePlexTitles : boolean // utilise title_for_plex pour les noms de dossiers
} }
const defaults: Settings = { const defaults: Settings = {
mediaPath : '', mediaPath : '',
completePath : '', completePath: '',
organizeMode : 'hardlink', organizeMode: 'hardlink',
category : 'fankai', category : 'fankai',
nfoSupport : false, nfoSupport : false,
autoImport : true, autoImport : true,
usePlexTitles: false,
} }
export function readSettings(): Settings { export function readSettings(): Settings {
@@ -72,12 +72,6 @@
description="Importer automatiquement dès qu'un téléchargement est terminé. Vérifie toutes les 5 minutes." description="Importer automatiquement dès qu'un téléchargement est terminé. Vérifie toutes les 5 minutes."
/> />
<SettingsToggle
v-model="form.usePlexTitles"
label="Titres optimisés Plex"
description="Utilise title_for_plex pour les noms de dossiers — évite les caractères spéciaux."
/>
<SettingsToggle <SettingsToggle
v-model="form.nfoSupport" v-model="form.nfoSupport"
label="NFO / Métadonnées" label="NFO / Métadonnées"
@@ -133,7 +127,6 @@ const form = ref({
organizeMode : 'hardlink' as 'hardlink' | 'move', organizeMode : 'hardlink' as 'hardlink' | 'move',
nfoSupport : false, nfoSupport : false,
autoImport : true, autoImport : true,
usePlexTitles: false,
}) })
const picker = ref<{ open: boolean; field: 'completePath' | 'mediaPath'; currentPath: string }>({ const picker = ref<{ open: boolean; field: 'completePath' | 'mediaPath'; currentPath: string }>({