// // // Main functions // // async function searchResults(query) { const encodeQuery = keyword => encodeURIComponent(keyword); const decodeHtmlEntities = (str) => { if (!str) return str; return str.replace(/(\d+);/g, (match, dec) => String.fromCharCode(dec)) .replace(/"/g, '"') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>'); }; const fuzzyMatch = (query, title) => { const q = query.toLowerCase().trim(); const t = title.toLowerCase().trim(); if (t === q) return 1000; if (t.startsWith(q + ' ') || t.startsWith(q + ':') || t.startsWith(q + '-')) return 950; const wordBoundaryRegex = new RegExp(`\\b${q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`); if (wordBoundaryRegex.test(t)) return 900; const qTokens = q.split(/\s+/).filter(token => token.length > 0); const tTokens = t.split(/[\s\-:]+/).filter(token => token.length > 0); const stopwords = new Set(['the', 'a', 'an', 'and', 'or', 'of', 'in', 'on', 'at', 'to', 'for', 'with']); let score = 0; let exactMatches = 0; let partialMatches = 0; let significantMatches = 0; qTokens.forEach(qToken => { const isStopword = stopwords.has(qToken); let bestMatch = 0; let hasExactMatch = false; tTokens.forEach(tToken => { let matchScore = 0; if (tToken === qToken) { matchScore = isStopword ? 25 : 120; hasExactMatch = true; if (!isStopword) significantMatches++; } else if (qToken.includes(tToken) && tToken.length >= 3 && qToken.length <= tToken.length + 2) { matchScore = isStopword ? 8 : 40; if (!isStopword) significantMatches++; } else if (tToken.startsWith(qToken) && qToken.length >= 3) { matchScore = isStopword ? 12 : 70; if (!isStopword) significantMatches++; } else if (qToken.length >= 4 && tToken.length >= 4) { const dist = levenshteinDistance(qToken, tToken); const maxLen = Math.max(qToken.length, tToken.length); const similarity = 1 - (dist / maxLen); if (similarity > 0.8) { matchScore = Math.floor(similarity * 60); if (!isStopword) significantMatches++; } } bestMatch = Math.max(bestMatch, matchScore); }); if (bestMatch > 0) { score += bestMatch; if (hasExactMatch) exactMatches++; else partialMatches++; } }); const significantTokens = qTokens.filter(t => !stopwords.has(t)).length; const requiredMatches = Math.max(1, Math.ceil(significantTokens * 0.8)); if (significantMatches < requiredMatches) { return 0; } if (exactMatches + partialMatches >= qTokens.length) { score += 80; } score += exactMatches * 20; const extraWords = tTokens.length - qTokens.length; if (extraWords > 2) { score -= (extraWords - 2) * 25; } let orderBonus = 0; for (let i = 0; i < qTokens.length - 1; i++) { const currentTokenIndex = tTokens.findIndex(t => t.includes(qTokens[i])); const nextTokenIndex = tTokens.findIndex(t => t.includes(qTokens[i + 1])); if (currentTokenIndex !== -1 && nextTokenIndex !== -1 && currentTokenIndex < nextTokenIndex) { orderBonus += 15; } } score += orderBonus; return Math.max(0, score); }; const levenshteinDistance = (a, b) => { const matrix = []; for (let i = 0; i <= b.length; i++) { matrix[i] = [i]; } for (let j = 0; j <= a.length; j++) { matrix[0][j] = j; } for (let i = 1; i <= b.length; i++) { for (let j = 1; j <= a.length; j++) { if (b.charAt(i - 1) === a.charAt(j - 1)) { matrix[i][j] = matrix[i - 1][j - 1]; } else { matrix[i][j] = Math.min( matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1 ); } } } return matrix[b.length][a.length]; }; const animekaiSearch = async () => { const searchBaseUrl = "https://animekai.to/browser?keyword="; const baseUrl = "https://animekai.to"; const posterHrefRegex = /href="[^"]*" class="poster"/g; const titleRegex = /class="title"[^>]*title="[^"]*"/g; const imageRegex = /data-src="[^"]*"/g; const extractHrefRegex = /href="([^"]*)"/; const extractImageRegex = /data-src="([^"]*)"/; const extractTitleRegex = /title="([^"]*)"/; const extractResultsFromHTML = (htmlText) => { const results = []; const posterMatches = htmlText.match(posterHrefRegex) || []; const titleMatches = htmlText.match(titleRegex) || []; const imageMatches = htmlText.match(imageRegex) || []; const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length); for (let i = 0; i < minLength; i++) { const hrefMatch = posterMatches[i].match(extractHrefRegex); const fullHref = hrefMatch ? (hrefMatch[1].startsWith("http") ? hrefMatch[1] : baseUrl + hrefMatch[1]) : null; const imageMatch = imageMatches[i].match(extractImageRegex); const imageSrc = imageMatch ? imageMatch[1] : null; const titleMatch = titleMatches[i].match(extractTitleRegex); const cleanTitle = titleMatch ? decodeHtmlEntities(titleMatch[1]) : null; if (fullHref && imageSrc && cleanTitle) { results.push({ href: `Animekai:${fullHref}`, image: imageSrc, title: cleanTitle }); } } return results; }; try { const encodedQuery = encodeQuery(query); const urls = [ `${searchBaseUrl}${encodedQuery}`, `${searchBaseUrl}${encodedQuery}&page=2`, `${searchBaseUrl}${encodedQuery}&page=3` ]; const responses = await Promise.all(urls.map(url => fetchv2(url))); const htmlTexts = await Promise.all(responses.map(res => res.text())); const allResults = []; htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html))); return allResults; } catch (error) { console.error("Animekai search error:" + error); return []; } }; const oneMoviesSearch = async () => { const searchBaseUrl = "https://1movies.bz/browser?keyword="; const baseUrl = "https://1movies.bz"; const posterHrefRegex = /href="([^"]*)" class="poster"/g; const titleRegex = /class="title" href="[^"]*">([^<]*) { const results = []; const posterMatches = [...htmlText.matchAll(posterHrefRegex)]; const titleMatches = [...htmlText.matchAll(titleRegex)]; const imageMatches = [...htmlText.matchAll(imageRegex)]; const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length); for (let i = 0; i < minLength; i++) { const href = posterMatches[i][1]; const fullHref = href.startsWith("http") ? href : baseUrl + href; const imageSrc = imageMatches[i][1]; const title = decodeHtmlEntities(titleMatches[i][1]); results.push({ href: fullHref, image: imageSrc, title }); } return results; }; try { const encodedQuery = encodeQuery(query); const urls = [ `${searchBaseUrl}${encodedQuery}`, `${searchBaseUrl}${encodedQuery}&page=2`, `${searchBaseUrl}${encodedQuery}&page=3` ]; const responses = await Promise.all(urls.map(url => fetchv2(url))); const htmlTexts = await Promise.all(responses.map(res => res.text())); const allResults = []; htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html))); return allResults; } catch (error) { console.error("1Movies search error:" + error); return []; } }; try { const [animekaiResults, oneMoviesResults] = await Promise.all([ animekaiSearch(), oneMoviesSearch() ]); const mergedResults = [...animekaiResults, ...oneMoviesResults]; const scoredResults = mergedResults.map(r => ({ ...r, score: fuzzyMatch(query, r.title) })); const filteredResults = scoredResults .filter(r => r.score > 50) .sort((a, b) => b.score - a.score) .map(({ score, ...rest }) => rest); return JSON.stringify(filteredResults.length > 0 ? filteredResults : [{ href: "", image: "", title: "No results found, please refine query." }]); } catch (error) { return JSON.stringify([{ href: "", image: "", title: "Search failed: " + error.message }]); } } async function extractDetails(url) { if (url.startsWith("Animekai:")) { const actualUrl = url.replace("Animekai:", "").trim(); try { const response = await fetchv2(actualUrl); const htmlText = await response.text(); const descriptionMatch = (/