Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04252db0c4 | ||
|
|
aeb2e938ef | ||
|
|
3799d33c62 | ||
|
|
98b317d493 | ||
|
|
4292f96645 | ||
|
|
182c7cc807 | ||
|
|
53be057425 | ||
|
|
ac70dba927 | ||
|
|
f79cc244ef | ||
|
|
14137e1b1b | ||
|
|
43ee4597a9 | ||
|
|
f311c8e2f3 | ||
|
|
48933b4078 |
No files matched your search
@@ -1,5 +1,22 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [3.20.2](https://github.com/Masutayunikon/FanKarr/compare/v3.20.1...v3.20.2) (2026-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **client:** add placeholder from config ([3799d33](https://github.com/Masutayunikon/FanKarr/commit/3799d33c625fe28eab4b46917ff32b1266cda1e3))
|
||||||
|
* **client:** remove alldebrid ([3799d33](https://github.com/Masutayunikon/FanKarr/commit/3799d33c625fe28eab4b46917ff32b1266cda1e3))
|
||||||
|
* **label:** add season of episodes ([98b317d](https://github.com/Masutayunikon/FanKarr/commit/98b317d493b7742c330d49ca816c0fecd983e82a))
|
||||||
|
* **scraper:** fallback on empty env for GITHUB_BASE ([3799d33](https://github.com/Masutayunikon/FanKarr/commit/3799d33c625fe28eab4b46917ff32b1266cda1e3))
|
||||||
|
|
||||||
|
## [3.20.1](https://github.com/Masutayunikon/FanKarr/compare/v3.20.0...v3.20.1) (2026-06-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **organize:** import manuel ne traite que les fichiers 100% complets ([f311c8e](https://github.com/Masutayunikon/FanKarr/commit/f311c8e2f3b9f53b6d03a327cf1b5bfd69cc6424))
|
||||||
|
|
||||||
## [3.20.0](https://github.com/Masutayunikon/FanKarr/compare/v3.19.1...v3.20.0) (2026-05-29)
|
## [3.20.0](https://github.com/Masutayunikon/FanKarr/compare/v3.19.1...v3.20.0) (2026-05-29)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 231 KiB |
@@ -2,8 +2,9 @@ import { DATA_DIR } from '../config.js'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
const GITHUB_BASE = process.env.GITHUB_BASE
|
const GITHUB_BASE =
|
||||||
?? 'https://raw.githubusercontent.com/masutayunikon/fankarr-scraper/main'
|
process.env.GITHUB_BASE?.trim() ||
|
||||||
|
'https://raw.githubusercontent.com/masutayunikon/fankarr-scraper/main'
|
||||||
|
|
||||||
const CACHE_TTL_MS = 1 * 60 * 60 * 1000
|
const CACHE_TTL_MS = 1 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -520,13 +520,17 @@ export async function organizeTorrent(
|
|||||||
hash : string,
|
hash : string,
|
||||||
name : string,
|
name : string,
|
||||||
savePath : string,
|
savePath : string,
|
||||||
seriesData: any[]
|
seriesData: any[],
|
||||||
|
files? : { name?: string; progress: number; priority?: number }[],
|
||||||
): Promise<OrganizeResult> {
|
): Promise<OrganizeResult> {
|
||||||
logger.info('organize', `Import manuel lancé pour "${name}" (${hash})`)
|
logger.info('organize', `Import manuel lancé pour "${name}" (${hash})`)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const worker = new Worker(resolveWorkerPath())
|
const worker = new Worker(resolveWorkerPath())
|
||||||
const fakeTorrent = { hash, name, save_path: savePath, state: 'seeding' }
|
// files : progression par fichier récupérée depuis le client torrent.
|
||||||
|
// Permet au worker de ne traiter que les fichiers à 100% et d'éviter
|
||||||
|
// les EBUSY sur Windows quand qBit est encore en train d'écrire les autres.
|
||||||
|
const fakeTorrent = { hash, name, save_path: savePath, state: 'seeding', files: files ?? [] }
|
||||||
const result: OrganizeResult = { total: 0, skipped: 0, done: 0, errors: [] }
|
const result: OrganizeResult = { total: 0, skipped: 0, done: 0, errors: [] }
|
||||||
|
|
||||||
worker.on('message', (msg: any) => {
|
worker.on('message', (msg: any) => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { logger } from '../logger.js'
|
|||||||
import { organizeTorrent, migrateOrganizedEpisodeIds } from '../organize.js'
|
import { organizeTorrent, migrateOrganizedEpisodeIds } from '../organize.js'
|
||||||
import { loadEnrichedSeriesData } from '../lib/github-cache.js'
|
import { loadEnrichedSeriesData } from '../lib/github-cache.js'
|
||||||
import { recentOrganized, pushNotif } from '../lib/notifs.js'
|
import { recentOrganized, pushNotif } from '../lib/notifs.js'
|
||||||
|
import { dispatchGetFiles } from '../torrent-clients/index.js'
|
||||||
import { DATA_DIR } from '../config.js'
|
import { DATA_DIR } from '../config.js'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
@@ -36,7 +37,13 @@ router.post('/organize', requireAuth, async (req, res) => {
|
|||||||
if (!hash || !name || !save_path) { res.status(400).json({ error: 'hash, name et save_path requis' }); return }
|
if (!hash || !name || !save_path) { res.status(400).json({ error: 'hash, name et save_path requis' }); return }
|
||||||
try {
|
try {
|
||||||
const seriesData = await loadEnrichedSeriesData()
|
const seriesData = await loadEnrichedSeriesData()
|
||||||
const result = await organizeTorrent(hash, name, save_path, seriesData)
|
|
||||||
|
// Récupère la progression par fichier depuis le client torrent.
|
||||||
|
// Permet au worker de sauter les fichiers encore en téléchargement (EBUSY sur Windows).
|
||||||
|
// En cas d'échec (client injoignable, hash non trouvé), on continue sans filtre.
|
||||||
|
const files = await dispatchGetFiles(hash).catch(() => [])
|
||||||
|
|
||||||
|
const result = await organizeTorrent(hash, name, save_path, seriesData, files)
|
||||||
if (result.done > 0 || result.errors.length > 0) {
|
if (result.done > 0 || result.errors.length > 0) {
|
||||||
pushNotif({ hash, name, done: result.done, skipped: result.skipped, errors: result.errors.length, errorFiles: result.errors, at: new Date().toISOString() })
|
pushNotif({ hash, name, done: result.done, skipped: result.skipped, errors: result.errors.length, errorFiles: result.errors, at: new Date().toISOString() })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -606,33 +606,47 @@ const _epChapCache = new Map<string | null | undefined, ReturnType<typeof _parse
|
|||||||
*/
|
*/
|
||||||
function _parseEpChap(plot: string | null | undefined): {
|
function _parseEpChap(plot: string | null | undefined): {
|
||||||
episodes: string | null
|
episodes: string | null
|
||||||
chapters: string | null
|
season: number | null
|
||||||
|
chapters: string |null
|
||||||
cleanPlot: string
|
cleanPlot: string
|
||||||
} {
|
} {
|
||||||
const text = plot ?? ''
|
const text = plot ?? ''
|
||||||
|
|
||||||
const epMatch = text.match(/[éeÉE]pisodes?\s+(\d+)(?:\s+[àa]\s+(\d+))?/i)
|
const epMatch = text.match(
|
||||||
const chapMatch = text.match(/chapitres?\s+(\d+)(?:\s+[àa]\s+(\d+))?/i)
|
/[éeÉE]pisodes?\s+(\d+)(?:\s+[àa]\s+(\d+))?(?:\s+de\s+la\s+saison\s+(\d+))?/i
|
||||||
|
)
|
||||||
|
|
||||||
|
const seasonMatch = text.match(/saison\s+(\d+)/i)
|
||||||
|
|
||||||
|
const chapMatch = text.match(
|
||||||
|
/chapitres?\s+(\d+)(?:\s+[àa]\s+(\d+))?/i
|
||||||
|
)
|
||||||
|
|
||||||
const episodes = epMatch
|
const episodes = epMatch
|
||||||
? (epMatch[2] ? `Ép. ${epMatch[1]}–${epMatch[2]}` : `Ép. ${epMatch[1]}`)
|
? (epMatch[2] ? `Ép. ${epMatch[1]}–${epMatch[2]}` : `Ép. ${epMatch[1]}`)
|
||||||
: null
|
: null
|
||||||
const chapters = chapMatch
|
|
||||||
? (chapMatch[2] ? `Ch. ${chapMatch[1]}–${chapMatch[2]}` : `Ch. ${chapMatch[1]}`)
|
const season = epMatch?.[3]
|
||||||
: null
|
? Number(epMatch[3])
|
||||||
|
: seasonMatch
|
||||||
|
? Number(seasonMatch[1])
|
||||||
|
: null
|
||||||
|
|
||||||
|
const chapters = chapMatch
|
||||||
|
? (chapMatch[2] ? `Ch. ${chapMatch[1]}–${chapMatch[2]}` : `Ch. ${chapMatch[1]}`)
|
||||||
|
: null
|
||||||
|
|
||||||
// Retirer les lignes contenant les références d'épisodes ou chapitres
|
|
||||||
let cleanPlot = text
|
let cleanPlot = text
|
||||||
if (episodes || chapters) {
|
if (episodes || chapters || season) {
|
||||||
cleanPlot = text
|
cleanPlot = text
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.filter(line => !/[éeÉE]pisodes?\s+\d+|chapitres?\s+\d+/i.test(line))
|
.filter(line => !/(?:[éeÉE]pisodes?\s+\d+|chapitres?\s+\d+|saison\s+\d+)/i.test(line))
|
||||||
.join('\n')
|
.join('\n')
|
||||||
.replace(/\n{3,}/g, '\n\n')
|
.replace(/\n{3,}/g, '\n\n')
|
||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
return { episodes, chapters, cleanPlot }
|
return { episodes, season, chapters, cleanPlot }
|
||||||
}
|
}
|
||||||
function parseEpChap(plot: string | null | undefined) {
|
function parseEpChap(plot: string | null | undefined) {
|
||||||
if (_epChapCache.has(plot)) return _epChapCache.get(plot)!
|
if (_epChapCache.has(plot)) return _epChapCache.get(plot)!
|
||||||
|
|||||||
@@ -182,7 +182,7 @@
|
|||||||
<input
|
<input
|
||||||
v-model="modal.config[field.key]"
|
v-model="modal.config[field.key]"
|
||||||
:type="field.type === 'password' ? 'password' : 'text'"
|
:type="field.type === 'password' ? 'password' : 'text'"
|
||||||
placeholder=""
|
:placeholder="field.placeholder ?? ''"
|
||||||
class="settings-input"
|
class="settings-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -254,11 +254,7 @@ const clientMatrix = [
|
|||||||
{
|
{
|
||||||
id: 'real-debrid', label: 'Real-Debrid', version: 'API v1',
|
id: 'real-debrid', label: 'Real-Debrid', version: 'API v1',
|
||||||
auth: 'Clé API', fileSelect: true, fileProgress: true, torrentUpload: 'proxy', categories: false, deleteFiles: true,
|
auth: 'Clé API', fileSelect: true, fileProgress: true, torrentUpload: 'proxy', categories: false, deleteFiles: true,
|
||||||
},
|
}
|
||||||
{
|
|
||||||
id: 'alldebrid', label: 'AllDebrid', version: 'API v4',
|
|
||||||
auth: 'Clé API', fileSelect: false, fileProgress: true, torrentUpload: 'proxy', categories: false, deleteFiles: true,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const modal = ref({
|
const modal = ref({
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
3.20.0
|
3.20.2
|
||||||
Reference in new issue
Block a user