anime(aniplay): rewrite anime details and video list logic

This commit is contained in:
Swakshan
2025-04-27 19:20:52 +05:30
parent 2cbedc93de
commit 79ef1409b4

View File

@@ -6,7 +6,7 @@ const mangayomiSources = [{
"iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://aniplaynow.live/",
"typeSource": "single",
"itemType": 1,
"version": "1.4.0",
"version": "1.4.5",
"dateFormat": "",
"dateFormatLocale": "",
"pkgPath": "anime/src/en/aniplay.js"
@@ -307,7 +307,7 @@ class DefaultExtension extends MProvider {
"Content-Type": "application/json",
}
var response = await new Client().post(url, headers, body);
var response = await this.client.post(url, headers, body);
if (response.statusCode != 200) {
throw new Error("Error: " + response.statusText);
@@ -319,6 +319,9 @@ class DefaultExtension extends MProvider {
async getDetail(url) {
var anilistId = url
if (url.includes("info/")) {
anilistId = url.substring(url.lastIndexOf("info/") + 5)
}
var animeData = await this.getAnimeDetails(anilistId)
@@ -329,39 +332,57 @@ class DefaultExtension extends MProvider {
throw new Error("Error: No data found for the given URL");
}
var user_provider = this.getPreference("aniplay_pref_provider_3");
var choices = result
if (user_provider !== "all") {
for (var ch of result) {
if (ch["providerId"] == user_provider) {
choices = [ch]
break;
}
var chapters = []
var chaps = {}
for (var item of result) {
var providerId = item["providerId"]
// Hika has bunch of embeds, not stream url, so avoiding it for now
if (providerId == "hika") continue
var episodes = item['episodes']
for (var episode of episodes) {
var id = episode.id
var number = episode.number.toString()
var chap = chaps.hasOwnProperty(number) ? chaps[number] : {}
var updatedAt = episode.hasOwnProperty("updatedAt") ? episode.updatedAt.toString() : null
var title = episode.hasOwnProperty("title") ? episode.title : ""
var isFiller = episode.hasOwnProperty("isFiller") ? episode.isFiller : false
var hasDub = episode.hasOwnProperty("hasDub") ? episode.hasDub : false
chap.title = title == "" ? chap.title : title
chap.isFiller = isFiller || chap.isFiller
chap.hasDub = hasDub || chap.hasDub
chap.updatedAt = updatedAt ?? chap.updatedAt
var prvds = chap.hasOwnProperty("prvds") ? chap["prvds"] : {}
prvds[providerId] = { anilistId, providerId, id, number, hasDub }
chap["prvds"] = prvds
chaps[number] = chap
}
}
var markFillers = this.getPreference("aniplay_pref_mark_filler")
for (const choice of choices) {
var user_mark_filler_ep = this.getPreference("aniplay_pref_mark_filler");
var chapters = []
var epList = choice.episodes
for (var ep of epList) {
var title = ep.title
var num = ep.number
var isFiller = ep.isFiller
var scanlator = isFiller && user_mark_filler_ep ? `Filler` : null;
var dateUpload = "createdAt" in ep ? new Date(ep.createdAt) : Date.now()
dateUpload = dateUpload.valueOf().toString();
delete ep.img
delete ep.title
delete ep.description
delete ep.isFiller
var epUrl = `${anilistId}||${JSON.stringify(ep)}||${choice.providerId}`
chapters.push({ name: `E${num}: ${title}`, url: epUrl, dateUpload, scanlator })
for (var episodeNum in chaps) {
var chap = chaps[episodeNum]
var title = chap.title
var dateUpload = chap.updatedAt
var scanlator = "SUB"
if (chap.hasDub) {
scanlator += ", DUB"
}
}
var isFillers = chap.isFiller
if (markFillers && isFillers) {
scanlator = "FILLER, " + scanlator
}
var epData = JSON.stringify(chap["prvds"])
chapters.push({ name: `E${episodeNum}: ${title}`, url: epData, dateUpload, scanlator })
}
var format = animeData.format
if (format === "MOVIE") chapters[0].name = "Movie"
@@ -372,179 +393,84 @@ class DefaultExtension extends MProvider {
return animeData
}
// Sorts streams based on user preference.
async sortStreams(streams) {
var sortedStreams = [];
var copyStreams = streams.slice()
var pref = await this.getPreference("aniplay_pref_video_resolution");
for (var stream of streams) {
if (stream.quality.indexOf(pref) > -1) {
sortedStreams.push(stream);
var index = copyStreams.indexOf(stream);
if (index > -1) {
copyStreams.splice(index, 1);
}
break;
}
}
return [...sortedStreams, ...copyStreams]
}
// Adds proxy to streams
async streamProxy(providerId, streams) {
var proxyBaseUrl = this.getPreference("aniplay_stream_proxy");
var slug = "/fetch?url="
var ref = "&ref="
if (providerId == "yuki") {
ref += "https://hianime.to"
} else if (providerId == "pahe") {
ref += "https://kwik.si"
} else if (providerId === "maze") {
ref += ""
} else if (providerId === "kuro") {
ref = ""
}
streams.forEach(stream => {
stream.url = proxyBaseUrl + slug + stream.url + ref;
stream.originalUrl = proxyBaseUrl + slug + stream.originalUrl + ref;
});
return streams
}
// Extracts the streams url for different resolutions from a hls stream.
async extractStreams(url, providerId, hdr = {}) {
var streams = [{
url: url,
originalUrl: url,
quality: `Auto - ${providerId}`,
headers: hdr
}];
var doExtract = this.getPreference("aniplay_pref_extract_streams");
// Pahe only has auto
if (providerId === "pahe" || !doExtract) {
return streams;
}
const response = await new Client().get(url, hdr);
const body = response.body;
const lines = body.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-STREAM-INF:')) {
var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1];
var m3u8Url = lines[i + 1].trim();
if (providerId === "yuki") {
var orginalUrl = url
m3u8Url = orginalUrl.replace("master.m3u8", m3u8Url)
}
streams.push({
url: m3u8Url,
originalUrl: m3u8Url,
quality: `${resolution} - ${providerId}`,
headers: hdr
});
}
}
return streams
}
async getAnyStreams(result) {
var m3u8Url = result.sources[0].url
return await this.extractStreams(m3u8Url, "any");
}
async getYukiStreams(result) {
var m3u8Url = result.sources[0].url
var streams = await this.extractStreams(m3u8Url, "yuki");
var subtitles = []
result.subtitles.forEach(sub => {
var label = sub.label
if (label.indexOf("thumbnail") < 0) { // thumbnails shouldnt be included
subtitles.push({
"label": label,
"file": sub.url,
});
}
})
streams[0].subtitles = subtitles
return streams
}
async getPaheStreams(result) {
var m3u8Url = result.sources[0].url
var hdr = result.headers;
return await this.extractStreams(m3u8Url, "pahe", hdr);
}
async getMazeStreams(result) {
var m3u8Url = result.sources[0].url
var hdr = result.headers;
return await this.extractStreams(m3u8Url, "maze", hdr);
}
async getKuroStreams(result) {
var links = result.sources
var hdr = result.headers;
var streams = [];
for (var stream of links) {
var quality = stream.quality
quality = quality == "default" ? "auto" : quality
streams.push({
url: stream.url,
originalUrl: stream.url,
quality: `${quality} - kuro`,
headers: hdr
});
}
return streams
}
// For anime episode video list
async getVideoList(url) {
var urlSplits = url.split("||")
var anilistId = urlSplits[0]
var epData = JSON.parse(urlSplits[1])
var providerId = urlSplits[2]
var providerInfos = JSON.parse(url)
var user_audio_type = this.getPreference("aniplay_pref_audio_type");
var subOrDub = epData.hasDub && user_audio_type === "dub" ? "dub" : "sub"
var pref_provider = this.getPreference("aniplay_pref_provider_4")
var providers = Object.keys(providerInfos)
var slug = `watch/${anilistId}`
var body = [
anilistId,
providerId,
epData.id,
epData.number.toString(),
subOrDub
]
var result = await this.aniplayRequest(slug, body)
if (result === null) {
if (pref_provider == "any") {
//any = randomly choose one
var randomIndex = Math.floor(Math.random() * providers.length);
providers = [providers[randomIndex]]
} else if (pref_provider == "pahe") {
//pahe = if "pahe" is available then chose it
if (providerInfos.hasOwnProperty("pahe")) {
providers = ["pahe"]
}
} else if (pref_provider == "yuki") {
//yuki = if "yuki" is available then chose it
if (providerInfos.hasOwnProperty("yuki")) {
providers = ["yuki"]
}
}
var finalStreams = []
var user_audio_type = this.getPreference("aniplay_pref_audio_type_1")
for (var provider of providers) {
var streams = []
var providerInfo = providerInfos[provider]
var anilistId = providerInfo.anilistId
var providerId = providerInfo.providerId
var id = providerInfo.id
var number = providerInfo.number
var hasDub = providerInfo.hasDub
var audios = []
// if there "sub" is prefered or there are no preference then add sub
if (user_audio_type.includes("sub") || user_audio_type.length < 1) audios.push("sub")
if (hasDub && user_audio_type.includes("dub")) audios.push("dub")
var slug = `watch/${anilistId}`
for (var audio of audios) {
var body = [
anilistId,
providerId,
id,
number,
audio
]
var result = await this.aniplayRequest(slug, body)
if (result === null) {
continue
}
if (providerId == "yuki") {
// Yuki always has softsubs aka subtitles are seperate.
streams = await this.getYukiStreams(result, "soft" + audio)
} else if (providerId == "pahe") {
// Pahe always has hardsubs aka subtitles printed on video.
streams = await this.getPaheStreams(result, "hard" + audio)
} else {
continue
}
if (this.getPreference("aniplay_proxy")) streams = this.streamProxy(providerId, streams)
var sortedStreams = this.sortStreams(streams)
finalStreams = [...sortedStreams, ...finalStreams]
}
}
if (finalStreams.length < 1) {
throw new Error("Error: No data found for the given URL");
}
var streams = []
if (providerId == "yuki") {
streams = await this.getYukiStreams(result)
} else if (providerId == "pahe") {
streams = await this.getPaheStreams(result)
} else if (providerId === "maze") {
streams = await this.getMazeStreams(result)
} else if (providerId === "kuro") {
streams = await this.getKuroStreams(result)
} else {
streams = await this.getAnyStreams(result) //If new provider found getAnyStreams will extract only the streams
}
if (this.getPreference("aniplay_proxy")) streams = await this.streamProxy(providerId, streams)
return await this.sortStreams(streams)
return finalStreams
}
getSourcePreferences() {
@@ -559,47 +485,47 @@ class DefaultExtension extends MProvider {
}
},
{
"key": "aniplay_pref_title",
"listPreference": {
"title": "Preferred Title",
"summary": "",
"valueIndex": 0,
"entries": ["Romaji", "English", "Native"],
"entryValues": ["romaji", "english", "native"],
key: "aniplay_pref_title",
listPreference: {
title: "Preferred Title",
summary: "",
valueIndex: 0,
entries: ["Romaji", "English", "Native"],
entryValues: ["romaji", "english", "native"],
}
},
{
"key": "aniplay_pref_provider_3",
"listPreference": {
"title": "Preferred provider",
"summary": "",
"valueIndex": 0,
"entries": ["Any", "Yuki", "Pahe", "Maze", "Kuro"],
"entryValues": ["any", "yuki", "pahe", "maze", "kuro"],
key: "aniplay_pref_provider_4",
listPreference: {
title: "Preferred provider",
summary: "",
valueIndex: 1,
entries: ["Any", "All", "Yuki", "Pahe"],
entryValues: ["any", "all", "yuki", "pahe"],
}
}, {
"key": "aniplay_pref_mark_filler",
"switchPreferenceCompat": {
"title": "Mark filler episodes",
"summary": "Filler episodes will be marked with (F)",
"value": false
key: "aniplay_pref_mark_filler",
switchPreferenceCompat: {
title: "Mark filler episodes",
summary: "Filler episodes will be marked with (F)",
value: false
}
},
{
"key": "aniplay_pref_audio_type",
"listPreference": {
"title": "Preferred audio type",
"summary": "Sub/Dub",
"valueIndex": 0,
"entries": ["Sub", "Dub"],
"entryValues": ["sub", "dub"],
key: "aniplay_pref_audio_type_1",
multiSelectListPreference: {
title: 'Preferred stream sub/dub type',
summary: '',
values: ["sub"],
entries: ["Sub", "Dub"],
entryValues: ["sub", "dub"]
}
}, {
"key": "aniplay_pref_extract_streams",
"switchPreferenceCompat": {
key: "aniplay_pref_extract_streams",
switchPreferenceCompat: {
'title': 'Split stream into different quality streams',
"summary": "Split stream Auto into 360p/720p/1080p",
"value": true
summary: "Split stream Auto into 360p/720p/1080p",
value: true
}
}, {
key: 'aniplay_pref_video_resolution',
@@ -631,8 +557,112 @@ class DefaultExtension extends MProvider {
]
}
//------------ Extract keys ---------------
// ----------- Stream manipulations -------
// Sorts streams based on user preference.
sortStreams(streams) {
var sortedStreams = [];
var copyStreams = streams.slice()
var pref = this.getPreference("aniplay_pref_video_resolution");
for (var stream of streams) {
if (stream.quality.indexOf(pref) > -1) {
sortedStreams.push(stream);
var index = copyStreams.indexOf(stream);
if (index > -1) {
copyStreams.splice(index, 1);
}
break;
}
}
return [...sortedStreams, ...copyStreams]
}
// Adds proxy to streams
streamProxy(providerId, streams) {
var proxyBaseUrl = this.getPreference("aniplay_stream_proxy");
var slug = "/fetch?url="
var ref = "&ref="
if (providerId == "yuki") {
ref += "https://hianime.to"
} else if (providerId == "pahe") {
ref += "https://kwik.si"
}
streams.forEach(stream => {
stream.url = proxyBaseUrl + slug + stream.url + ref;
stream.originalUrl = proxyBaseUrl + slug + stream.originalUrl + ref;
});
return streams
}
// Extracts the streams url for different resolutions from a hls stream.
async extractStreams(url, audio, providerId, hdr = {}) {
audio = audio.toUpperCase()
var streams = [{
url: url,
originalUrl: url,
quality: `Auto - ${providerId} : ${audio}`,
headers: hdr
}];
var doExtract = this.getPreference("aniplay_pref_extract_streams");
// Pahe only has auto
if (providerId === "pahe" || !doExtract) {
return streams;
}
const response = await this.client.get(url, hdr);
const body = response.body;
const lines = body.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-STREAM-INF:')) {
var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1];
var m3u8Url = lines[i + 1].trim();
if (providerId === "yuki") {
var orginalUrl = url
m3u8Url = orginalUrl.replace("master.m3u8", m3u8Url)
}
streams.push({
url: m3u8Url,
originalUrl: m3u8Url,
quality: `${resolution} - ${providerId} : ${audio}`,
headers: hdr
});
}
}
return streams
}
async getYukiStreams(result, audio) {
var m3u8Url = result.sources[0].url
var streams = await this.extractStreams(m3u8Url, audio, "yuki");
var subtitles = []
result.subtitles.forEach(sub => {
var label = sub.label
if (label.indexOf("thumbnail") < 0) { // thumbnails shouldnt be included
subtitles.push({
"label": sub.lang,
"file": sub.url,
});
}
})
streams[0].subtitles = subtitles
return streams
}
async getPaheStreams(result, audio) {
var m3u8Url = result.sources[0].url
var hdr = result.headers;
return await this.extractStreams(m3u8Url, audio, "pahe", hdr);
}
//------------ Extract keys ---------------
async extractKeys(baseUrl) {
const preferences = new SharedPreferences();
let KEYS = preferences.getString("aniplay_keys", "");