File size: 4,650 Bytes
92a83a5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | const fs = require('fs')
const path = require('path')
const LIB_PATH = path.join(__dirname, '..', 'config', 'patternLibrary.json')
let _cache = null
function load() {
if (_cache) return _cache
try {
_cache = JSON.parse(fs.readFileSync(LIB_PATH, 'utf-8'))
return _cache
} catch {
_cache = { version: 1, patterns: [] }
return _cache
}
}
function save() {
if (!_cache) return
fs.writeFileSync(LIB_PATH, JSON.stringify(_cache, null, 2), 'utf-8')
}
function matchByMediaType(mediaType) {
const lib = load()
return lib.patterns.filter(p => p.mediaType.includes(mediaType) && p.type !== 'search')
}
function matchBySite(baseUrl) {
const lib = load()
const domain = new URL(baseUrl).hostname.replace(/^www\./, '')
return lib.patterns.filter(p =>
p.sites.some(s => domain.includes(s.replace(/^www\./, '')))
)
}
function matchByExtractionPattern(html, limit = 3) {
const lib = load()
const hints = []
for (const p of lib.patterns) {
const method = p.extraction?.method || ''
const selector = p.extraction?.selector || ''
if (selector && html.includes(selector)) {
hints.push({ patternId: p.id, matchType: 'selector', confidence: 'high', pattern: p })
}
if (method === 'iframe_regex' && /video\[\d+\]/.test(html)) {
hints.push({ patternId: p.id, matchType: 'regex', confidence: 'high', pattern: p })
}
if (method === 'xorscrape' && html.includes('data-server')) {
hints.push({ patternId: p.id, matchType: 'data_attr', confidence: 'medium', pattern: p })
}
if (method === 'dooplayer_options' && html.includes('dooplay_player_option')) {
hints.push({ patternId: p.id, matchType: 'class', confidence: 'high', pattern: p })
}
if (method === 'dooplayer_ajax' && html.includes('dooplayer')) {
hints.push({ patternId: p.id, matchType: 'keyword', confidence: 'medium', pattern: p })
}
}
return hints.slice(0, limit)
}
function learnFromFix(providerName, baseUrl, mediaType, urlPattern, extractionMethod, htmlSample) {
const lib = load()
const domain = new URL(baseUrl).hostname
// Check if this site already has this pattern
const existing = lib.patterns.find(p =>
p.sites.some(s => domain.includes(s.replace(/^www\./, ''))) &&
p.urlTmpl === urlPattern
)
if (existing) {
existing.successCount = (existing.successCount || 1) + 1
if (!existing.sites.includes(domain)) existing.sites.push(domain)
save()
return { action: 'updated', pattern: existing }
}
// New pattern: extract as much as possible
const newPattern = {
id: `${mediaType}-${Date.now()}`,
urlTmpl: urlPattern,
mediaType: [mediaType],
slugSource: 'unknown',
extraction: {
method: extractionMethod || 'unknown',
step: 'auto_learned',
hint: 'Pattern auto-learned from successful repair',
},
sites: [domain],
successCount: 1,
autoLearned: true,
learnedAt: new Date().toISOString(),
}
lib.patterns.push(newPattern)
save()
return { action: 'added', pattern: newPattern }
}
function getDirectPatterns(mediaType) {
return matchByMediaType(mediaType)
}
function getDirectFromTitle(mediaType) {
const lib = load()
return lib.patterns.filter(p =>
p.mediaType.includes(mediaType) &&
p.type !== 'search' &&
(p.slugSource === 'title' || !p.slugSource)
)
}
function getSearchPatterns(mediaType) {
const lib = load()
return lib.patterns.filter(p => p.mediaType.includes(mediaType) && p.type === 'search')
}
function getAllForProvider(baseUrl, mediaType) {
const bySite = matchBySite(baseUrl)
const byType = matchByMediaType(mediaType)
const merged = [...bySite, ...byType]
const seen = new Set()
return merged.filter(p => {
if (seen.has(p.id)) return false
seen.add(p.id)
return true
})
}
function fillUrlTemplate(tmpl, vars) {
// vars: { slug, id, season, episode, query, year }
let url = tmpl
.replace(/{slug}/g, vars.slug || '')
.replace(/{id}/g, String(vars.id || ''))
.replace(/{animeId}/g, String(vars.animeId || vars.id || ''))
.replace(/{season}/g, String(vars.season || ''))
.replace(/{episode}/g, String(vars.episode || ''))
.replace(/{groupSlug}/g, vars.groupSlug || '')
.replace(/{query}/g, encodeURIComponent(vars.query || ''))
.replace(/{title}/g, encodeURIComponent(vars.title || ''))
.replace(/{year}/g, String(vars.year || ''))
return url
}
module.exports = {
load, save,
matchByMediaType,
matchBySite,
matchByExtractionPattern,
learnFromFix,
getDirectPatterns,
getDirectFromTitle,
getSearchPatterns,
getAllForProvider,
fillUrlTemplate,
}
|