Update ashi/ashi.js
This commit is contained in:
+190
-40
@@ -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 & 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,
|
||||||
|
|||||||
Reference in New Issue
Block a user