Update ashi/ashi.js

This commit is contained in:
aka paul
2026-03-14 21:04:25 +00:00
parent b856529fce
commit 71e8747998
+190 -40
View File
@@ -4,8 +4,52 @@
// //
// //
const DENO_PROXY_PREFIX = "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=";
const ANIKAI_HOME_TITLE_REGEX = /<title>Home - AnimeKai - Watch Free Anime Online, Stream Subbed &amp; Dubbed Anime in HD<\/title>/i;
const ANIKAI_CHECK_TIMEOUT_MS = 900;
let animekaiBlockCheckPromise = null;
function proxyUrl(url) {
return DENO_PROXY_PREFIX + encodeURIComponent(url);
}
async function isAnimekaiBlockedForUser() {
if (!animekaiBlockCheckPromise) {
animekaiBlockCheckPromise = (async () => {
let timeoutId;
try {
const htmlText = await Promise.race([
fetchv2("https://anikai.to/home").then(response => response.text()),
new Promise(resolve => {
timeoutId = setTimeout(() => resolve(""), ANIKAI_CHECK_TIMEOUT_MS);
})
]);
if (timeoutId) {
clearTimeout(timeoutId);
}
return !ANIKAI_HOME_TITLE_REGEX.test(htmlText);
} catch (error) {
if (timeoutId) {
clearTimeout(timeoutId);
}
console.error("Animekai accessibility check failed:" + error);
return true;
}
})();
}
return animekaiBlockCheckPromise;
}
async function searchResults(query) { async function searchResults(query) {
const encodeQuery = keyword => encodeURIComponent(keyword); const encodeQuery = keyword => encodeURIComponent(keyword);
const MIN_RESULTS_FAST_RETURN = 8;
const HIGH_CONFIDENCE_SCORE = 900;
const queryNormalized = query.toLowerCase().trim();
const queryTokens = queryNormalized.split(/\s+/).filter(Boolean);
const isSpecificQuery = queryTokens.length <= 2 && queryNormalized.length >= 5;
const decodeHtmlEntities = (str) => { const decodeHtmlEntities = (str) => {
if (!str) return str; if (!str) return str;
@@ -59,6 +103,14 @@ async function searchResults(query) {
if (!isStopword) significantMatches++; if (!isStopword) significantMatches++;
} }
else if (qToken.length >= 4 && tToken.length >= 4) { else if (qToken.length >= 4 && tToken.length >= 4) {
if (Math.abs(qToken.length - tToken.length) > 2) {
return;
}
if (qToken[0] !== tToken[0]) {
return;
}
const dist = levenshteinDistance(qToken, tToken); const dist = levenshteinDistance(qToken, tToken);
const maxLen = Math.max(qToken.length, tToken.length); const maxLen = Math.max(qToken.length, tToken.length);
const similarity = 1 - (dist / maxLen); const similarity = 1 - (dist / maxLen);
@@ -149,6 +201,7 @@ async function searchResults(query) {
const extractHrefRegex = /href="([^"]*)"/; const extractHrefRegex = /href="([^"]*)"/;
const extractImageRegex = /data-src="([^"]*)"/; const extractImageRegex = /data-src="([^"]*)"/;
const extractTitleRegex = /title="([^"]*)"/; const extractTitleRegex = /title="([^"]*)"/;
let useProxy = true;
const extractResultsFromHTML = (htmlText) => { const extractResultsFromHTML = (htmlText) => {
const results = []; const results = [];
@@ -170,7 +223,7 @@ async function searchResults(query) {
if (fullHref && imageSrc && cleanTitle) { if (fullHref && imageSrc && cleanTitle) {
results.push({ results.push({
href: `Animekai:${fullHref}`, href: `Animekai:${fullHref}`,
image: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(imageSrc), image: useProxy ? proxyUrl(imageSrc) : imageSrc,
title: cleanTitle title: cleanTitle
}); });
} }
@@ -181,21 +234,33 @@ async function searchResults(query) {
try { try {
const encodedQuery = encodeQuery(query); const encodedQuery = encodeQuery(query);
const urls = [ useProxy = await isAnimekaiBlockedForUser();
`${searchBaseUrl}${encodedQuery}`, const fetchPages = async (pages) => {
`${searchBaseUrl}${encodedQuery}&page=2`, const urls = pages.map(page =>
`${searchBaseUrl}${encodedQuery}&page=3` page === 1
]; ? `${searchBaseUrl}${encodedQuery}`
: `${searchBaseUrl}${encodedQuery}&page=${page}`
);
const responses = await Promise.all(urls.map(url => fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url)))); const responses = await Promise.all(urls.map(url => fetchv2(useProxy ? proxyUrl(url) : url)));
const htmlTexts = await Promise.all(responses.map(res => res.text())); const htmlTexts = await Promise.all(responses.map(res => res.text()));
const allResults = []; const allResults = [];
htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html))); htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html)));
return allResults; return allResults;
};
const page1Results = await fetchPages([1]);
return {
page1Results,
fetchRemaining: () => fetchPages([2, 3])
};
} catch (error) { } catch (error) {
console.error("Animekai search error:" + error); console.error("Animekai search error:" + error);
return []; return {
page1Results: [],
fetchRemaining: async () => []
};
} }
}; };
@@ -228,41 +293,123 @@ async function searchResults(query) {
try { try {
const encodedQuery = encodeQuery(query); const encodedQuery = encodeQuery(query);
const urls = [ const fetchPages = async (pages) => {
`${searchBaseUrl}${encodedQuery}`, const urls = pages.map(page =>
`${searchBaseUrl}${encodedQuery}&page=2`, page === 1
`${searchBaseUrl}${encodedQuery}&page=3` ? `${searchBaseUrl}${encodedQuery}`
]; : `${searchBaseUrl}${encodedQuery}&page=${page}`
);
const responses = await Promise.all(urls.map(url => fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url)))); const responses = await Promise.all(urls.map(url => fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url))));
const htmlTexts = await Promise.all(responses.map(res => res.text())); const htmlTexts = await Promise.all(responses.map(res => res.text()));
const allResults = []; const allResults = [];
htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html))); htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html)));
return allResults; return allResults;
};
const page1Results = await fetchPages([1]);
return {
page1Results,
fetchRemaining: () => fetchPages([2, 3])
};
} catch (error) { } catch (error) {
console.error("1Movies search error:" + error); console.error("1Movies search error:" + error);
return []; return {
page1Results: [],
fetchRemaining: async () => []
};
} }
}; };
try { try {
const [animekaiResults, oneMoviesResults] = await Promise.all([ const animekaiPromise = animekaiSearch();
animekaiSearch(),
oneMoviesSearch() const animekaiData = await animekaiPromise;
const dedupeResults = (results) => {
const seen = new Set();
return results.filter(item => {
const key = `${item.href}|${item.title}`;
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
};
const scoreCache = new Map();
const rankResults = (results) => {
const scoredResults = results.map(r => {
const cacheKey = r.title;
const cached = scoreCache.get(cacheKey);
const score = cached !== undefined ? cached : fuzzyMatch(query, r.title);
if (cached === undefined) {
scoreCache.set(cacheKey, score);
}
return {
...r,
score
};
});
const sorted = scoredResults
.filter(r => r.score > 50)
.sort((a, b) => b.score - a.score);
return {
topScore: sorted[0]?.score || 0,
items: sorted.map(({ score, ...rest }) => rest)
};
};
const shouldFastReturn = (resultCount, topScore) => {
if (resultCount >= MIN_RESULTS_FAST_RETURN) {
return true;
}
return isSpecificQuery && resultCount >= 1 && topScore >= HIGH_CONFIDENCE_SCORE;
};
let mergedResults = dedupeResults([...animekaiData.page1Results]);
let rankedResults = rankResults(mergedResults);
let filteredResults = rankedResults.items;
if (shouldFastReturn(filteredResults.length, rankedResults.topScore)) {
return JSON.stringify(filteredResults);
}
const oneMoviesData = await oneMoviesSearch();
mergedResults = dedupeResults([
...mergedResults,
...oneMoviesData.page1Results
]); ]);
const mergedResults = [...animekaiResults, ...oneMoviesResults]; rankedResults = rankResults(mergedResults);
filteredResults = rankedResults.items;
const scoredResults = mergedResults.map(r => ({ if (shouldFastReturn(filteredResults.length, rankedResults.topScore)) {
...r, return JSON.stringify(filteredResults);
score: fuzzyMatch(query, r.title) }
}));
const filteredResults = scoredResults const [animekaiExtra, oneMoviesExtra] = await Promise.all([
.filter(r => r.score > 50) animekaiData.fetchRemaining(),
.sort((a, b) => b.score - a.score) oneMoviesData.fetchRemaining()
.map(({ score, ...rest }) => rest); ]);
mergedResults = dedupeResults([
...mergedResults,
...animekaiExtra,
...oneMoviesExtra
]);
rankedResults = rankResults(mergedResults);
filteredResults = rankedResults.items;
return JSON.stringify(filteredResults.length > 0 ? filteredResults : [{ return JSON.stringify(filteredResults.length > 0 ? filteredResults : [{
href: "", href: "",
@@ -284,7 +431,8 @@ async function extractDetails(url) {
const actualUrl = url.replace("Animekai:", "").trim(); const actualUrl = url.replace("Animekai:", "").trim();
try { try {
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl)); const useProxy = await isAnimekaiBlockedForUser();
const response = await fetchv2(useProxy ? proxyUrl(actualUrl) : actualUrl);
const htmlText = await response.text(); const htmlText = await response.text();
const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1]; const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
@@ -332,7 +480,8 @@ async function extractEpisodes(url) {
try { try {
if (url.startsWith("Animekai:")) { if (url.startsWith("Animekai:")) {
const actualUrl = url.replace("Animekai:", "").trim(); const actualUrl = url.replace("Animekai:", "").trim();
const htmlText = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl))).text(); const useProxy = await isAnimekaiBlockedForUser();
const htmlText = await (await fetchv2(useProxy ? proxyUrl(actualUrl) : actualUrl)).text();
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1]; const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]); if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);
@@ -341,7 +490,7 @@ async function extractEpisodes(url) {
const token = tokenData.result; const token = tokenData.result;
const episodeListUrl = `https://anikai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`; const episodeListUrl = `https://anikai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
const episodeListData = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(episodeListUrl))).json(); const episodeListData = await (await fetchv2(useProxy ? proxyUrl(episodeListUrl) : episodeListUrl)).json();
const cleanedHtml = cleanJsonHtml(episodeListData.result); const cleanedHtml = cleanJsonHtml(episodeListData.result);
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g; const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
@@ -398,6 +547,7 @@ async function extractStreamUrl(url) {
if (source === "Animekai") { if (source === "Animekai") {
try { try {
const useProxy = await isAnimekaiBlockedForUser();
const tokenMatch = actualUrl.match(/token=([^&]+)/); const tokenMatch = actualUrl.match(/token=([^&]+)/);
if (tokenMatch && tokenMatch[1]) { if (tokenMatch && tokenMatch[1]) {
const rawToken = tokenMatch[1]; const rawToken = tokenMatch[1];
@@ -407,7 +557,7 @@ async function extractStreamUrl(url) {
actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`); actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
} }
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl)); const response = await fetchv2(useProxy ? proxyUrl(actualUrl) : actualUrl);
const text = await response.text(); const text = await response.text();
const cleanedHtml = cleanJsonHtml(text); const cleanedHtml = cleanJsonHtml(text);
const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/; const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
@@ -454,7 +604,7 @@ async function extractStreamUrl(url) {
const streamResponses = await Promise.all( const streamResponses = await Promise.all(
streamUrls.map(async ({ type, url }) => { streamUrls.map(async ({ type, url }) => {
try { try {
const res = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url)); const res = await fetchv2(useProxy ? proxyUrl(url) : url);
const json = await res.json(); const json = await res.json();
return { return {
type: type, type: type,