2025-11-01 16:02:56 +00:00
|
|
|
// Settings start
|
|
|
|
|
const preferedQualityOption = "Auto"; // ["Auto", "2160p", "1080p", "720p", "480p"]
|
|
|
|
|
const maxResultsPerResolution = 0;
|
|
|
|
|
const maxSize = 0;
|
|
|
|
|
const cachedOnly = false; // [true, false]
|
|
|
|
|
const removeTrash = true; // [true, false]
|
|
|
|
|
const resultFormat = "all"; // ["all"]
|
|
|
|
|
const debridService = "realdebrid"; // ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink"]
|
2026-03-08 17:33:06 +00:00
|
|
|
const debridApiKey = "";
|
2025-11-01 16:02:56 +00:00
|
|
|
const debridStreamProxyPassword = "";
|
|
|
|
|
const languagesExclude = "";
|
|
|
|
|
const languagesPreferred = "en";
|
|
|
|
|
const removeRanksUnder = -10000000000;
|
|
|
|
|
const allowEnglishInLanguages = false; // [true, false]
|
|
|
|
|
const removeUnknownLanguages = false; // [true, false]
|
|
|
|
|
// Settings end
|
|
|
|
|
|
2026-03-08 17:31:20 +00:00
|
|
|
function btoa(str) {
|
|
|
|
|
if (typeof globalThis !== "undefined" && typeof globalThis.btoa === "function" && globalThis.btoa !== btoa) {
|
|
|
|
|
return globalThis.btoa(str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof Buffer !== "undefined") {
|
|
|
|
|
return Buffer.from(str, "utf8").toString("base64");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
|
let result = "";
|
|
|
|
|
let i = 0;
|
|
|
|
|
|
|
|
|
|
while (i < str.length) {
|
|
|
|
|
const byte1 = str.charCodeAt(i++) & 0xff;
|
|
|
|
|
const hasByte2 = i < str.length;
|
|
|
|
|
const byte2 = hasByte2 ? str.charCodeAt(i++) & 0xff : 0;
|
|
|
|
|
const hasByte3 = i < str.length;
|
|
|
|
|
const byte3 = hasByte3 ? str.charCodeAt(i++) & 0xff : 0;
|
|
|
|
|
|
|
|
|
|
const chunk = (byte1 << 16) | (byte2 << 8) | byte3;
|
|
|
|
|
result += chars[(chunk >> 18) & 63];
|
|
|
|
|
result += chars[(chunk >> 12) & 63];
|
|
|
|
|
result += hasByte2 ? chars[(chunk >> 6) & 63] : "=";
|
|
|
|
|
result += hasByte3 ? chars[chunk & 63] : "=";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 16:02:56 +00:00
|
|
|
async function searchResults(keyword) {
|
|
|
|
|
try {
|
|
|
|
|
const moviesresponse = await fetchv2(
|
|
|
|
|
"https://v3-cinemeta.strem.io/catalog/movie/top/search=" + encodeURIComponent(keyword) + ".json"
|
|
|
|
|
);
|
|
|
|
|
const moviesdata = await moviesresponse.json();
|
|
|
|
|
|
|
|
|
|
const results = moviesdata.metas.map(item => ({
|
|
|
|
|
title: item.name.trim(),
|
|
|
|
|
image: item.poster.trim(),
|
|
|
|
|
href: "Movie: " + (item.id.startsWith("tt") ? item.id : "")
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const showsresponse = await fetchv2(
|
|
|
|
|
"https://v3-cinemeta.strem.io/catalog/series/top/search=" + encodeURIComponent(keyword) + ".json"
|
|
|
|
|
);
|
|
|
|
|
const showsdata = await showsresponse.json();
|
|
|
|
|
|
|
|
|
|
const showResults = showsdata.metas.map(item => ({
|
|
|
|
|
title: item.name.trim(),
|
|
|
|
|
image: item.poster.trim(),
|
|
|
|
|
href: "TV: " + (item.id.startsWith("tt") ? item.id : "")
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
results.push(...showResults);
|
|
|
|
|
|
|
|
|
|
return JSON.stringify(results);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return JSON.stringify([{
|
|
|
|
|
title: "Error",
|
|
|
|
|
image: "Error",
|
|
|
|
|
href: "Error"
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function extractDetails(ID) {
|
|
|
|
|
try {
|
|
|
|
|
let decodedID = decodeURIComponent(ID);
|
|
|
|
|
let actualID = decodedID;
|
|
|
|
|
let type = "movie";
|
|
|
|
|
|
|
|
|
|
if (decodedID.startsWith("Movie: ")) {
|
|
|
|
|
actualID = decodedID.replace("Movie: ", "");
|
|
|
|
|
type = "movie";
|
|
|
|
|
} else if (decodedID.startsWith("TV: ")) {
|
|
|
|
|
actualID = decodedID.replace("TV: ", "");
|
|
|
|
|
type = "series";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const url = "https://v3-cinemeta.strem.io/meta/" + type + "/" + actualID + ".json";
|
|
|
|
|
const response = await fetchv2(url);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
return JSON.stringify([{
|
|
|
|
|
description: data.meta.description || "N/A",
|
|
|
|
|
aliases: "N/A",
|
|
|
|
|
airdate: data.meta.released || "N/A"
|
|
|
|
|
}]);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return JSON.stringify([{
|
|
|
|
|
description: "Error",
|
|
|
|
|
aliases: "Error",
|
|
|
|
|
airdate: "Error"
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function extractEpisodes(ID) {
|
|
|
|
|
let decodedID = decodeURIComponent(ID);
|
|
|
|
|
let actualID = decodedID;
|
|
|
|
|
let type = "movie";
|
|
|
|
|
|
|
|
|
|
if (decodedID.startsWith("Movie: ")) {
|
|
|
|
|
actualID = decodedID.replace("Movie: ", "");
|
|
|
|
|
type = "movie";
|
|
|
|
|
} else if (decodedID.startsWith("TV: ")) {
|
|
|
|
|
actualID = decodedID.replace("TV: ", "");
|
|
|
|
|
type = "series";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (type === "series") {
|
|
|
|
|
const response = await fetchv2("https://v3-cinemeta.strem.io/meta/series/" + actualID + ".json");
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
const videos = data.meta.videos || [];
|
|
|
|
|
|
2025-11-02 21:05:25 +00:00
|
|
|
const shouldAdjust = videos.length > 0 && videos[0].season === 0;
|
|
|
|
|
|
2025-11-01 16:02:56 +00:00
|
|
|
let currentSeason = 0;
|
|
|
|
|
let episodeCounter = 0;
|
|
|
|
|
|
|
|
|
|
for (const video of videos) {
|
2025-11-02 21:05:25 +00:00
|
|
|
const adjustedSeason = shouldAdjust ? video.season + 1 : video.season;
|
2025-11-02 20:54:20 +00:00
|
|
|
if (adjustedSeason !== currentSeason) {
|
|
|
|
|
currentSeason = adjustedSeason;
|
2025-11-01 16:02:56 +00:00
|
|
|
episodeCounter = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
episodeCounter++;
|
|
|
|
|
|
2025-11-02 20:54:20 +00:00
|
|
|
let adjustedId = video.id || "";
|
2025-11-02 21:05:25 +00:00
|
|
|
if (adjustedId && shouldAdjust) {
|
2025-11-02 20:54:20 +00:00
|
|
|
const idParts = adjustedId.split(':');
|
|
|
|
|
if (idParts.length === 3) {
|
|
|
|
|
idParts[1] = String(adjustedSeason);
|
|
|
|
|
adjustedId = idParts.join(':');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 16:02:56 +00:00
|
|
|
results.push({
|
2025-11-02 20:54:20 +00:00
|
|
|
href: "TV: " + adjustedId,
|
2025-11-01 16:02:56 +00:00
|
|
|
number: episodeCounter
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return JSON.stringify(results);
|
|
|
|
|
} else if (type === "movie") {
|
|
|
|
|
return JSON.stringify([{
|
|
|
|
|
href: "Movie: " + (actualID || ""),
|
|
|
|
|
number: 1
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return JSON.stringify([{
|
|
|
|
|
href: "Error",
|
|
|
|
|
number: "Error"
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function extractStreamUrl(ID) {
|
|
|
|
|
let decodedID = decodeURIComponent(ID);
|
|
|
|
|
let actualID = decodedID;
|
|
|
|
|
let type = "movie";
|
|
|
|
|
|
|
|
|
|
if (decodedID.startsWith("Movie: ")) {
|
|
|
|
|
actualID = decodedID.replace("Movie: ", "");
|
|
|
|
|
type = "movie";
|
|
|
|
|
} else if (decodedID.startsWith("TV: ")) {
|
|
|
|
|
actualID = decodedID.replace("TV: ", "");
|
|
|
|
|
type = "series";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
|
maxResultsPerResolution: maxResultsPerResolution,
|
|
|
|
|
maxSize: maxSize,
|
|
|
|
|
cachedOnly: cachedOnly,
|
|
|
|
|
removeTrash: removeTrash,
|
|
|
|
|
resultFormat: [resultFormat],
|
|
|
|
|
debridService: debridService,
|
|
|
|
|
debridApiKey: debridApiKey,
|
|
|
|
|
debridStreamProxyPassword: debridStreamProxyPassword,
|
|
|
|
|
languages: {
|
|
|
|
|
exclude: languagesExclude ? languagesExclude.split(",").map(s => s.trim()) : [],
|
|
|
|
|
preferred: languagesPreferred ? languagesPreferred.split(",").map(s => s.trim()) : ["en"]
|
|
|
|
|
},
|
|
|
|
|
resolutions: {},
|
|
|
|
|
options: {
|
|
|
|
|
remove_ranks_under: removeRanksUnder,
|
|
|
|
|
allow_english_in_languages: allowEnglishInLanguages,
|
|
|
|
|
remove_unknown_languages: removeUnknownLanguages
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const encodedConfig = btoa(JSON.stringify(config));
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const endpoint = type === "movie"
|
2026-03-08 17:31:20 +00:00
|
|
|
? "https://comet.feels.legal/" + encodedConfig + "/stream/movie/" + actualID + ".json"
|
|
|
|
|
: "https://comet.feels.legal/" + encodedConfig + "/stream/series/" + actualID + ".json";
|
2025-11-01 16:02:56 +00:00
|
|
|
|
|
|
|
|
const response = await fetchv2(endpoint);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
if (!data.streams || !Array.isArray(data.streams)) {
|
|
|
|
|
return JSON.stringify({
|
|
|
|
|
streams: [],
|
|
|
|
|
subtitle: "https://none.com"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const streamsByQuality = {
|
|
|
|
|
"2160p": [],
|
|
|
|
|
"1080p": [],
|
|
|
|
|
"720p": [],
|
|
|
|
|
"480p": []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const stream of data.streams) {
|
|
|
|
|
const name = stream.name || "";
|
|
|
|
|
let quality = null;
|
|
|
|
|
|
|
|
|
|
if (name.includes("2160p")) quality = "2160p";
|
|
|
|
|
else if (name.includes("1080p")) quality = "1080p";
|
|
|
|
|
else if (name.includes("720p")) quality = "720p";
|
|
|
|
|
else if (name.includes("480p")) quality = "480p";
|
|
|
|
|
|
|
|
|
|
if (quality && streamsByQuality[quality]) {
|
|
|
|
|
streamsByQuality[quality].push({
|
|
|
|
|
title: stream.name || "Unknown",
|
|
|
|
|
streamUrl: stream.url || "",
|
|
|
|
|
headers: {}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let results = [];
|
|
|
|
|
|
|
|
|
|
if (preferedQualityOption === "Auto") {
|
|
|
|
|
results.push(...streamsByQuality["2160p"].slice(0, 5));
|
|
|
|
|
results.push(...streamsByQuality["1080p"].slice(0, 5));
|
|
|
|
|
results.push(...streamsByQuality["720p"].slice(0, 5));
|
|
|
|
|
results.push(...streamsByQuality["480p"].slice(0, 5));
|
|
|
|
|
} else {
|
|
|
|
|
if (streamsByQuality[preferedQualityOption]) {
|
|
|
|
|
results = streamsByQuality[preferedQualityOption].slice(0, 10);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return JSON.stringify({
|
|
|
|
|
streams: results,
|
|
|
|
|
subtitle: "https://none.com"
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return JSON.stringify({
|
|
|
|
|
streams: [],
|
|
|
|
|
subtitle: "https://none.com"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|