]*class=["'][^"']*limiter[^"']*["'][^>]*>([\s\S]*?)<\/div>/i
+ );
+
+ const description = _stripTags(
+ (descMatch && descMatch[1]) || _extractMeta(html, "name", "description") || "No description available"
+ ) || "No description available";
+
+ let aliases = _extractMeta(html, "property", "og:title") || "";
+ aliases = aliases.replace(/\s*(?:>|\u00BB)+\s*SameBand\s*$/i, "").trim();
+
+ return JSON.stringify([
+ {
+ description,
+ aliases: aliases || "SameBand",
+ airdate: _bestYearCandidate(html)
+ }
+ ]);
+ } catch (_) {
+ return JSON.stringify([]);
+ }
+}
+
+async function extractEpisodes(url) {
+ try {
+ const animeUrl = _extractAnimeUrl(url);
+ if (!animeUrl) return JSON.stringify([]);
+
+ const page = await fetchv2(animeUrl, _htmlHeaders(SEARCH_URL));
+ const html = await page.text();
+
+ const iframeSrc = _extractIframeSrc(html);
+ if (!iframeSrc) return JSON.stringify([]);
+
+ const candidates = _buildListCandidates(iframeSrc);
+ const playlist = await _fetchPlaylistArray(candidates, animeUrl);
+ if (!playlist.length) return JSON.stringify([]);
+
+ const episodes = playlist.map((item, index) => {
+ const titleHtml = String(item?.title || "");
+ const title = _extractEpisodeTitle(titleHtml, index + 1);
+ const number = _extractEpisodeNumber(title, index + 1);
+
+ const imgTag = (titleHtml.match(/
![]()
]*>/i) || [""])[0];
+ const image = _absUrl(_attr(imgTag, "src"), BASE_URL);
+
+ const payload = {
+ animeUrl,
+ file: String(item?.file || ""),
+ thumbnails: String(item?.thumbnails || ""),
+ title,
+ image
+ };
+
+ const out = {
+ href: _packEpisode(payload),
+ number,
+ title
+ };
+
+ if (image) out.image = image;
+ return out;
+ });
+
+ episodes.sort((a, b) => Number(a.number) - Number(b.number));
+ return JSON.stringify(episodes);
+ } catch (_) {
+ return JSON.stringify([]);
+ }
+}
+
+async function extractStreamUrl(href) {
+ try {
+ const payload = _unpackEpisode(href);
+ const fileField = payload?.file ? String(payload.file) : "";
+ if (!fileField) {
+ return JSON.stringify({ streams: [], subtitle: "https://none.com" });
+ }
+
+ const variants = _parseFileVariants(fileField);
+ if (!variants.length) {
+ return JSON.stringify({ streams: [], subtitle: "https://none.com" });
+ }
+
+ const byQuality = {
+ 1080: "",
+ 720: "",
+ 480: ""
+ };
+
+ for (const v of variants) {
+ if ((v.quality === 1080 || v.quality === 720 || v.quality === 480) && !byQuality[v.quality]) {
+ byQuality[v.quality] = v.url;
+ }
+ }
+
+ const url1080 = byQuality[1080] || null;
+ const url720 = byQuality[720] || null;
+ const url480 = byQuality[480] || null;
+
+ const headers = {
+ "User-Agent": _ua(),
+ "Referer": payload?.animeUrl ? String(payload.animeUrl) : (BASE_URL + "/"),
+ "Origin": BASE_URL
+ };
+
+ const streams = [];
+
+ if (url1080) {
+ streams.push({
+ title: "1080p",
+ streamUrl: url1080,
+ url1080,
+ url720,
+ url480,
+ headers
+ });
+ }
+
+ if (url720) {
+ streams.push({
+ title: "720p",
+ streamUrl: url720,
+ url1080,
+ url720,
+ url480,
+ headers
+ });
+ }
+
+ if (url480) {
+ streams.push({
+ title: "480p",
+ streamUrl: url480,
+ url1080,
+ url720,
+ url480,
+ headers
+ });
+ }
+
+ if (!streams.length) {
+ const first = variants[0];
+ if (first?.url) {
+ streams.push({
+ title: "1080p",
+ streamUrl: first.url,
+ url1080: first.url,
+ url720: null,
+ url480: null,
+ headers
+ });
+ }
+ }
+
+ return JSON.stringify({
+ streams,
+ subtitle: "https://none.com"
+ });
+ } catch (_) {
+ return JSON.stringify({ streams: [], subtitle: "https://none.com" });
+ }
+}
diff --git a/sameband/sameband.json b/sameband/sameband.json
new file mode 100644
index 0000000..a2792d7
--- /dev/null
+++ b/sameband/sameband.json
@@ -0,0 +1,24 @@
+{
+ "sourceName": "SameBand",
+ "iconUrl": "https://sameband.studio/templates/sameband/dleimages/favicon/touch-icon-ipad.png",
+ "author": {
+ "name": "emp0ry",
+ "icon": "https://avatars.githubusercontent.com/u/64217088"
+ },
+ "version": "1.0.0",
+ "language": "Russian",
+ "streamType": "HLS",
+ "quality": "1080p",
+ "baseUrl": "https://sameband.studio/",
+ "searchBaseUrl": "https://sameband.studio/",
+ "scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/sameband/sameband.js",
+ "asyncJS": true,
+ "streamAsyncJS": true,
+ "softsub": false,
+ "type": "anime",
+ "downloadSupport": false,
+ "supportsMojuru": true,
+ "supportsDartotsu": true,
+ "supportsSora": true,
+ "supportsLuna": true
+}
diff --git a/shizaproject/shizaproject.js b/shizaproject/shizaproject.js
new file mode 100644
index 0000000..843740c
--- /dev/null
+++ b/shizaproject/shizaproject.js
@@ -0,0 +1,687 @@
+// ShizaProject module for Sora (AsyncJS)
+// Author: emp0ry
+// Version: 1.0.0
+
+const SITE_BASES = [
+ "https://shizaproject.com",
+ "https://shiza-project.com"
+];
+
+const SITE_BASE = SITE_BASES[0];
+const DEFAULT_SUBTITLE = "https://none.com";
+
+function _ua() {
+ return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
+}
+
+function _safeJsonParse(value, fallback) {
+ try {
+ return JSON.parse(value);
+ } catch (_) {
+ return fallback;
+ }
+}
+
+function _cleanBase(base) {
+ const raw = String(base || SITE_BASE).trim().replace(/\/+$/, "");
+ return raw || SITE_BASE;
+}
+
+function _normalizeUrl(url) {
+ const raw = String(url || "").trim();
+ if (!raw) return "";
+ if (raw.startsWith("//")) return "https:" + raw;
+ return raw;
+}
+
+function _packRelease(payload) {
+ return "shizaproject-release:" + encodeURIComponent(JSON.stringify(payload || {}));
+}
+
+function _unpackRelease(href) {
+ const raw = String(href || "");
+ if (!raw.startsWith("shizaproject-release:")) return null;
+ return _safeJsonParse(decodeURIComponent(raw.slice("shizaproject-release:".length)), null);
+}
+
+function _packEpisode(payload) {
+ return "shizaproject:" + encodeURIComponent(JSON.stringify(payload || {}));
+}
+
+function _unpackEpisode(href) {
+ const raw = String(href || "");
+ if (!raw.startsWith("shizaproject:")) return null;
+ return _safeJsonParse(decodeURIComponent(raw.slice("shizaproject:".length)), null);
+}
+
+function _extractSlug(input) {
+ const packed = _unpackRelease(input);
+ if (packed?.slug) return String(packed.slug);
+
+ const raw = String(input || "").trim();
+ if (!raw) return "";
+
+ try {
+ const u = new URL(raw);
+ const parts = u.pathname.split("/").filter(Boolean);
+ return parts[parts.length - 1] || raw;
+ } catch (_) {}
+
+ return raw;
+}
+
+function _extractPreferredBase(input) {
+ const packed = _unpackRelease(input);
+ if (packed?.siteBase) return _cleanBase(packed.siteBase);
+
+ const raw = String(input || "").trim();
+ if (!raw) return SITE_BASE;
+
+ try {
+ const u = new URL(raw);
+ return _cleanBase(u.origin);
+ } catch (_) {}
+
+ return SITE_BASE;
+}
+
+function _orderedBases(preferredBase) {
+ const out = [];
+
+ const add = (base) => {
+ const clean = _cleanBase(base);
+ if (clean && !out.includes(clean)) out.push(clean);
+ };
+
+ if (preferredBase) add(preferredBase);
+ for (const base of SITE_BASES) add(base);
+
+ return out;
+}
+
+function _graphqlHeaders(base) {
+ const site = _cleanBase(base);
+
+ return {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ "User-Agent": _ua(),
+ "Origin": site,
+ "Referer": site + "/"
+ };
+}
+
+async function _graphqlOnBase(base, operationName, variables, query) {
+ const site = _cleanBase(base);
+ const body = JSON.stringify({ operationName, variables, query });
+
+ const response = await fetchv2(site + "/graphql", _graphqlHeaders(site), "POST", body);
+ const data = await response.json();
+
+ if (data?.errors?.length) throw new Error("GraphQL error");
+
+ return {
+ data: data?.data || {},
+ siteBase: site
+ };
+}
+
+async function _graphql(operationName, variables, query, preferredBase) {
+ let lastError = null;
+
+ for (const base of _orderedBases(preferredBase)) {
+ try {
+ return await _graphqlOnBase(base, operationName, variables, query);
+ } catch (e) {
+ lastError = e;
+ }
+ }
+
+ throw lastError || new Error("All ShizaProject domains failed");
+}
+
+const SEARCH_QUERY = `
+query search($query: String!, $type: SearchType!) {
+ search(query: $query, type: $type, first: 10) {
+ edges {
+ node {
+ id
+ __typename
+ ... on Release {
+ id
+ slug
+ name
+ originalName
+ airedOn
+ releasedOn
+ publishedAt
+ announcement
+ episodesCount
+ episodesAired
+ episodeDuration
+ season
+ seasonYear
+ seasonNumber
+ status
+ activity
+ type
+ rating
+ viewCount
+ score
+ genres {
+ id
+ slug
+ name
+ __typename
+ }
+ __typename
+ }
+ }
+ }
+ }
+}`;
+
+const RELEASE_QUERY = `
+query releaseAllSafe($slug: String!) {
+ release(slug: $slug) {
+ __typename
+ id
+ slug
+ name
+ originalName
+ airedOn
+ releasedOn
+ publishedAt
+ announcement
+ episodesCount
+ episodesAired
+ episodeDuration
+ season
+ seasonYear
+ seasonNumber
+ status
+ activity
+ type
+ rating
+ viewCount
+ score
+ genres {
+ id
+ slug
+ name
+ __typename
+ }
+ episodes {
+ __typename
+ id
+ number
+ name
+ duration
+ videos {
+ id
+ embedUrl
+ __typename
+ }
+ }
+ }
+}`;
+
+async function _getRelease(slug, preferredBase) {
+ const result = await _graphql(
+ "releaseAllSafe",
+ { slug: String(slug || "") },
+ RELEASE_QUERY,
+ preferredBase
+ );
+
+ return {
+ release: result?.data?.release || null,
+ siteBase: result?.siteBase || _cleanBase(preferredBase)
+ };
+}
+
+function _releaseAliases(release) {
+ const parts = [];
+
+ if (release?.originalName) parts.push(`Original: ${release.originalName}`);
+ if (release?.type) parts.push(`Type: ${release.type}`);
+ if (Number.isFinite(release?.episodesCount)) parts.push(`Episodes: ${release.episodesCount}`);
+ if (Number.isFinite(release?.episodeDuration)) parts.push(`Duration: ${release.episodeDuration}m`);
+ if (release?.announcement) parts.push(String(release.announcement));
+
+ if (Array.isArray(release?.genres) && release.genres.length) {
+ parts.push(`Genres: ${release.genres.map(g => g?.name).filter(Boolean).join(", ")}`);
+ }
+
+ return parts.join(" | ");
+}
+
+function _scoreTitle(title, keyword) {
+ const t = String(title || "").toLowerCase().trim();
+ const k = String(keyword || "").toLowerCase().trim();
+
+ if (!t || !k) return 99;
+ if (t === k) return 0;
+ if (t.startsWith(k)) return 1;
+ if (t.includes(k)) return 2;
+
+ return 3;
+}
+
+// ------------------------------------------------------------
+// Search -> JSON string
+// ------------------------------------------------------------
+async function searchResults(keyword) {
+ try {
+ const query = String(keyword || "").trim();
+ if (!query) return JSON.stringify([]);
+
+ const result = await _graphql("search", { query, type: "RELEASE" }, SEARCH_QUERY);
+ const siteBase = result?.siteBase || SITE_BASE;
+ const edges = Array.isArray(result?.data?.search?.edges) ? result.data.search.edges : [];
+
+ const out = edges
+ .map(edge => edge?.node)
+ .filter(node => node?.__typename === "Release" && node?.slug)
+ .map(release => {
+ const title = release?.name || release?.originalName || "Unknown title";
+
+ return {
+ title,
+ href: _packRelease({
+ slug: release.slug,
+ id: release.id,
+ title,
+ originalName: release?.originalName || "",
+ airedOn: release?.airedOn || release?.releasedOn || "",
+ siteBase
+ }),
+ _score: Math.min(
+ _scoreTitle(release?.name || "", query),
+ _scoreTitle(release?.originalName || "", query)
+ )
+ };
+ });
+
+ out.sort((a, b) => a._score - b._score);
+ return JSON.stringify(out.map(({ _score, ...rest }) => rest));
+ } catch (_) {
+ return JSON.stringify([]);
+ }
+}
+
+// ------------------------------------------------------------
+// Details -> JSON string
+// ------------------------------------------------------------
+async function extractDetails(href) {
+ try {
+ const slug = _extractSlug(href);
+ const preferredBase = _extractPreferredBase(href);
+
+ if (!slug) return JSON.stringify([]);
+
+ const result = await _getRelease(slug, preferredBase);
+ const release = result?.release;
+
+ if (!release) return JSON.stringify([]);
+
+ const titleLine = release?.originalName && release?.originalName !== release?.name
+ ? `${release.name} / ${release.originalName}`
+ : (release?.name || release?.originalName || "ShizaProject");
+
+ const statusLine = [release?.status, release?.activity]
+ .filter(Boolean)
+ .join(" / ");
+
+ const description = [titleLine, statusLine]
+ .filter(Boolean)
+ .join("\n");
+
+ return JSON.stringify([{
+ description: description || "No description available.",
+ aliases: _releaseAliases(release) || "ShizaProject",
+ airdate: release?.airedOn || release?.releasedOn || "Unknown"
+ }]);
+ } catch (_) {
+ return JSON.stringify([]);
+ }
+}
+
+// ------------------------------------------------------------
+// Episodes -> JSON string
+// ------------------------------------------------------------
+async function extractEpisodes(href) {
+ try {
+ const slug = _extractSlug(href);
+ const preferredBase = _extractPreferredBase(href);
+
+ if (!slug) return JSON.stringify([]);
+
+ const result = await _getRelease(slug, preferredBase);
+ const release = result?.release;
+ const siteBase = result?.siteBase || preferredBase || SITE_BASE;
+
+ if (!release) return JSON.stringify([]);
+
+ const episodes = Array.isArray(release?.episodes) ? release.episodes : [];
+
+ const out = episodes
+ .map((ep, index) => {
+ const videos = Array.isArray(ep?.videos) ? ep.videos : [];
+
+ const embedUrls = videos
+ .map(v => _normalizeUrl(v?.embedUrl || ""))
+ .filter(Boolean);
+
+ if (!embedUrls.length) return null;
+
+ const number = Number.isFinite(ep?.number) ? ep.number : (index + 1);
+ const title = ep?.name ? String(ep.name) : `Episode ${number}`;
+
+ const entry = {
+ href: _packEpisode({
+ releaseSlug: release.slug,
+ episodeId: ep?.id || "",
+ number,
+ title,
+ embedUrls,
+ fallbackEmbedUrl: embedUrls[0],
+ siteBase
+ }),
+ number,
+ title
+ };
+
+ if (Number.isFinite(ep?.duration)) {
+ entry.duration = ep.duration * 60;
+ }
+
+ return entry;
+ })
+ .filter(Boolean)
+ .sort((a, b) => Number(a.number) - Number(b.number));
+
+ return JSON.stringify(out);
+ } catch (_) {
+ return JSON.stringify([]);
+ }
+}
+
+function _qualityNumber(key) {
+ const n = parseInt(String(key || "").replace(/[^\d]/g, ""), 10);
+ return Number.isFinite(n) ? n : 0;
+}
+
+function _pickQualityUrl(qualities, target) {
+ for (const key in qualities || {}) {
+ const q = _qualityNumber(key);
+ const src = _normalizeUrl(qualities?.[key]?.src || "");
+ if (q === target && src) return src;
+ }
+
+ return null;
+}
+
+function _streamHeaders(siteBase) {
+ const site = _cleanBase(siteBase || SITE_BASE);
+
+ return {
+ "User-Agent": _ua(),
+ "Referer": site + "/",
+ "Origin": site
+ };
+}
+
+function _buildStreamsFromQualities(qualities, siteBase) {
+ const url1080 = _pickQualityUrl(qualities, 1080);
+ const url720 = _pickQualityUrl(qualities, 720);
+ const url480 = _pickQualityUrl(qualities, 480);
+ const url360 = _pickQualityUrl(qualities, 360);
+
+ const known = {
+ 1080: url1080,
+ 720: url720,
+ 480: url480,
+ 360: url360
+ };
+
+ const headers = _streamHeaders(siteBase);
+ const streams = [];
+
+ for (const quality of [1080, 720, 480, 360]) {
+ const streamUrl = known[quality];
+ if (!streamUrl) continue;
+
+ streams.push({
+ title: `${quality}p`,
+ streamUrl,
+ url1080,
+ url720,
+ url480,
+ url360,
+ headers
+ });
+ }
+
+ if (!streams.length) {
+ let bestQuality = 0;
+ let bestUrl = "";
+
+ for (const key in qualities || {}) {
+ const q = _qualityNumber(key);
+ const src = _normalizeUrl(qualities?.[key]?.src || "");
+ if (!src) continue;
+
+ if (q > bestQuality) {
+ bestQuality = q;
+ bestUrl = src;
+ }
+ }
+
+ if (bestUrl) {
+ streams.push({
+ title: bestQuality ? `${bestQuality}p` : "Kodik",
+ streamUrl: bestUrl,
+ url1080: bestQuality >= 1080 ? bestUrl : null,
+ url720: bestQuality >= 720 && bestQuality < 1080 ? bestUrl : null,
+ url480: bestQuality >= 480 && bestQuality < 720 ? bestUrl : null,
+ url360: bestQuality >= 360 && bestQuality < 480 ? bestUrl : null,
+ headers
+ });
+ }
+ }
+
+ return streams;
+}
+
+function _isKodikUrl(url) {
+ const raw = String(url || "").toLowerCase();
+ return raw.includes("kodikplayer.com") || raw.includes("kodik.info");
+}
+
+// ------------------------------------------------------------
+// Stream -> quality picker JSON
+// ------------------------------------------------------------
+async function extractStreamUrl(href) {
+ try {
+ const payload = _unpackEpisode(href);
+
+ if (!payload) {
+ return JSON.stringify({
+ streams: [],
+ subtitle: DEFAULT_SUBTITLE
+ });
+ }
+
+ const siteBase = _cleanBase(payload?.siteBase || SITE_BASE);
+
+ const embedUrls = Array.isArray(payload?.embedUrls)
+ ? payload.embedUrls.map(_normalizeUrl).filter(Boolean)
+ : [_normalizeUrl(payload?.fallbackEmbedUrl)].filter(Boolean);
+
+ const streams = [];
+
+ for (const embedUrl of embedUrls) {
+ if (!_isKodikUrl(embedUrl)) continue;
+
+ try {
+ const qualitiesJson = await kodikParser(embedUrl, siteBase);
+ const qualities = _safeJsonParse(qualitiesJson, {});
+ const parsedStreams = _buildStreamsFromQualities(qualities, siteBase);
+ streams.push(...parsedStreams);
+ } catch (_) {}
+ }
+
+ return JSON.stringify({
+ streams,
+ subtitle: DEFAULT_SUBTITLE
+ });
+ } catch (_) {
+ return JSON.stringify({
+ streams: [],
+ subtitle: DEFAULT_SUBTITLE
+ });
+ }
+}
+
+// Kodik parser adapted from yummyanime/onwave-style logic.
+async function kodikParser(url, siteBase) {
+ try {
+ const site = _cleanBase(siteBase || SITE_BASE);
+ const embedUrl = _normalizeUrl(url);
+
+ const headers = {
+ "Referer": site + "/",
+ "User-Agent": _ua()
+ };
+
+ const response = await fetchv2(embedUrl, headers);
+ const htmlText = await response.text();
+
+ const urlParamsMatch = htmlText.match(/var\s+urlParams\s*=\s*'([^']+)'/);
+ const videoInfoTypeMatch = htmlText.match(/vInfo\.type\s*=\s*'([^']+)'/);
+ const videoInfoHashMatch = htmlText.match(/vInfo\.hash\s*=\s*'([^']+)'/);
+ const videoInfoIdMatch = htmlText.match(/vInfo\.id\s*=\s*'([^']+)'/);
+
+ const urlParams = urlParamsMatch ? _safeJsonParse(urlParamsMatch[1], {}) : {};
+ const videoInfoType = videoInfoTypeMatch ? videoInfoTypeMatch[1] : "";
+ const videoInfoHash = videoInfoHashMatch ? videoInfoHashMatch[1] : "";
+ const videoInfoId = videoInfoIdMatch ? videoInfoIdMatch[1] : "";
+
+ const finalData =
+ `d=${urlParams.d || ""}` +
+ `&d_sign=${urlParams.d_sign || ""}` +
+ `&pd=${urlParams.pd || ""}` +
+ `&pd_sign=${urlParams.pd_sign || ""}` +
+ `&ref=${urlParams.ref || ""}` +
+ `&ref_sign=${urlParams.ref_sign || ""}` +
+ `&bad_user=false&cdn_is_working=true` +
+ `&type=${encodeURIComponent(videoInfoType)}` +
+ `&hash=${encodeURIComponent(videoInfoHash)}` +
+ `&id=${encodeURIComponent(videoInfoId)}` +
+ `&info=%7B%7D`;
+
+ const headers2 = {
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
+ "Referer": site + "/",
+ "User-Agent": _ua(),
+ "X-Requested-With": "XMLHttpRequest"
+ };
+
+ const apiResponse = await fetchv2(
+ "https://kodikplayer.com/ftor",
+ headers2,
+ "POST",
+ finalData
+ );
+
+ const apiJson = await apiResponse.json();
+
+ const qualities = {};
+
+ if (apiJson?.links) {
+ for (const quality in apiJson.links) {
+ const qualityArray = apiJson.links[quality];
+ const first = Array.isArray(qualityArray) ? qualityArray[0] : null;
+
+ if (!first?.src) continue;
+
+ qualities[quality] = {
+ src: decode(first.src),
+ type: first.type || "application/x-mpegURL"
+ };
+ }
+ }
+
+ return JSON.stringify(qualities, null, 2);
+ } catch (_) {
+ return JSON.stringify({});
+ }
+}
+
+function decode(input) {
+ const map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ let out = "";
+ let buffer = 0;
+ let count = 0;
+
+ const rotated = [];
+ const source = String(input || "");
+
+ for (let i = 0; i < source.length; i++) {
+ const ch = source[i];
+
+ if (/[a-zA-Z]/.test(ch)) {
+ const code = ch.charCodeAt(0);
+ const max = ch <= "Z" ? 90 : 122;
+ const shifted = code + 18;
+
+ rotated.push(String.fromCharCode(shifted <= max ? shifted : shifted - 26));
+ } else {
+ rotated.push(ch);
+ }
+ }
+
+ const rot = rotated.join("");
+
+ for (let j = 0; j < rot.length; j++) {
+ const ch = rot[j];
+
+ if (ch === "=") break;
+
+ const val = map.indexOf(ch);
+ if (val === -1) continue;
+
+ buffer = (buffer << 6) | val;
+ count += 6;
+
+ if (count >= 8) {
+ count -= 8;
+ out += String.fromCharCode((buffer >> count) & 0xff);
+ }
+ }
+
+ return out;
+}
+
+function _defaultExport() {
+ return {
+ searchResults,
+ extractDetails,
+ extractEpisodes,
+ extractStreamUrl
+ };
+}
+
+try {
+ globalThis.default = _defaultExport;
+} catch (_) {}
+
+try {
+ this.default = _defaultExport;
+} catch (_) {}
+
+try {
+ globalThis.module = globalThis.module || {};
+ globalThis.module.exports = { default: _defaultExport };
+} catch (_) {}
\ No newline at end of file
diff --git a/shizaproject/shizaproject.json b/shizaproject/shizaproject.json
new file mode 100644
index 0000000..7d19b06
--- /dev/null
+++ b/shizaproject/shizaproject.json
@@ -0,0 +1,24 @@
+{
+ "sourceName": "ShizaProject",
+ "iconUrl": "https://shizaproject.com/favicon.ico",
+ "author": {
+ "name": "emp0ry",
+ "icon": "https://avatars.githubusercontent.com/u/64217088"
+ },
+ "version": "1.0.0",
+ "language": "Russian",
+ "streamType": "HLS",
+ "quality": "720p",
+ "baseUrl": "https://shizaproject.com/",
+ "searchBaseUrl": "https://shizaproject.com/",
+ "scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/shizaproject/shizaproject.js",
+ "asyncJS": true,
+ "streamAsyncJS": true,
+ "softsub": false,
+ "type": "anime",
+ "downloadSupport": false,
+ "supportsMojuru": true,
+ "supportsDartotsu": true,
+ "supportsSora": true,
+ "supportsLuna": true
+}
diff --git a/yummyanime/yummyanime.js b/yummyanime/yummyanime.js
index c69025f..450a2a5 100644
--- a/yummyanime/yummyanime.js
+++ b/yummyanime/yummyanime.js
@@ -314,11 +314,20 @@ async function extractStreamUrl(href) {
const qualities = _safeJsonParse(qualitiesJson, {});
let bestUrl = "";
let bestQ = 0;
+ let url1080 = null;
+ let url720 = null;
+ let url480 = null;
for (const q in qualities) {
- const src = qualities?.[q]?.src;
+ const srcRaw = qualities?.[q]?.src;
+ const src = srcRaw ? (String(srcRaw).startsWith("//") ? "https:" + String(srcRaw) : String(srcRaw)) : "";
if (!src) continue;
const n = parseInt(String(q).replace(/[^\d]/g, ""), 10) || 0;
+
+ if (n >= 1080 && !url1080) url1080 = src;
+ if (n === 720 && !url720) url720 = src;
+ if (n === 480 && !url480) url480 = src;
+
if (n > bestQ) {
bestQ = n;
bestUrl = src;
@@ -327,11 +336,19 @@ async function extractStreamUrl(href) {
if (!bestUrl) continue;
- const finalUrl = bestUrl.startsWith("//") ? "https:" + bestUrl : bestUrl;
+ const finalUrl = bestUrl;
+
+ // Fallback quality mapping for sources with uncommon labels.
+ if (!url1080 && bestQ >= 1080) url1080 = finalUrl;
+ if (!url720 && bestQ >= 720 && bestQ < 1080) url720 = finalUrl;
+ if (!url480 && bestQ >= 480 && bestQ < 720) url480 = finalUrl;
streams.push({
- title: `${opt.dubbing}${bestQ ? ` (${bestQ}p)` : ""} (Kodik)`,
+ title: `${opt.dubbing} (Kodik)`,
streamUrl: finalUrl,
+ url1080,
+ url720,
+ url480,
headers: {
"User-Agent": _ua(),
"Referer": IMAGE_REFERER
diff --git a/yummyanime/yummyanime.json b/yummyanime/yummyanime.json
index 22f5c2c..925f7a4 100644
--- a/yummyanime/yummyanime.json
+++ b/yummyanime/yummyanime.json
@@ -5,7 +5,7 @@
"name": "emp0ry",
"icon": "https://avatars.githubusercontent.com/u/64217088"
},
- "version": "1.0.3",
+ "version": "1.0.4",
"language": "Russian",
"streamType": "HLS",
"quality": "1080p",
@@ -20,8 +20,5 @@
"supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true,
- "supportsLuna": true,
- "supportsAnymex": true,
- "supportsTsumi": true,
- "supportsHiyoku": true
-}
\ No newline at end of file
+ "supportsLuna": true
+}