+
⚠️ IMPORTANT
+
Any app meeting the following criteria is free to use my modules:
+
+- No paywall, subscription, or payment required for my modules
+ (you may charge for other parts of your app).
+- No advertisements during the usage of my modules.
+- Open source: your app’s source code must be publicly available.
+
+
All the above terms are mandatory unless given permission by me.
+In short: no commercial use and transparency is required.
+
+
+
+
🚫 CAUTION
+
Do not pay to use these modules — if someone is charging you, it's a scam!
+
Neither should you bear watching ads to use these modules. Please report apps that do this forcibly!
+
+
+
+
+[](https://discord.com/users/1072985316916469870)
+
+
+
+[](https://ko-fi.com/50n50)
+
+
+
+
diff --git a/.tmp.driveupload/3462 b/.tmp.driveupload/3462
new file mode 100644
index 0000000..41bffab
--- /dev/null
+++ b/.tmp.driveupload/3462
@@ -0,0 +1,19 @@
+{
+ "sourceName": "Ashi (あし) - Literally Everything",
+ "iconUrl": "https://files.catbox.moe/y8v199.png",
+ "author": {
+ "name": "50/50",
+ "icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
+ },
+ "version": "1.1.1",
+ "language": "English",
+ "streamType": "HLS",
+ "quality": "1080p",
+ "baseUrl": "https://animekai.to/",
+ "searchBaseUrl": "https://animekai.to/",
+ "scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/ashi/ashi.js",
+ "type": "anime/movies/shows",
+ "asyncJS": true,
+ "softsub": true,
+ "downloadSupport": true
+}
diff --git a/.tmp.driveupload/3464 b/.tmp.driveupload/3464
new file mode 100644
index 0000000..69436d5
--- /dev/null
+++ b/.tmp.driveupload/3464
@@ -0,0 +1,761 @@
+//
+//
+// 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 = (/