mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-14 13:30:21 +00:00
feat: use tosho instead of nyaa for RSS
This commit is contained in:
parent
0c55d543f1
commit
4163db7a0d
8 changed files with 27 additions and 232 deletions
|
|
@ -4,5 +4,6 @@
|
|||
"paths": {
|
||||
"@/*": ["src/renderer/*"],
|
||||
}
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "**/node_modules", "dist", "build"]
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
import RSSView from './views/RSSView.svelte'
|
||||
import Menubar from './components/Menubar.svelte'
|
||||
import Toasts from './components/Toasts.svelte'
|
||||
import CatBlock from './views/CatBlock.svelte' // TODO: deprecate
|
||||
import IspBlock from './views/IspBlock.svelte'
|
||||
|
||||
setContext('view', view)
|
||||
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
<Toasts />
|
||||
<div class='page-wrapper with-sidebar with-transitions bg-dark' data-sidebar-type='overlayed-all'>
|
||||
<div class='sticky-alerts' />
|
||||
<CatBlock />
|
||||
<IspBlock />
|
||||
<Menubar bind:page={$page} />
|
||||
<ViewAnime />
|
||||
<ViewTrailer />
|
||||
|
|
|
|||
|
|
@ -1,199 +0,0 @@
|
|||
/* eslint-env browser */
|
||||
function waitToCompleteIceGathering (pc, state = pc.iceGatheringState) {
|
||||
return state !== 'complete' && new Promise(resolve => {
|
||||
pc.addEventListener('icegatheringstatechange', () => (pc.iceGatheringState === 'complete') && resolve())
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @typedef {AddEventListenerOptions} Test~options
|
||||
* @property {AbortSignal} signal - funkis?
|
||||
*/
|
||||
|
||||
export default class Peer {
|
||||
/**
|
||||
* @param {{
|
||||
* polite: boolean,
|
||||
* trickle: boolean,
|
||||
* iceServers: RTCIceServer[]
|
||||
* signal: AbortSignal
|
||||
* }} [options]
|
||||
*/
|
||||
constructor (options = {}) {
|
||||
let { polite = true, trickle = true, quality = {} } = options
|
||||
|
||||
let { port1, port2 } = new MessageChannel()
|
||||
let send = msg => port2.postMessage(JSON.stringify(msg))
|
||||
|
||||
const pc = new RTCPeerConnection({
|
||||
iceServers: options?.iceServers || [{
|
||||
urls: [
|
||||
'stun:stun.l.google.com:19302',
|
||||
'stun:stun1.l.google.com:19302',
|
||||
'stun:global.stun.twilio.com:3478',
|
||||
'stun:stun.nextcloud.com:443'
|
||||
]
|
||||
}]
|
||||
})
|
||||
|
||||
const ctrl = new AbortController()
|
||||
|
||||
/** @type {any} dummy alias for AbortSignal to make TS happy */
|
||||
const signal = { signal: ctrl.signal }
|
||||
|
||||
pc.addEventListener('iceconnectionstatechange', () => {
|
||||
if (
|
||||
pc.iceConnectionState === 'disconnected' ||
|
||||
pc.iceConnectionState === 'failed'
|
||||
) {
|
||||
ctrl.abort()
|
||||
}
|
||||
}, signal)
|
||||
|
||||
const dc = pc.createDataChannel('both', { negotiated: true, id: 0 })
|
||||
|
||||
this.pc = pc
|
||||
this.dc = dc
|
||||
this.signal = ctrl.signal
|
||||
this.polite = polite
|
||||
this.signalingPort = port1
|
||||
|
||||
this.ready = new Promise(resolve => {
|
||||
dc.addEventListener('open', () => {
|
||||
// At this point we start to trickle over datachannel instead
|
||||
// we also close the message channel as we do not need it anymore
|
||||
trickle = true
|
||||
send = (msg) => dc.send(JSON.stringify(msg))
|
||||
port1.close()
|
||||
port2.close()
|
||||
this.ready = port2 = port1 = port2.onmessage = null
|
||||
resolve()
|
||||
}, { once: true, ...signal })
|
||||
})
|
||||
|
||||
pc.addEventListener('icecandidate', ({ candidate }) => {
|
||||
trickle && send({ candidate })
|
||||
}, { ...signal })
|
||||
|
||||
// The rest is the polite peer negotiation logic, copied from this blog
|
||||
|
||||
let makingOffer = false; let ignoreOffer = false
|
||||
|
||||
pc.addEventListener('negotiationneeded', async () => {
|
||||
makingOffer = true
|
||||
const offer = await pc.createOffer()
|
||||
if (pc.signalingState !== 'stable') return
|
||||
offer.sdp = setQuality(offer.sdp, quality)
|
||||
await pc.setLocalDescription(offer)
|
||||
makingOffer = false
|
||||
if (trickle) {
|
||||
send({ description: pc.localDescription })
|
||||
} else {
|
||||
await waitToCompleteIceGathering(pc)
|
||||
const description = pc.localDescription.toJSON()
|
||||
description.sdp = description.sdp.replace(/a=ice-options:trickle\s\n/g, '')
|
||||
send({ description })
|
||||
}
|
||||
}, { ...signal })
|
||||
|
||||
async function onmessage ({ data }) {
|
||||
const { description, candidate } = typeof data === 'string' ? JSON.parse(data) : data
|
||||
|
||||
if (description) {
|
||||
const offerCollision = description.type === 'offer' &&
|
||||
(makingOffer || pc.signalingState !== 'stable')
|
||||
|
||||
ignoreOffer = !this.polite && offerCollision
|
||||
if (ignoreOffer) {
|
||||
return
|
||||
}
|
||||
|
||||
if (offerCollision) {
|
||||
await Promise.all([
|
||||
pc.setLocalDescription({ type: 'rollback' }),
|
||||
pc.setRemoteDescription(description)
|
||||
])
|
||||
} else {
|
||||
try {
|
||||
(description.type === 'answer' && pc.signalingState === 'stable') ||
|
||||
await pc.setRemoteDescription(description)
|
||||
} catch (err) { }
|
||||
}
|
||||
if (description.type === 'offer') {
|
||||
const answ = await pc.createAnswer()
|
||||
answ.sdp = setQuality(answ.sdp, quality)
|
||||
await pc.setLocalDescription(answ)
|
||||
// Edge didn't set the state to 'new' after calling the above :[
|
||||
if (!trickle) await waitToCompleteIceGathering(pc, 'new')
|
||||
send({ description: pc.localDescription })
|
||||
}
|
||||
} else if (candidate) {
|
||||
await pc.addIceCandidate(candidate)
|
||||
}
|
||||
}
|
||||
|
||||
port2.onmessage = onmessage.bind(this)
|
||||
dc.addEventListener('message', onmessage.bind(this), { ...signal })
|
||||
}
|
||||
}
|
||||
|
||||
// I cannot describe the sheer anger and hatred I bear towards whoever came up with SDP
|
||||
function setQuality (sdp, opts = {}) {
|
||||
if (!sdp || (!opts.video && !opts.audio)) return sdp
|
||||
let newSDP = sdp
|
||||
if (opts.video) { // bitrate, codecs[]
|
||||
const videoData = sdp.matchAll(/^m=video.*SAVPF (.*)$/gm).next().value
|
||||
if (videoData && videoData[1]) {
|
||||
const RTPIndex = videoData[1]
|
||||
const RTPMaps = {}
|
||||
let last = null
|
||||
for (const [match, id, type] of [...sdp.matchAll(/^a=rtpmap:(\d{1,3}) (.*)\/90000$/gm)]) {
|
||||
if (type === 'rtx') {
|
||||
RTPMaps[last].push(id)
|
||||
} else {
|
||||
if (!RTPMaps[type]) RTPMaps[type] = []
|
||||
RTPMaps[type].push(id)
|
||||
last = type
|
||||
if (opts.video.bitrate) {
|
||||
const fmtp = `a=fmtp:${id} x-google-min-bitrate=${opts.video.bitrate}; x-google-max-bitrate=${opts.video.bitrate}\n`
|
||||
newSDP = newSDP.replace(match, fmtp + match)
|
||||
}
|
||||
}
|
||||
}
|
||||
const newIndex = Object.entries(RTPMaps).sort((a, b) => {
|
||||
const indexA = opts.video.codecs.indexOf(a[0])
|
||||
const indexB = opts.video.codecs.indexOf(b[0])
|
||||
return (indexA === -1 ? opts.video.codecs.length : indexA) - (indexB === -1 ? opts.video.codecs.length : indexB)
|
||||
}).map(value => {
|
||||
return value[1].join(' ')
|
||||
}).join(' ')
|
||||
newSDP = newSDP.replace(RTPIndex, newIndex)
|
||||
}
|
||||
}
|
||||
if (opts.audio) {
|
||||
const audioData = sdp.matchAll(/^a=rtpmap:(\d{1,3}) opus\/48000\/2$/gm).next().value
|
||||
if (audioData && audioData[0]) {
|
||||
const regex = new RegExp(`^a=fmtp:${audioData[1]}.*$`, 'gm')
|
||||
const FMTPData = sdp.match(regex)
|
||||
if (FMTPData && FMTPData[0]) {
|
||||
let newFMTPData = FMTPData[0].slice(0, FMTPData[0].indexOf(' ') + 1)
|
||||
newFMTPData += 'stereo=' + (opts.audio.stereo != null ? opts.audio.stereo : '1')
|
||||
newFMTPData += ';sprop-stereo=' + (opts.audio['sprop-stereo'] != null ? opts.audio['sprop-stereo'] : '1')
|
||||
|
||||
if (opts.audio.maxaveragebitrate != null) newFMTPData += '; maxaveragebitrate=' + (opts.audio.maxaveragebitrate || 128 * 1024 * 8)
|
||||
|
||||
if (opts.audio.maxplaybackrate != null) newFMTPData += '; maxplaybackrate=' + (opts.audio.maxplaybackrate || 128 * 1024 * 8)
|
||||
|
||||
if (opts.audio.cbr != null) newFMTPData += '; cbr=' + opts.audio.cbr
|
||||
|
||||
if (opts.audio.useinbandfec != null) newFMTPData += '; useinbandfec=' + opts.audio.useinbandfec
|
||||
|
||||
if (opts.audio.usedtx != null) newFMTPData += '; usedtx=' + opts.audio.usedtx
|
||||
|
||||
if (opts.audio.maxptime != null) newFMTPData += ';maxptime:' + opts.audio.maxptime
|
||||
if (opts.audio.minptime != null) newFMTPData += '; minptime:' + opts.audio.minptime
|
||||
newSDP = newSDP.replace(FMTPData[0], newFMTPData)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newSDP
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ function isMovie (media) {
|
|||
|
||||
function buildQuery (quality) {
|
||||
let query = `&qx=1&q=!("${exclusions.join('"|"')}")`
|
||||
if (quality) query += ` "'${quality}"`
|
||||
if (quality) query += ` "${quality}"`
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function parseRSSNodes (nodes) {
|
|||
|
||||
return {
|
||||
title: item.querySelector('title')?.textContent || '?',
|
||||
link: item.querySelector('link')?.textContent || '?',
|
||||
link: item.querySelector('enclosure')?.attributes.url.value || '?',
|
||||
seeders: item.querySelector('seeders')?.textContent ?? '?',
|
||||
leechers: item.querySelector('leechers')?.textContent ?? '?',
|
||||
downloads: item.querySelector('downloads')?.textContent ?? '?',
|
||||
|
|
@ -35,9 +35,9 @@ export function parseRSSNodes (nodes) {
|
|||
}
|
||||
|
||||
const rssmap = {
|
||||
SubsPlease: `${set.catURL}/?page=rss&c=0_0&f=0&u=subsplease&q=`,
|
||||
'Erai-raws [Multi-Sub]': `${set.catURL}/?page=rss&c=0_0&f=0&u=Erai-raws&q=`,
|
||||
'NC-Raws': `${set.catURL}/?page=rss&c=0_0&f=0&u=BraveSail&q=`
|
||||
SubsPlease: set.toshoURL + 'rss2?qx=1&q="[SubsPlease] "',
|
||||
'NC-Raws': set.toshoURL + 'rss2?qx=1&q="[NC-Raws] "',
|
||||
'Erai-raws [Multi-Sub]': set.toshoURL + 'rss2?qx=1&q="[Erai-raws] "'
|
||||
}
|
||||
export function getReleasesRSSurl (val) {
|
||||
const rss = rssmap[val] || val
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
const manager = new Sections()
|
||||
|
||||
for (const [title, url] of set.rssFeeds.reverse()) {
|
||||
for (const [title, url] of set.rssFeedsNew.reverse()) {
|
||||
const load = (page = 1, perPage = 6) => RSSManager.getMediaForRSS(page, perPage, url)
|
||||
manager.add([
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
async function testConnection () {
|
||||
try {
|
||||
for (let i = 0; i < 2; ++i) {
|
||||
// fetch cat twice, sometimes it will go tru once if ISP is shitty
|
||||
await fetch(set.catURL)
|
||||
// fetch twice, sometimes it will go tru once if ISP is shitty
|
||||
await fetch(set.toshoURL)
|
||||
}
|
||||
block = false
|
||||
} catch (e) {
|
||||
|
|
@ -20,9 +20,9 @@
|
|||
{#if block}
|
||||
<div class='w-full h-full left-0 z-50 position-absolute content-wrapper bg-dark d-flex align-items-center justify-content-center flex-column'>
|
||||
<div>
|
||||
<h1 class='font-weight-bold'>Could not connect to Nyaa!</h1>
|
||||
<div class='font-size-16'>This happens either because Nyaa is down, or because your ISP blocks Nyaa, the latter being more likely.</div>
|
||||
<div class='font-size-16'>Most features of Miru will not function correctly without being able to connect to Nyaa.</div>
|
||||
<h1 class='font-weight-bold'>Could not connect to Tosho!</h1>
|
||||
<div class='font-size-16'>This happens either because Tosho is down, or because your ISP blocks Tosho, the latter being more likely.</div>
|
||||
<div class='font-size-16'>Most features of Miru will not function correctly without being able to connect to Tosho.</div>
|
||||
<div class='font-size-16'>If you enable a VPN a restart might be required for it to take effect.</div>
|
||||
<!-- eslint-disable-next-line svelte/valid-compile -->
|
||||
<div class='font-size-16'>Visit <a class='text-primary pointer' on:click={() => { window.IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>this guide</a> for a tutorial on how to bypass ISP blocks.</div>
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
playerPause: true,
|
||||
playerAutocomplete: true,
|
||||
rssQuality: '1080',
|
||||
rssFeeds: [['New Releases', 'SubsPlease']],
|
||||
rssFeedsNew: [['New Releases', 'SubsPlease']],
|
||||
rssAutoplay: true,
|
||||
torrentSpeed: 10,
|
||||
torrentPersist: false,
|
||||
|
|
@ -19,20 +19,13 @@
|
|||
enableDoH: false,
|
||||
doHURL: 'https://cloudflare-dns.com/dns-query',
|
||||
disableSubtitleBlur: false,
|
||||
catURL: decodeURIComponent(atob('aHR0cHMlM0ElMkYlMkZueWFhLnNp')),
|
||||
toshoURL: decodeURIComponent(atob('aHR0cHM6Ly9mZWVkLmFuaW1ldG9zaG8ub3JnLw==')),
|
||||
showDetailsInRPC: true,
|
||||
cards: 'small'
|
||||
}
|
||||
localStorage.removeItem('relations') // TODO: remove
|
||||
export const set = { ...defaults, ...(JSON.parse(localStorage.getItem('settings')) || {}) }
|
||||
if (set.enableDoH) window.IPC.emit('doh', set.doHURL)
|
||||
if (!set.rssFeeds) { // TODO: remove ;-;
|
||||
if (set.rssFeed) {
|
||||
set.rssFeeds = [['New Releases', set.rssFeed]]
|
||||
} else {
|
||||
set.rssFeeds = [['New Releases', 'SubsPlease']]
|
||||
}
|
||||
}
|
||||
window.addEventListener('paste', ({ clipboardData }) => {
|
||||
if (clipboardData.items?.[0]) {
|
||||
if (clipboardData.items[0].type === 'text/plain' && clipboardData.items[0].kind === 'string') {
|
||||
|
|
@ -341,7 +334,7 @@
|
|||
</Tab>
|
||||
<Tab>
|
||||
<div class='root p-20 m-20'>
|
||||
{#each settings.rssFeeds as _, i}
|
||||
{#each settings.rssFeedsNew as _, i}
|
||||
<div
|
||||
class='input-group mb-10 w-700 form-control-lg'
|
||||
data-toggle='tooltip'
|
||||
|
|
@ -350,20 +343,20 @@
|
|||
<div class='input-group-prepend'>
|
||||
<span class='input-group-text w-100 justify-content-center'>Feed</span>
|
||||
</div>
|
||||
<input type='text' class='form-control form-control-lg w-150 flex-reset' placeholder='New Releases' autocomplete='off' bind:value={settings.rssFeeds[i][0]} />
|
||||
<input id='rss-feed-{i}' type='text' list='rss-feed-list-{i}' class='w-400 form-control form-control-lg' placeholder={set.catURL + '/?page=rss&c=0_0&f=0&q='} autocomplete='off' bind:value={settings.rssFeeds[i][1]} />
|
||||
<input type='text' class='form-control form-control-lg w-150 flex-reset' placeholder='New Releases' autocomplete='off' bind:value={settings.rssFeedsNew[i][0]} />
|
||||
<input id='rss-feed-{i}' type='text' list='rss-feed-list-{i}' class='w-400 form-control form-control-lg' placeholder={set.toshoURL + 'rss2?qx=1&q="[SubsPlease] "'} autocomplete='off' bind:value={settings.rssFeedsNew[i][1]} />
|
||||
<datalist id='rss-feed-list-{i}'>
|
||||
<option value='SubsPlease'>{set.catURL + '/?page=rss&c=0_0&f=0&u=subsplease&q='}</option>
|
||||
<option value='NC-Raws'>{set.catURL + '/?page=rss&c=0_0&f=0&u=BraveSail&q='}</option>
|
||||
<option value='Erai-raws [Multi-Sub]'>{set.catURL + '/?page=rss&c=0_0&f=0&u=Erai-raws&q='}</option>
|
||||
<option value='SubsPlease'>{set.toshoURL + 'rss2?qx=1&q="[SubsPlease] "'}</option>
|
||||
<option value='NC-Raws'>{set.toshoURL + 'rss2?qx=1&q="[NC-Raws] "'}</option>
|
||||
<option value='Erai-raws [Multi-Sub]'>{set.toshoURL + 'rss2?qx=1&q="[Erai-raws] "'}</option>
|
||||
</datalist>
|
||||
<div class='input-group-append'>
|
||||
<button type='button' on:click={() => { settings.rssFeeds.splice(i, 1); settings.rssFeeds = settings.rssFeeds }} class='btn btn-danger btn-lg input-group-append'>Remove</button>
|
||||
<button type='button' on:click={() => { settings.rssFeedsNew.splice(i, 1); settings.rssFeedsNew = settings.rssFeedsNew }} class='btn btn-danger btn-lg input-group-append'>Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class='input-group input-group-lg form-control-lg mb-10 w-500'>
|
||||
<button type='button' on:click={() => { settings.rssFeeds[settings.rssFeeds.length] = ['New Releases', null] }} class='btn btn-lg btn-primary mb-10'>Add Feed</button>
|
||||
<button type='button' on:click={() => { settings.rssFeedsNew[settings.rssFeedsNew.length] = ['New Releases', null] }} class='btn btn-lg btn-primary mb-10'>Add Feed</button>
|
||||
</div>
|
||||
<div class='input-group mb-10 w-300 form-control-lg' data-toggle='tooltip' data-placement='top' data-title='What Quality To Find Torrents In'>
|
||||
<div class='input-group-prepend'>
|
||||
|
|
@ -409,9 +402,9 @@
|
|||
data-placement='bottom'
|
||||
data-title='URL For The Cat, Change If You Want To Use Your Own Proxy'>
|
||||
<div class='input-group-prepend'>
|
||||
<span class='input-group-text w-150 justify-content-center'>Cat URL</span>
|
||||
<span class='input-group-text w-150 justify-content-center'>Tosho URL</span>
|
||||
</div>
|
||||
<input type='text' class='form-control' bind:value={settings.catURL} placeholder={defaults.catURL} />
|
||||
<input type='text' class='form-control' bind:value={settings.toshoURL} placeholder={defaults.toshoURL} />
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
|
|
|
|||
Loading…
Reference in a new issue