feat: use tosho instead of nyaa for RSS

This commit is contained in:
ThaUnknown 2023-07-07 16:02:15 +02:00
parent 0c55d543f1
commit 4163db7a0d
8 changed files with 27 additions and 232 deletions

View file

@ -4,5 +4,6 @@
"paths": {
"@/*": ["src/renderer/*"],
}
}
},
"exclude": ["node_modules", "**/node_modules", "dist", "build"]
}

View file

@ -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 />

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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([
{

View file

@ -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>

View file

@ -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>