|
1 | 1 | // ==UserScript== |
2 | 2 | // @name AniLINK - Episode Link Extractor |
3 | 3 | // @namespace https://greasyfork.org/en/users/781076-jery-js |
4 | | -// @version 6.22.1 |
| 4 | +// @version 6.23.0 |
5 | 5 | // @description Stream or download your favorite anime series effortlessly with AniLINK! Unlock the power to play any anime series directly in your preferred video player or download entire seasons in a single click using popular download managers like IDM. AniLINK generates direct download links for all episodes, conveniently sorted by quality. Elevate your anime-watching experience now! |
6 | 6 | // @icon https://www.google.com/s2/favicons?domain=animepahe.ru |
7 | 7 | // @author Jery |
|
46 | 46 | // @match https://animekai.ac/watch/* |
47 | 47 | // @match https://animekai.cc/watch/* |
48 | 48 | // @match https://anikai.to/watch/* |
| 49 | +// @match https://yflix.to/watch/* |
49 | 50 | // @grant GM_registerMenuCommand |
50 | 51 | // @grant GM_xmlhttpRequest |
51 | 52 | // @grant GM.xmlHttpRequest |
@@ -821,6 +822,43 @@ const Websites = [ |
821 | 822 | }, |
822 | 823 | _encdec: async (s, t = 'e') => await GM_fetch(`https://enc-dec.app/api/${t == 'e' ? 'enc' : 'dec'}-kai?text=` + s).then(r => r.json()).then(d => d.result), |
823 | 824 | _typeSuffix: type => ({ sub: "Hard Sub", softsub: "Soft Sub", dub: "Dub & S-Sub" }[type] || type) |
| 825 | + }, |
| 826 | + { |
| 827 | + name: "YFlix", |
| 828 | + url: ["yflix.to/"], |
| 829 | + _chunkSize: 12, |
| 830 | + addStartButton: function (id) { |
| 831 | + setInterval(() => { |
| 832 | + if ($('#' + id).get(0)) return; try { |
| 833 | + const button = Object.assign(document.createElement('li'), { id, className: "btn btn-primary", textContent: "Extract Stream Links", style: "height: fit-content; margin-left: 10px;" }); |
| 834 | + document.querySelector('#filmServer > ul')?.appendChild(button); |
| 835 | + button.addEventListener('click', extractEpisodes); |
| 836 | + } catch (e) { /* ignore */ } |
| 837 | + }, 500); |
| 838 | + }, |
| 839 | + extractEpisodes: async function* (status) { |
| 840 | + status.text = 'Fetching episode list...'; |
| 841 | + const contentId = _$('div.rating[data-id]')?.dataset.id; if (!contentId) return; |
| 842 | + const encId = await this._enc(contentId); |
| 843 | + const epElms = await fetch(`/ajax/episodes/list?id=${contentId}&_=${encId}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }).then(r => r.json().then(d => d.result)).then(t => (new DOMParser()).parseFromString(t, 'text/html')).then(doc => [...doc.querySelectorAll('ul.episodes[data-season] li a')]); |
| 844 | + const filteredEps = await applyEpisodeRangeFilter(epElms); if (!filteredEps?.length) return; |
| 845 | + const srcCfg = await (async () => { |
| 846 | + const servers = await fetch(`/ajax/links/list?eid=${filteredEps[0].getAttribute('eid')}&_=${await this._enc(filteredEps[0].getAttribute('eid'))}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }).then(r => r.json().then(d => d.result)).then(t => (new DOMParser()).parseFromString(t, 'text/html')).then(doc => [...doc.querySelectorAll('li.server span')].map(s => s.textContent.trim())); |
| 847 | + return await showSourceSelector(servers, 'yflix', { mode: 'single' }); |
| 848 | + })(); |
| 849 | + for (let i = 0; i < filteredEps.length; i += this._chunkSize) |
| 850 | + yield* yieldEpisodesFromPromises(filteredEps.slice(i, i + this._chunkSize).map(async ep => { |
| 851 | + const epNum = ep.getAttribute('num') || ep.querySelector('.num')?.textContent || '1'; |
| 852 | + status.text = `Extracting Episodes ${(epNum - Math.min(this._chunkSize, epNum) + 1)} - ${epNum}...`; |
| 853 | + const servers = await fetch(`/ajax/links/list?eid=${ep.getAttribute('eid')}&_=${await this._enc(ep.getAttribute('eid'))}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }).then(r => r.json().then(d => d.result)).then(t => (new DOMParser()).parseFromString(t, 'text/html')).then(doc => [...doc.querySelectorAll('li.server')].map(s => ({ lid: s.dataset.lid, name: s.querySelector('span')?.textContent.trim() }))).catch(() => []); |
| 854 | + const links = {}, fetchSource = async s => { try { const encUrl = await fetch(`/ajax/links/view?id=${s.lid}&_=${await this._enc(s.lid)}`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }).then(r => r.json().then(d => d.result)); const iframe = await this._dec(encUrl); links[s.name] = await Extractors.use(iframe, (new URL(iframe)).origin+'/'); } catch (e) { showToast(`Failed to fetch Ep ${epNum} from ${s.name}: ${e.message || e}`); } }; |
| 855 | + if (srcCfg?.mode === 'single') { for (const key of srcCfg.sources) { const s = servers.find(srv => srv.name === key); if (s) { await fetchSource(s); if (Object.keys(links).length) break; } } } |
| 856 | + else for (const key of srcCfg.sources) { const s = servers.find(srv => srv.name === key); if (s) await fetchSource(s); } |
| 857 | + return new Episode(epNum, _$('h1.title')?.textContent || '', links, _$('.poster img')?.src || '', ep.querySelector('span:not(.num)')?.textContent || ''); |
| 858 | + })) |
| 859 | + }, |
| 860 | + _enc: async s => await GM_fetch(`https://enc-dec.app/api/enc-movies-flix?text=${s}`).then(r => r.json()).then(d => d.result), |
| 861 | + _dec: async s => await GM_fetch(`https://enc-dec.app/api/dec-movies-flix?text=${s}`).then(r => r.json()).then(d => d.result.url), |
824 | 862 | } |
825 | 863 | ]; |
826 | 864 |
|
@@ -902,11 +940,13 @@ const Extractors = { |
902 | 940 | const source = sources.reduce((best, curr) => (s => parseInt(s.label) || 0)(curr) > (s => parseInt(s.label) || 0)(best) ? curr : best, sources[0]); |
903 | 941 | return { file: source.file, type: source.file.includes('.m3u8') ? 'm3u8' : 'mp4', tracks: [] }; |
904 | 942 | }, |
905 | | - '/^(4spromax|megaup)(\\d+)?\\.?(live|online|cc|site)$/': async function (url, referer = 'https://megaup.cc/') { |
| 943 | + '/^(4spromax|megaup|rapidairmax|rapidshare)(\\d+)?\\.?(live|online|cc|site)$/': async function (url, referer = 'https://megaup.cc/') { |
906 | 944 | // workaround: use GM_xmlhttpRequest to avoid passing cookies (coudnt do that with GM_fetch) |
| 945 | + const u = new URL(url), subListUrl = u.searchParams.get('sub.list'); |
907 | 946 | const encToken = await new Promise((r, j) => GM_xmlhttpRequest({ method: 'GET', url: url.replace('/e/', '/media/'), headers: { 'User-Agent': USER_AGENT_HEADER }, anonymous: true, onload: res => { try { r(JSON.parse(res.responseText).result); } catch (e) { j(e); } }, onerror: j })); |
908 | | - const src = (await GM_fetch('https://enc-dec.app/api/dec-mega', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: encToken, agent: USER_AGENT_HEADER }) }).then(r => r.json())).result; |
909 | | - return { stream: src.sources[0].file, type: 'm3u8', tracks: src.tracks?.map(t => ({ file: t.file, label: t.label, kind: t.kind, default: !!t.default })), referer: 'https://megaup.cc/' }; |
| 947 | + const src = (await GM_fetch(`https://enc-dec.app/api/dec-${url.includes('://rapid') ? 'rapid' : 'mega'}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: encToken, agent: USER_AGENT_HEADER }) }).then(r => r.json())).result; |
| 948 | + const tracks = subListUrl ? await fetch(subListUrl, { headers: { 'Accept': '*/*', 'Referer': `${u.origin}/` } }).then(r => r.json()).then(list => list.filter(t => t.kind === 'captions' && t.file && t.label).map(t => ({ file: t.file, label: t.label, kind: t.kind }))).catch(() => []) : (src.tracks || []).filter(t => t.kind === 'captions' && t.file && t.label).map(t => ({ file: t.file, label: t.label, kind: t.kind, default: !!t.default })); |
| 949 | + return { stream: src.sources[0].file, type: 'm3u8', tracks, referer }; |
910 | 950 | } |
911 | 951 | } |
912 | 952 | /** |
|
0 commit comments