|
1 | 1 | import myzod from 'myzod' |
2 | | -import { translateMany, translateText } from '../modules/googleTranslate.js' |
3 | | -import { |
4 | | - decodeTrack, |
5 | | - http1makeRequest, |
6 | | - logger, |
7 | | - sendErrorResponse |
8 | | -} from '../utils.js' |
| 2 | +import { decodeTrack, logger, sendErrorResponse } from '../utils.js' |
9 | 3 |
|
10 | 4 | const meaningSchema = myzod.object({ |
11 | 5 | encodedTrack: myzod.string(), |
12 | 6 | lang: myzod.string().optional() |
13 | 7 | }) |
14 | 8 |
|
15 | | -const decodeHtml = (text) => { |
16 | | - if (!text) return text |
17 | | - return text |
18 | | - .replace(/&/g, '&') |
19 | | - .replace(/"/g, '"') |
20 | | - .replace(/'/g, "'") |
21 | | - .replace(/'/g, "'") |
22 | | - .replace(/</g, '<') |
23 | | - .replace(/>/g, '>') |
24 | | -} |
25 | | - |
26 | | -const extractMeta = (html, property) => { |
27 | | - const re1 = new RegExp( |
28 | | - `<meta[^>]+property=[\"']${property}[\"'][^>]+content=[\"']([^\"']+)[\"'][^>]*>`, |
29 | | - 'i' |
30 | | - ) |
31 | | - const re2 = new RegExp( |
32 | | - `<meta[^>]+content=[\"']([^\"']+)[^>]+property=[\"']${property}[\"'][^>]*>`, |
33 | | - 'i' |
34 | | - ) |
35 | | - const match = html.match(re1) || html.match(re2) |
36 | | - return match ? decodeHtml(match[1]) : null |
37 | | -} |
38 | | - |
39 | | -const extractOmqLyric = (html) => { |
40 | | - const match = html.match(/_omq\.push\(\['ui\/lyric',\s*({[\s\S]*?})\s*,/i) |
41 | | - if (!match) return null |
42 | | - try { |
43 | | - return JSON.parse(match[1]) |
44 | | - } catch { |
45 | | - return null |
46 | | - } |
47 | | -} |
48 | | - |
49 | | -const extractOmqMeaning = (html) => { |
50 | | - const match = html.match( |
51 | | - /_omq\.push\(\['ui\/lyric',\s*({[\s\S]*?})\s*,\s*({[\s\S]*?})\s*,/i |
52 | | - ) |
53 | | - if (!match) return null |
54 | | - try { |
55 | | - return JSON.parse(match[2]) |
56 | | - } catch { |
57 | | - return null |
58 | | - } |
59 | | -} |
60 | | - |
61 | | -const extractMeaning = (html) => { |
62 | | - const match = html.match(/<div class="lyric-meaning[^>]*">([\s\S]*?)<\/div>/i) |
63 | | - if (!match) return { title: null, body: [] } |
64 | | - let block = match[1] |
65 | | - const titleMatch = block.match(/<h3[^>]*>([\s\S]*?)<\/h3>/i) |
66 | | - const title = titleMatch ? decodeHtml(titleMatch[1].replace(/<[^>]+>/g, '')) : null |
67 | | - block = block.replace(/<h3[^>]*>[\s\S]*?<\/h3>/i, '') |
68 | | - const paragraphs = [] |
69 | | - const pRegex = /<p[^>]*>([\s\S]*?)<\/p>/gi |
70 | | - let pMatch |
71 | | - while ((pMatch = pRegex.exec(block))) { |
72 | | - let text = pMatch[1] |
73 | | - text = text.replace(/<br\s*\/?>/gi, '\n') |
74 | | - text = text.replace(/<[^>]+>/g, '') |
75 | | - text = decodeHtml(text) |
76 | | - const lines = text |
77 | | - .split('\n') |
78 | | - .map((line) => line.trim()) |
79 | | - .filter(Boolean) |
80 | | - if (lines.length) paragraphs.push(lines.join(' ')) |
81 | | - } |
82 | | - if (!paragraphs.length) { |
83 | | - let text = block.replace(/<br\s*\/?>/gi, '\n') |
84 | | - text = text.replace(/<[^>]+>/g, '') |
85 | | - text = decodeHtml(text) |
86 | | - const lines = text |
87 | | - .split('\n') |
88 | | - .map((line) => line.trim()) |
89 | | - .filter(Boolean) |
90 | | - if (lines.length) paragraphs.push(lines.join(' ')) |
91 | | - } |
92 | | - return { title, body: paragraphs } |
93 | | -} |
94 | | - |
95 | 9 | async function handler(nodelink, req, res, sendResponse, parsedUrl) { |
96 | 10 | const result = meaningSchema.try({ |
97 | 11 | encodedTrack: parsedUrl.searchParams.get('encodedTrack'), |
@@ -130,106 +44,56 @@ async function handler(nodelink, req, res, sendResponse, parsedUrl) { |
130 | 44 | } |
131 | 45 |
|
132 | 46 | try { |
133 | | - let trackInfo = decodedTrack.info |
134 | | - if (nodelink.sources?.resolve && decodedTrack.info?.uri) { |
135 | | - const resolved = await nodelink.sources.resolve(decodedTrack.info.uri) |
136 | | - if (resolved.loadType !== 'track') { |
137 | | - return sendResponse( |
138 | | - req, |
139 | | - res, |
140 | | - { loadType: 'empty', data: {} }, |
141 | | - 200 |
142 | | - ) |
143 | | - } |
144 | | - trackInfo = resolved.data?.info || resolved.data |
145 | | - } |
146 | | - |
147 | | - if (!trackInfo || trackInfo.sourceName !== 'letrasmus') { |
148 | | - return sendResponse( |
| 47 | + let delegated = false |
| 48 | + if (nodelink.sourceWorkerManager) { |
| 49 | + delegated = nodelink.sourceWorkerManager.delegate( |
149 | 50 | req, |
150 | 51 | res, |
151 | | - { loadType: 'empty', data: {} }, |
152 | | - 200 |
| 52 | + 'loadMeaning', |
| 53 | + { |
| 54 | + decodedTrackInfo: decodedTrack.info, |
| 55 | + language: targetLang |
| 56 | + } |
153 | 57 | ) |
154 | 58 | } |
155 | 59 |
|
156 | | - const baseUrl = trackInfo.uri?.endsWith('/') |
157 | | - ? trackInfo.uri |
158 | | - : `${trackInfo.uri}/` |
159 | | - const meaningUrl = `${baseUrl}significado.html` |
160 | | - const { body, statusCode, error } = await http1makeRequest(meaningUrl, { |
161 | | - method: 'GET' |
162 | | - }).catch((e) => ({ error: e })) |
163 | | - |
164 | | - if (error || statusCode !== 200 || !body) { |
165 | | - return sendResponse( |
| 60 | + if (delegated) return |
| 61 | + |
| 62 | + let meaning |
| 63 | + if (nodelink.workerManager) { |
| 64 | + const worker = nodelink.workerManager.getBestWorker() |
| 65 | + meaning = await nodelink.workerManager.execute(worker, 'loadMeaning', { |
| 66 | + decodedTrackInfo: decodedTrack.info, |
| 67 | + language: targetLang |
| 68 | + }) |
| 69 | + } else if (nodelink.meanings?.loadMeaning) { |
| 70 | + meaning = await nodelink.meanings.loadMeaning(decodedTrack, targetLang) |
| 71 | + } else { |
| 72 | + logger('error', 'Meaning', 'Meaning sources are not available.') |
| 73 | + return sendErrorResponse( |
166 | 74 | req, |
167 | 75 | res, |
168 | | - { loadType: 'empty', data: {} }, |
169 | | - 200 |
| 76 | + 503, |
| 77 | + 'meaning sources unavailable', |
| 78 | + 'Meaning sources are not available.', |
| 79 | + parsedUrl.pathname, |
| 80 | + true |
170 | 81 | ) |
171 | 82 | } |
172 | 83 |
|
173 | | - const meaning = extractMeaning(body) |
174 | | - const omq = extractOmqLyric(body) |
175 | | - const meaningMeta = extractOmqMeaning(body) |
176 | | - const ogImage = extractMeta(body, 'og:image') |
177 | | - const ogTitle = extractMeta(body, 'og:title') |
178 | | - const ogDescription = extractMeta(body, 'og:description') |
179 | | - |
180 | | - let translated = null |
181 | | - if (targetLang) { |
182 | | - const sourceLang = 'pt' |
183 | | - try { |
184 | | - const translatedParagraphs = await translateMany( |
185 | | - meaning.body, |
186 | | - sourceLang, |
187 | | - targetLang |
188 | | - ) |
189 | | - const translatedTitle = meaning.title |
190 | | - ? await translateText(meaning.title, sourceLang, targetLang) |
191 | | - : null |
192 | | - const translatedDescription = ogDescription |
193 | | - ? await translateText(ogDescription, sourceLang, targetLang) |
194 | | - : null |
195 | | - translated = { |
196 | | - language: { |
197 | | - source: sourceLang, |
198 | | - target: targetLang |
199 | | - }, |
200 | | - title: translatedTitle?.translation || null, |
201 | | - description: translatedDescription?.translation || null, |
202 | | - paragraphs: translatedParagraphs |
203 | | - } |
204 | | - } catch (e) { |
205 | | - logger('warn', 'Meaning', `Translate failed: ${e.message}`) |
206 | | - } |
| 84 | + if (meaning?.loadType === 'error') { |
| 85 | + return sendErrorResponse( |
| 86 | + req, |
| 87 | + res, |
| 88 | + 500, |
| 89 | + 'failed to load meaning', |
| 90 | + meaning.data?.message || 'Failed to load meaning', |
| 91 | + parsedUrl.pathname, |
| 92 | + true |
| 93 | + ) |
207 | 94 | } |
208 | 95 |
|
209 | | - return sendResponse(req, res, { |
210 | | - loadType: meaning.body.length ? 'meaning' : 'empty', |
211 | | - data: { |
212 | | - title: meaning.title || ogTitle || null, |
213 | | - description: ogDescription || null, |
214 | | - paragraphs: meaning.body, |
215 | | - translation: translated, |
216 | | - url: meaningUrl, |
217 | | - meaningMeta: { |
218 | | - id: meaningMeta?.ID || null, |
219 | | - localeId: meaningMeta?.LocaleID || null, |
220 | | - origin: meaningMeta?.Origin || null, |
221 | | - submittedBy: null, |
222 | | - reviewedBy: null |
223 | | - }, |
224 | | - song: { |
225 | | - title: omq?.Name || trackInfo.title || null, |
226 | | - artist: omq?.Artist || trackInfo.author || null, |
227 | | - youtubeId: omq?.YoutubeID || null, |
228 | | - letrasId: omq?.ID || null, |
229 | | - artworkUrl: ogImage || trackInfo.artworkUrl || null |
230 | | - } |
231 | | - } |
232 | | - }, 200) |
| 96 | + return sendResponse(req, res, meaning, 200) |
233 | 97 | } catch (err) { |
234 | 98 | logger('error', 'Meaning', `Failed to load meaning: ${err.message}`) |
235 | 99 | return sendErrorResponse( |
|
0 commit comments