Compare commits

...
10 Commits
5 changed files with 66 additions and 13 deletions

No files matched your search

+14
View File
@@ -1,5 +1,19 @@
# Changelog # Changelog
## [3.11.2](https://github.com/Masutayunikon/FanKarr/compare/v3.11.1...v3.11.2) (2026-04-26)
### Bug Fixes
* ajout de undici dans le package.json ([459e602](https://github.com/Masutayunikon/FanKarr/commit/459e602c1df1c610466be5015a660624b1189620))
## [3.11.1](https://github.com/Masutayunikon/FanKarr/compare/v3.11.0...v3.11.1) (2026-04-26)
### Bug Fixes
* le setup plex peux maintenant etre utilisé sur un serveur local (probleme de certificat) ([6907584](https://github.com/Masutayunikon/FanKarr/commit/69075842be396b149949b158922a9dc84dbc7ccf))
## [3.11.0](https://github.com/Masutayunikon/FanKarr/compare/v3.10.0...v3.11.0) (2026-04-23) ## [3.11.0](https://github.com/Masutayunikon/FanKarr/compare/v3.10.0...v3.11.0) (2026-04-23)
+1
View File
@@ -26,6 +26,7 @@
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"undici": "^8.1.0",
"vue": "^3.5.25", "vue": "^3.5.25",
"vue-router": "^5.0.3" "vue-router": "^5.0.3"
}, },
+9
View File
@@ -38,6 +38,9 @@ importers:
tailwindcss: tailwindcss:
specifier: ^4.2.1 specifier: ^4.2.1
version: 4.2.1 version: 4.2.1
undici:
specifier: ^8.1.0
version: 8.1.0
vue: vue:
specifier: ^3.5.25 specifier: ^3.5.25
version: 3.5.29(typescript@5.9.3) version: 3.5.29(typescript@5.9.3)
@@ -1343,6 +1346,10 @@ packages:
undici-types@7.16.0: undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
undici@8.1.0:
resolution: {integrity: sha512-E9MkTS4xXLnRPYqxH2e6Hr2/49e7WFDKczKcCaFH4VaZs2iNvHMqeIkyUAD9vM8kujy9TjVrRlQ5KkdEJxB2pw==}
engines: {node: '>=22.19.0'}
unpipe@1.0.0: unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -2645,6 +2652,8 @@ snapshots:
undici-types@7.16.0: {} undici-types@7.16.0: {}
undici@8.1.0: {}
unpipe@1.0.0: {} unpipe@1.0.0: {}
unplugin-utils@0.3.1: unplugin-utils@0.3.1:
+41 -12
View File
@@ -1,4 +1,5 @@
import { Router } from 'express' import { Router } from 'express'
import { Agent, fetch as undiciFetch } from 'undici'
import { requireAuth } from '../auth.js' import { requireAuth } from '../auth.js'
import { logger } from '../logger.js' import { logger } from '../logger.js'
@@ -6,6 +7,16 @@ const router = Router()
const PLEX_TV_API = 'https://plex.tv/api/v2' const PLEX_TV_API = 'https://plex.tv/api/v2'
// ── Fetch avec bypass SSL pour les serveurs locaux ────────────────────────────
function getServerFetch(serverUrl: string) {
return (url: string, opts: any = {}) =>
undiciFetch(url, {
...opts,
dispatcher: new Agent({ connect: { rejectUnauthorized: false } }),
})
}
// ── Fetch vers plex.tv (certificat valide, pas besoin de bypass) ──────────────
async function plexFetch(url: string, options: RequestInit = {}): Promise<any> { async function plexFetch(url: string, options: RequestInit = {}): Promise<any> {
const res = await fetch(url, { const res = await fetch(url, {
...options, ...options,
@@ -105,6 +116,10 @@ router.post('/plex/setup', requireAuth, async (req, res) => {
if (!token || !serverUrl || !libraryName || !libraryPath) { if (!token || !serverUrl || !libraryName || !libraryPath) {
res.status(400).json({ error: 'token, serverUrl, libraryName, libraryPath requis' }); return res.status(400).json({ error: 'token, serverUrl, libraryName, libraryPath requis' }); return
} }
// Fetch avec SSL bypass pour les appels vers le serveur Plex local
const serverFetch = getServerFetch(serverUrl)
const headers: Record<string, string> = { 'X-Plex-Token': token, 'Accept': 'application/json' } const headers: Record<string, string> = { 'X-Plex-Token': token, 'Accept': 'application/json' }
const TARGET_AGENT_URI = 'https://metadata.fankai.fr/plex' const TARGET_AGENT_URI = 'https://metadata.fankai.fr/plex'
const steps: { step: string; ok: boolean; message: string }[] = [] const steps: { step: string; ok: boolean; message: string }[] = []
@@ -114,27 +129,30 @@ router.post('/plex/setup', requireAuth, async (req, res) => {
// ── Étape 1 : Enregistrer le provider Fankai ───────────── // ── Étape 1 : Enregistrer le provider Fankai ─────────────
try { try {
const providersRes = await fetch(`${serverUrl}/media/providers/metadata`, { headers }) const providersRes = await serverFetch(`${serverUrl}/media/providers/metadata`, { headers })
if (providersRes.ok) { if (providersRes.ok) {
const data = await providersRes.json() const data = await providersRes.json() as any
const providers: any[] = data?.MediaContainer?.MetadataAgentProvider ?? [] const providers: any[] = data?.MediaContainer?.MetadataAgentProvider ?? []
let provider = providers.find((p: any) => p.uri === TARGET_AGENT_URI) let provider = providers.find((p: any) => p.uri === TARGET_AGENT_URI)
if (!provider) { if (!provider) {
const r = await fetch(`${serverUrl}/media/providers/metadata?uri=${encodeURIComponent(TARGET_AGENT_URI)}`, { method: 'POST', headers }) const r = await serverFetch(`${serverUrl}/media/providers/metadata?uri=${encodeURIComponent(TARGET_AGENT_URI)}`, { method: 'POST', headers })
if (r.ok) provider = (await r.json())?.MediaContainer?.MetadataAgentProvider?.[0] if (r.ok) provider = ((await r.json()) as any)?.MediaContainer?.MetadataAgentProvider?.[0]
} }
if (provider) { if (provider) {
agentIdentifier = provider.identifier ?? agentIdentifier agentIdentifier = provider.identifier ?? agentIdentifier
steps.push({ step: 'provider', ok: true, message: `Provider enregistré (${agentIdentifier})` }) steps.push({ step: 'provider', ok: true, message: `Provider enregistré (${agentIdentifier})` })
} }
const groupsRes = await fetch(`${serverUrl}/media/providers/metadata/group`, { headers }) const groupsRes = await serverFetch(`${serverUrl}/media/providers/metadata/group`, { headers })
if (groupsRes.ok) { if (groupsRes.ok) {
const gdata = await groupsRes.json() const gdata = await groupsRes.json() as any
const groups: any[] = gdata?.MediaContainer?.MetadataAgentProviderGroup ?? [] const groups: any[] = gdata?.MediaContainer?.MetadataAgentProviderGroup ?? []
let group = groups.find((g: any) => g.primaryIdentifier === agentIdentifier) let group = groups.find((g: any) => g.primaryIdentifier === agentIdentifier)
if (!group) { if (!group) {
const r = await fetch(`${serverUrl}/media/providers/metadata/group?title=Fankai&primaryIdentifier=${encodeURIComponent(agentIdentifier)}`, { method: 'POST', headers }) const r = await serverFetch(
if (r.ok) group = (await r.json())?.MediaContainer?.MetadataAgentProviderGroup?.[0] `${serverUrl}/media/providers/metadata/group?title=Fankai&primaryIdentifier=${encodeURIComponent(agentIdentifier)}`,
{ method: 'POST', headers },
)
if (r.ok) group = ((await r.json()) as any)?.MediaContainer?.MetadataAgentProviderGroup?.[0]
} }
if (group) { if (group) {
groupId = String(group.id ?? '') groupId = String(group.id ?? '')
@@ -142,18 +160,29 @@ router.post('/plex/setup', requireAuth, async (req, res) => {
agentSetupOk = true agentSetupOk = true
} }
} }
} else {
const status = providersRes.status
steps.push({ step: 'agent', ok: false, message: `Agent non configuré (HTTP ${status} — Plex < 1.43 ?) — setup manuel requis` })
logger.warn('plex', `Setup agent Fankai — HTTP ${status} depuis ${serverUrl}`)
} }
} catch (err) { } catch (err) {
const msg = err instanceof Error ? err.message : 'Erreur' const msg = err instanceof Error ? err.message : 'Erreur'
steps.push({ step: 'agent', ok: false, message: `Agent non configuré (Plex < 1.43 ?) — setup manuel requis` }) steps.push({ step: 'agent', ok: false, message: `Agent non configuré — setup manuel requis` })
logger.warn('plex', `Setup agent Fankai échoué : ${msg}`) logger.warn('plex', `Setup agent Fankai échoué : ${msg}`)
} }
// ── Étape 2 : Créer la bibliothèque ────────────────────── // ── Étape 2 : Créer la bibliothèque ──────────────────────
try { try {
const params = new URLSearchParams({ type: 'show', name: libraryName, agent: agentIdentifier, scanner: 'Plex TV Series', language: 'fr-FR', location: libraryPath }) const params = new URLSearchParams({
type : 'show',
name : libraryName,
agent : agentIdentifier,
scanner : 'Plex TV Series',
language: 'fr-FR',
location: libraryPath,
})
if (groupId) params.set('metadataAgentProviderGroupId', groupId) if (groupId) params.set('metadataAgentProviderGroupId', groupId)
const libRes = await fetch(`${serverUrl}/library/sections?${params}`, { method: 'POST', headers }) const libRes = await serverFetch(`${serverUrl}/library/sections?${params}`, { method: 'POST', headers })
if (!libRes.ok) throw new Error(`HTTP ${libRes.status}: ${await libRes.text()}`) if (!libRes.ok) throw new Error(`HTTP ${libRes.status}: ${await libRes.text()}`)
steps.push({ step: 'library', ok: true, message: `Bibliothèque "${libraryName}" créée` }) steps.push({ step: 'library', ok: true, message: `Bibliothèque "${libraryName}" créée` })
logger.info('plex', `Bibliothèque "${libraryName}" créée sur ${serverUrl}`) logger.info('plex', `Bibliothèque "${libraryName}" créée sur ${serverUrl}`)
@@ -166,4 +195,4 @@ router.post('/plex/setup', requireAuth, async (req, res) => {
} }
}) })
export default router export default router
+1 -1
View File
@@ -1 +1 @@
3.11.0 3.11.2