mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 10:51:17 +00:00
Merge pull request #252 from Swakshan/anime/netmirror
Rework: Netmirror
This commit is contained in:
@@ -2,26 +2,27 @@ const mangayomiSources = [{
|
|||||||
"name": "NetMirror",
|
"name": "NetMirror",
|
||||||
"id": 446414301,
|
"id": 446414301,
|
||||||
"lang": "all",
|
"lang": "all",
|
||||||
"baseUrl": "https://iosmirror.cc",
|
"baseUrl": "https://netfree2.cc",
|
||||||
"apiUrl": "https://pcmirror.cc",
|
"apiUrl": "https://netfree2.cc",
|
||||||
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/all.netflixmirror.png",
|
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/all.netflixmirror.png",
|
||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"itemType": 1,
|
"itemType": 1,
|
||||||
"version": "0.2.1",
|
"version": "0.3.4",
|
||||||
"pkgPath": "anime/src/all/netflixmirror.js"
|
"pkgPath": "anime/src/all/netflixmirror.js"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
class DefaultExtension extends MProvider {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.client = new Client();
|
||||||
|
}
|
||||||
|
|
||||||
getPreference(key) {
|
getPreference(key) {
|
||||||
const preferences = new SharedPreferences();
|
const preferences = new SharedPreferences();
|
||||||
return preferences.get(key);
|
return preferences.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMobileBaseUrl() {
|
|
||||||
return this.getPreference("netmirror_override_mobile_base_url");
|
|
||||||
}
|
|
||||||
|
|
||||||
getTVBaseUrl() {
|
getTVBaseUrl() {
|
||||||
return this.getPreference("netmirror_override_tv_base_url");
|
return this.getPreference("netmirror_override_tv_base_url");
|
||||||
}
|
}
|
||||||
@@ -37,8 +38,7 @@ class DefaultExtension extends MProvider {
|
|||||||
return `https://imgcdn.media/pv/480/${id}.jpg`
|
return `https://imgcdn.media/pv/480/${id}.jpg`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCookie(service) {
|
||||||
async getCookie() {
|
|
||||||
const preferences = new SharedPreferences();
|
const preferences = new SharedPreferences();
|
||||||
let cookie = preferences.getString("cookie", "");
|
let cookie = preferences.getString("cookie", "");
|
||||||
var cookie_ts = parseInt(preferences.getString("cookie_ts", "0"));
|
var cookie_ts = parseInt(preferences.getString("cookie_ts", "0"));
|
||||||
@@ -46,64 +46,81 @@ class DefaultExtension extends MProvider {
|
|||||||
|
|
||||||
// Cookie lasts for 24hrs but still checking for 12hrs
|
// Cookie lasts for 24hrs but still checking for 12hrs
|
||||||
if (now_ts - cookie_ts > 60 * 60 * 12) {
|
if (now_ts - cookie_ts > 60 * 60 * 12) {
|
||||||
const check = await new Client().get(this.getMobileBaseUrl() + `/mobile/home`, { "cookie": cookie });
|
var baseUrl = this.getTVBaseUrl()
|
||||||
|
const check = await this.client.get(baseUrl + `/mobile/home`, { "cookie": cookie });
|
||||||
const hDocBody = new Document(check.body).selectFirst("body")
|
const hDocBody = new Document(check.body).selectFirst("body")
|
||||||
|
|
||||||
const addhash = hDocBody.attr("data-addhash");
|
const addhash = hDocBody.attr("data-addhash");
|
||||||
const data_time = hDocBody.attr("data-time");
|
const data_time = hDocBody.attr("data-time");
|
||||||
|
|
||||||
var res = await new Client().post(`${this.getTVBaseUrl()}/tv/p.php`, { "cookie": "" }, { "hash": addhash });
|
var res = await this.client.post(`${baseUrl}/tv/p.php`, { "cookie": "" }, { "hash": addhash });
|
||||||
cookie = res.headers["set-cookie"];
|
cookie = res.headers["set-cookie"];
|
||||||
preferences.setString("cookie", cookie);
|
preferences.setString("cookie", cookie);
|
||||||
preferences.setString("cookie_ts", data_time);
|
preferences.setString("cookie_ts", data_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
var service = this.getServiceDetails();
|
service = service ?? this.getServiceDetails();
|
||||||
|
|
||||||
return `ott=${service}; ${cookie}`;
|
return `ott=${service}; ${cookie}`;
|
||||||
}
|
}
|
||||||
async request(url, cookie) {
|
|
||||||
cookie = cookie ?? await this.getCookie();
|
|
||||||
|
|
||||||
|
async request(slug, service = null, cookie = null) {
|
||||||
|
var service = service ?? this.getServiceDetails();
|
||||||
|
var cookie = cookie ?? await this.getCookie();
|
||||||
|
|
||||||
|
var srv = ""
|
||||||
|
if (service === "pv") srv = "/" + service
|
||||||
|
var url = this.getTVBaseUrl() + "/tv" + srv + slug
|
||||||
|
return (await this.client.get(url, { "cookie": cookie })).body;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getHome(body) {
|
||||||
var service = this.getServiceDetails();
|
var service = this.getServiceDetails();
|
||||||
var slug = "";
|
var list = []
|
||||||
if (url == "/home") slug = "";
|
if (service === "nf") {
|
||||||
else if (service == "pv") slug = "/pv";
|
var body = await this.request("/home", service)
|
||||||
if (!(url.startsWith("https"))) {
|
var elements = new Document(body).select("a.slider-item.boxart-container.open-modal.focusme");
|
||||||
url = this.getMobileBaseUrl() + "/mobile" + slug + url
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await new Client().get(url, { "cookie": cookie })).body;
|
elements.forEach(item => {
|
||||||
}
|
var id = item.attr("data-post")
|
||||||
async getPopular(page) {
|
|
||||||
return await this.getPages(await this.request("/home"), ".tray-container, #top10")
|
|
||||||
}
|
|
||||||
async getLatestUpdates(page) {
|
|
||||||
return await this.getPages(await this.request("/home"), ".inner-mob-tray-container")
|
|
||||||
}
|
|
||||||
async getPages(body, selector) {
|
|
||||||
var name_pref = this.getPreference("netmirror_pref_display_name_1");
|
|
||||||
const elements = new Document(body).select(selector);
|
|
||||||
const cookie = await this.getCookie();
|
|
||||||
const list = [];
|
|
||||||
for (const element of elements) {
|
|
||||||
const linkElement = element.selectFirst("article, .top10-post");
|
|
||||||
const id = linkElement.selectFirst("a").attr("data-post");
|
|
||||||
if (id.length > 0) {
|
if (id.length > 0) {
|
||||||
const imageUrl = linkElement.selectFirst(".card-img-container img, .top10-img img").attr("data-src");
|
var imageUrl = this.getPoster(id, service)
|
||||||
var name = name_pref ? JSON.parse(await this.request(`/post.php?id=${id}`, cookie)).title : `\n${id}`
|
// Having no name breaks the script so having "id" as name
|
||||||
|
var name = `\n${id}`
|
||||||
list.push({ name, imageUrl, link: id });
|
list.push({ name, imageUrl, link: id })
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
var body = await this.request("/homepage.php", service)
|
||||||
|
var elements = JSON.parse(body).post
|
||||||
|
|
||||||
|
elements.forEach(item => {
|
||||||
|
var ids = item.ids
|
||||||
|
ids.split(",").forEach(id => {
|
||||||
|
var imageUrl = this.getPoster(id, service)
|
||||||
|
// Having no name breaks the script so having "id" as name
|
||||||
|
var name = `\n${id}`
|
||||||
|
list.push({ name, imageUrl, link: id })
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
list: list,
|
list: list,
|
||||||
hasNextPage: false
|
hasNextPage: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPopular(page) {
|
||||||
|
return await this.getHome()
|
||||||
|
}
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
return await this.getHome()
|
||||||
|
}
|
||||||
|
|
||||||
async search(query, page, filters) {
|
async search(query, page, filters) {
|
||||||
var service = this.getServiceDetails();
|
var service = this.getServiceDetails();
|
||||||
const data = JSON.parse(await this.request(`/search.php?s=${query}`));
|
const data = JSON.parse(await this.request(`/search.php?s=${query}`, service));
|
||||||
const list = [];
|
const list = [];
|
||||||
data.searchResult.map(async (res) => {
|
data.searchResult.map(async (res) => {
|
||||||
const id = res.id;
|
const id = res.id;
|
||||||
@@ -115,57 +132,62 @@ class DefaultExtension extends MProvider {
|
|||||||
hasNextPage: false
|
hasNextPage: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDetail(url) {
|
async getDetail(url) {
|
||||||
var service = this.getServiceDetails();
|
var service = this.getServiceDetails();
|
||||||
const cookie = await this.getCookie();
|
var cookie = await this.getCookie(service);
|
||||||
|
var linkSlug = "https://netflix.com/title/"
|
||||||
|
if (service === "pv") linkSlug = `https://www.primevideo.com/detail/`
|
||||||
|
|
||||||
const data = JSON.parse(await this.request(`/post.php?id=${url}`, cookie));
|
// Check needed while refreshing existing data
|
||||||
|
var vidId = url
|
||||||
|
if (url.includes(linkSlug)) vidId = url.replaceAll(linkSlug, '')
|
||||||
|
|
||||||
|
const data = JSON.parse(await this.request(`/post.php?id=${vidId}`));
|
||||||
const name = data.title;
|
const name = data.title;
|
||||||
const genre = [data.ua, ...(data.genre || '').split(',').map(g => g.trim())];
|
const genre = [data.ua, ...(data.genre || '').split(',').map(g => g.trim())];
|
||||||
const description = data.desc;
|
const description = data.desc;
|
||||||
let episodes = [];
|
let episodes = [];
|
||||||
if (data.episodes[0] === null) {
|
|
||||||
episodes.push({ name, url: url });
|
var seasons = data.season
|
||||||
} else {
|
if (seasons) {
|
||||||
episodes = data.episodes.map(ep => ({
|
|
||||||
name: `${ep.s.replace('S', 'Season ')} ${ep.ep.replace('E', 'Episode ')} : ${ep.t}`,
|
|
||||||
url: ep.id
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (data.nextPageShow === 1) {
|
|
||||||
const eps = await this.getEpisodes(name, url, data.nextPageSeason, 2, cookie);
|
|
||||||
episodes.push(...eps);
|
|
||||||
}
|
|
||||||
episodes.reverse();
|
|
||||||
if (data.season && data.season.length > 1) {
|
|
||||||
let newEpisodes = [];
|
let newEpisodes = [];
|
||||||
const seasonsToProcess = data.season.slice(0, -1);
|
await Promise.all(seasons.map(async (season) => {
|
||||||
await Promise.all(seasonsToProcess.map(async (season) => {
|
const eps = await this.getEpisodes(name, vidId, season.id, 1, service, cookie);
|
||||||
const eps = await this.getEpisodes(name, url, season.id, 1, cookie);
|
|
||||||
newEpisodes.push(...eps);
|
newEpisodes.push(...eps);
|
||||||
}));
|
}));
|
||||||
newEpisodes.reverse();
|
|
||||||
episodes.push(...newEpisodes);
|
episodes.push(...newEpisodes);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// For movies aka if there are no seasons and episodes
|
||||||
|
episodes.push({
|
||||||
|
name: `Movie`,
|
||||||
|
url: vidId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
var service = this.getServiceDetails();
|
var link = `${linkSlug}${vidId}`
|
||||||
var link = `https://netflix.com/title/${url}`
|
|
||||||
if (service === "pv") link = `https://www.primevideo.com/detail/${url}`
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name, imageUrl: this.getPoster(url, service), link, description, status: 1, genre, episodes
|
name, imageUrl: this.getPoster(vidId, service), link, description, status: 1, genre, episodes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async getEpisodes(name, eid, sid, page, cookie) {
|
|
||||||
|
async getEpisodes(name, eid, sid, page, service, cookie) {
|
||||||
const episodes = [];
|
const episodes = [];
|
||||||
let pg = page;
|
let pg = page;
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(await this.request(`/episodes.php?s=${sid}&series=${eid}&page=${pg}`, cookie));
|
const data = JSON.parse(await this.request(`/episodes.php?s=${sid}&series=${eid}&page=${pg}`, service, cookie));
|
||||||
|
|
||||||
data.episodes?.forEach(ep => {
|
data.episodes?.forEach(ep => {
|
||||||
|
var season = ep.s.replace('S', 'Season ')
|
||||||
|
var epNum = ep.ep.replace("E", "")
|
||||||
|
var epText = `Episode ${epNum}`
|
||||||
|
var title = ep.t
|
||||||
|
title = title == epText ? title : `${epText}: ${title}`
|
||||||
|
|
||||||
episodes.push({
|
episodes.push({
|
||||||
name: `${ep.s.replace('S', 'Season ')} ${ep.ep.replace('E', 'Episode ')} : ${ep.t}`,
|
name: `${season} ${title}`,
|
||||||
url: ep.id
|
url: ep.id
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -177,7 +199,7 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return episodes;
|
return episodes.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorts streams based on user preference.
|
// Sorts streams based on user preference.
|
||||||
@@ -201,27 +223,18 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getVideoList(url) {
|
async getVideoList(url) {
|
||||||
var slug = ""
|
|
||||||
var src = this.getPreference("netmirror_pref_stream_extraction");
|
|
||||||
var service = this.getServiceDetails();
|
|
||||||
|
|
||||||
// prime extracton works only in mobile
|
var baseUrl = this.getTVBaseUrl()
|
||||||
if (service == "pv") {
|
var url = `/playlist.php?id=${url}`
|
||||||
slug = "/pv"
|
|
||||||
src = "mobile"
|
|
||||||
}
|
|
||||||
|
|
||||||
var device = "/mobile"
|
|
||||||
if (src == 'tv') device = "/tv";
|
|
||||||
|
|
||||||
var baseUrl = src === 'tv' ? this.getTVBaseUrl() : this.getMobileBaseUrl()
|
|
||||||
url = baseUrl + device + slug + `/playlist.php?id=${url}`
|
|
||||||
const data = JSON.parse(await this.request(url));
|
const data = JSON.parse(await this.request(url));
|
||||||
|
|
||||||
|
|
||||||
let videoList = [];
|
let videoList = [];
|
||||||
let subtitles = [];
|
let subtitles = [];
|
||||||
let audios = [];
|
let audios = [];
|
||||||
for (const playlist of data) {
|
var playlist = data[0]
|
||||||
var source = playlist.sources[0]
|
var source = playlist.sources[0]
|
||||||
|
|
||||||
var link = baseUrl + source.file;
|
var link = baseUrl + source.file;
|
||||||
var headers =
|
var headers =
|
||||||
{
|
{
|
||||||
@@ -229,7 +242,10 @@ class DefaultExtension extends MProvider {
|
|||||||
'Referer': `${baseUrl}/`
|
'Referer': `${baseUrl}/`
|
||||||
};
|
};
|
||||||
|
|
||||||
var resp = await new Client().get(link, headers);
|
// Auto
|
||||||
|
videoList.push({ url: link, quality: "Auto", "originalUrl": link, headers });
|
||||||
|
|
||||||
|
var resp = await this.client.get(link, headers);
|
||||||
|
|
||||||
if (resp.statusCode === 200) {
|
if (resp.statusCode === 200) {
|
||||||
const masterPlaylist = resp.body;
|
const masterPlaylist = resp.body;
|
||||||
@@ -247,18 +263,13 @@ class DefaultExtension extends MProvider {
|
|||||||
|
|
||||||
|
|
||||||
masterPlaylist.substringAfter('#EXT-X-STREAM-INF:').split('#EXT-X-STREAM-INF:').forEach(it => {
|
masterPlaylist.substringAfter('#EXT-X-STREAM-INF:').split('#EXT-X-STREAM-INF:').forEach(it => {
|
||||||
var quality = `${it.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p (${source.label})`;
|
var quality = `${it.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p`;
|
||||||
let videoUrl = it.substringAfter('\n').substringBefore('\n');
|
let videoUrl = it.substringAfter('\n').substringBefore('\n');
|
||||||
|
|
||||||
if (!videoUrl.startsWith('http')) {
|
if (!videoUrl.startsWith('http')) {
|
||||||
videoUrl = resp.request.url.substringBeforeLast('/') + `/${videoUrl}`;
|
videoUrl = resp.request.url.substringBeforeLast('/') + `/${videoUrl}`;
|
||||||
}
|
}
|
||||||
var headers =
|
headers['Host'] = videoUrl.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1]
|
||||||
{
|
|
||||||
'Host': videoUrl.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1],
|
|
||||||
'Origin': baseUrl,
|
|
||||||
'Referer': `${baseUrl}/`
|
|
||||||
};
|
|
||||||
videoList.push({ url: videoUrl, quality, originalUrl: videoUrl, headers });
|
videoList.push({ url: videoUrl, quality, originalUrl: videoUrl, headers });
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -266,61 +277,37 @@ class DefaultExtension extends MProvider {
|
|||||||
|
|
||||||
|
|
||||||
if ("tracks" in playlist) {
|
if ("tracks" in playlist) {
|
||||||
playlist.tracks.filter(track => track.kind === 'captions').forEach(track => {
|
await Promise.all(playlist.tracks.map(async (track) => {
|
||||||
|
if (track.kind == 'captions') {
|
||||||
var subUrl = track.file
|
var subUrl = track.file
|
||||||
subUrl = subUrl.startsWith("//") ? `https:${subUrl}` : subUrl;
|
subUrl = subUrl.startsWith("//") ? `https:${subUrl}` : subUrl;
|
||||||
|
var subText = await this.client.get(subUrl)
|
||||||
subtitles.push({
|
subtitles.push({
|
||||||
label: track.label,
|
label: track.label,
|
||||||
file: subUrl
|
file: subText.body
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
videoList[0].audios = audios;
|
videoList[0].audios = audios;
|
||||||
videoList[0].subtitles = subtitles;
|
videoList[0].subtitles = subtitles;
|
||||||
return this.sortStreams(videoList);
|
return this.sortStreams(videoList);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSourcePreferences() {
|
getSourcePreferences() {
|
||||||
return [
|
return [{
|
||||||
{
|
|
||||||
key: "netmirror_override_mobile_base_url",
|
|
||||||
editTextPreference: {
|
|
||||||
title: "Override mobile base url",
|
|
||||||
summary: "",
|
|
||||||
value: "https://netfree.cc",
|
|
||||||
dialogTitle: "Override base url",
|
|
||||||
dialogMessage: "",
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: "netmirror_override_tv_base_url",
|
key: "netmirror_override_tv_base_url",
|
||||||
editTextPreference: {
|
editTextPreference: {
|
||||||
title: "Override tv base url",
|
title: "Override tv base url",
|
||||||
summary: "",
|
summary: "",
|
||||||
value: "https://pcmirror.cc",
|
value: "https://netfree2.cc",
|
||||||
dialogTitle: "Override base url",
|
dialogTitle: "Override base url",
|
||||||
dialogMessage: "",
|
dialogMessage: "",
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
key: 'netmirror_pref_video_resolution',
|
|
||||||
listPreference: {
|
|
||||||
title: 'Preferred video resolution',
|
|
||||||
summary: '',
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["1080p", "720p", "480p"],
|
|
||||||
entryValues: ["1080", "720", "480"]
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"key": "netmirror_pref_display_name_1",
|
|
||||||
"switchPreferenceCompat": {
|
|
||||||
"title": "Display media name on home page",
|
|
||||||
"summary": "Homepage loads faster by not calling details API",
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
key: 'netmirror_pref_service',
|
key: 'netmirror_pref_service',
|
||||||
listPreference: {
|
listPreference: {
|
||||||
@@ -331,15 +318,15 @@ class DefaultExtension extends MProvider {
|
|||||||
entryValues: ["nf", "pv",]
|
entryValues: ["nf", "pv",]
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'netmirror_pref_stream_extraction',
|
key: 'netmirror_pref_video_resolution',
|
||||||
listPreference: {
|
listPreference: {
|
||||||
title: 'Preferred stream extraction source',
|
title: 'Preferred video resolution',
|
||||||
summary: 'Extract stream from which source (if one source fails choose another)',
|
summary: '',
|
||||||
valueIndex: 0,
|
valueIndex: 0,
|
||||||
entries: ["TV", "Mobile"],
|
entries: ["1080p", "720p", "480p"],
|
||||||
entryValues: ["tv", "mobile"]
|
entryValues: ["1080", "720", "480"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user