feat: NNTP streaming

This commit is contained in:
ThaUnknown 2025-10-09 00:46:19 +02:00
parent de3da8acf8
commit f2b6f12fcb
No known key found for this signature in database
8 changed files with 75 additions and 19 deletions

View file

@ -1,6 +1,6 @@
{
"name": "ui",
"version": "6.4.150",
"version": "6.4.151",
"license": "BUSL-1.1",
"private": true,
"packageManager": "pnpm@9.15.5",

View file

@ -185,7 +185,7 @@ importers:
version: 1.8.13(@gql.tada/svelte-support@1.0.1(svelte@4.2.19)(typescript@5.9.2))(graphql@16.10.0)(typescript@5.9.2)
hayase-extensions:
specifier: github:hayase-app/extensions
version: https://codeload.github.com/hayase-app/extensions/tar.gz/0fad214a4a0aacd826c0801255ced65bd1d49a13
version: https://codeload.github.com/hayase-app/extensions/tar.gz/2c43ec0bf157733901be1ad9188b64933aac2e15
jassub:
specifier: ^1.8.6
version: 1.8.6
@ -197,7 +197,7 @@ importers:
version: 2.1.3
native:
specifier: github:hayase-app/native
version: https://codeload.github.com/hayase-app/native/tar.gz/b77747898ca7e4991fac752559ea159c8c0f5083
version: https://codeload.github.com/hayase-app/native/tar.gz/e8045ec8878086117a9c3b52dcff069f4b9b48e8
rollup-plugin-license:
specifier: ^3.6.0
version: 3.6.0(picomatch@4.0.3)(rollup@4.40.2)
@ -1711,8 +1711,8 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hayase-extensions@https://codeload.github.com/hayase-app/extensions/tar.gz/0fad214a4a0aacd826c0801255ced65bd1d49a13:
resolution: {tarball: https://codeload.github.com/hayase-app/extensions/tar.gz/0fad214a4a0aacd826c0801255ced65bd1d49a13}
hayase-extensions@https://codeload.github.com/hayase-app/extensions/tar.gz/2c43ec0bf157733901be1ad9188b64933aac2e15:
resolution: {tarball: https://codeload.github.com/hayase-app/extensions/tar.gz/2c43ec0bf157733901be1ad9188b64933aac2e15}
version: 1.0.6
idb-keyval@6.2.2:
@ -2051,8 +2051,8 @@ packages:
engines: {node: ^18 || >=20}
hasBin: true
native@https://codeload.github.com/hayase-app/native/tar.gz/b77747898ca7e4991fac752559ea159c8c0f5083:
resolution: {tarball: https://codeload.github.com/hayase-app/native/tar.gz/b77747898ca7e4991fac752559ea159c8c0f5083}
native@https://codeload.github.com/hayase-app/native/tar.gz/e8045ec8878086117a9c3b52dcff069f4b9b48e8:
resolution: {tarball: https://codeload.github.com/hayase-app/native/tar.gz/e8045ec8878086117a9c3b52dcff069f4b9b48e8}
version: 1.0.0
natural-compare@1.4.0:
@ -4560,7 +4560,7 @@ snapshots:
dependencies:
function-bind: 1.1.2
hayase-extensions@https://codeload.github.com/hayase-app/extensions/tar.gz/0fad214a4a0aacd826c0801255ced65bd1d49a13: {}
hayase-extensions@https://codeload.github.com/hayase-app/extensions/tar.gz/2c43ec0bf157733901be1ad9188b64933aac2e15: {}
idb-keyval@6.2.2: {}
@ -4865,7 +4865,7 @@ snapshots:
nanoid@5.1.5: {}
native@https://codeload.github.com/hayase-app/native/tar.gz/b77747898ca7e4991fac752559ea159c8c0f5083: {}
native@https://codeload.github.com/hayase-app/native/tar.gz/e8045ec8878086117a9c3b52dcff069f4b9b48e8: {}
natural-compare@1.4.0: {}

View file

@ -4,6 +4,8 @@
<head>
<meta charset="utf-8" />
<title>Hayase</title>
<link rel="preconnect" href="https://graphql.anilist.co/">
<link rel="preconnect" href="https://www.youtube-nocookie.com">
<link rel="icon" href="%sveltekit.assets%/logo_white_fit.svg" />
%sveltekit.head%
</head>

View file

@ -13,5 +13,6 @@
<div class='text-muted-foreground'>
{location.country}
</div>
{:catch}
{/await}
</div>

View file

@ -1,6 +1,7 @@
import anitomyscript, { type AnitomyResult } from 'anitomyscript'
import Debug from 'debug'
import { get } from 'svelte/store'
import { toast } from 'svelte-sonner'
import { dedupeAiring, episodes, isMovie, type Media, getParentForSpecial, isSingleEpisode } from '../anilist'
import { episodes as _episodes } from '../anizip'
@ -203,13 +204,14 @@ export const extensions = new class Extensions {
debug(`Checking ${Object.keys(workers).length} extensions for ${media.id}:${media.title?.userPreferred} ${episode} ${resolution} ${checkMovie ? 'movie' : ''} ${checkBatch ? 'batch' : ''}`)
for (const [id, worker] of Object.entries(workers)) {
if (!extopts[id]!.enabled) continue
const thisExtOpts = extopts[id]!
if (!thisExtOpts.enabled) continue
if (configs[id]!.type !== 'torrent') continue
try {
const promises: Array<Promise<TorrentResult[]>> = []
promises.push(worker.single(options))
if (checkMovie) promises.push(worker.movie(options))
if (checkBatch) promises.push(worker.batch(options))
promises.push(worker.single(options, thisExtOpts.options))
if (checkMovie) promises.push(worker.movie(options, thisExtOpts.options))
if (checkBatch) promises.push(worker.batch(options, thisExtOpts.options))
for (const result of await Promise.allSettled(promises)) {
if (result.status === 'fulfilled') {
@ -246,6 +248,37 @@ export const extensions = new class Extensions {
return { results: navigator.onLine ? await this.updatePeerCounts(deduped) : deduped, errors }
}
async getNZBResultsFromExtensions (hash: string) {
await storage.modules
const workers = storage.workers
const results: Array<{ nzb: string, options: Record<string, string> }> = []
const errors: Array<{ error: Error, extension: string }> = []
const extopts = get(extensionOptions)
const configs = get(saved)
for (const [id, worker] of Object.entries(workers)) {
const thisExtOpts = extopts[id]!
if (!thisExtOpts.enabled) continue
if (configs[id]!.type !== 'nzb') continue
try {
const nzb = await worker.query(hash, thisExtOpts.options)
if (!nzb) continue
results.push({ nzb, options: thisExtOpts.options })
} catch (error) {
errors.push({ error: error as Error, extension: id })
}
}
if (errors.length) {
for (const { error, extension } of errors) {
toast.error(`Error fetching NZB from ${configs[extension]?.name ?? extension}`, { description: error.message })
}
}
return results
}
async updatePeerCounts <T extends TorrentResult[]> (entries: T): Promise<T> {
debug(`Updating peer counts for ${entries.length} entries`)

View file

@ -1,15 +1,15 @@
import { finalizer } from 'abslink'
import { expose } from 'abslink/w3c'
import type { SearchFunction, TorrentSource } from 'hayase-extensions'
import type { SearchFunction, TorrentSource, NZBorURLSource } from 'hayase-extensions'
export default expose({
mod: null as unknown as Promise<TorrentSource>,
mod: null as unknown as Promise<TorrentSource | NZBorURLSource>,
construct (code: string) {
this.mod = this.load(code)
},
async load (code: string): Promise<TorrentSource> {
async load (code: string): Promise<TorrentSource | NZBorURLSource> {
// WARN: unsafe eval
const url = URL.createObjectURL(new Blob([code], { type: 'application/javascript' }))
const module = await import(/* @vite-ignore */url)
@ -22,15 +22,19 @@ export default expose({
},
async single (...args: Parameters<SearchFunction>): ReturnType<SearchFunction> {
return await ((await this.mod)).single(...args)
return await ((await this.mod) as TorrentSource).single(...args)
},
async batch (...args: Parameters<SearchFunction>): ReturnType<SearchFunction> {
return await ((await this.mod)).batch(...args)
return await ((await this.mod) as TorrentSource).batch(...args)
},
async movie (...args: Parameters<SearchFunction>): ReturnType<SearchFunction> {
return await ((await this.mod)).movie(...args)
return await ((await this.mod) as TorrentSource).movie(...args)
},
async query (...args: Parameters<NZBorURLSource['query']>) {
return await ((await this.mod) as NZBorURLSource).query(...args)
},
async test () {

View file

@ -138,6 +138,7 @@ export default Object.assign<Native, Partial<Native>>({
navigate: async () => undefined,
downloadProgress: async () => undefined,
updateProgress: async () => undefined,
createNZB: async () => undefined,
torrentInfo: async (): Promise<TorrentInfo> => ({
name: '',
progress: 0,

View file

@ -2,8 +2,10 @@ import Debug from 'debug'
import { writable } from 'simple-store-svelte'
import { get } from 'svelte/store'
import { persisted } from 'svelte-persisted-store'
import { toast } from 'svelte-sonner'
import client from '../auth/client'
import { extensions } from '../extensions'
import native from '../native'
import { SUPPORTS } from '../settings'
import { w2globby } from '../w2g/lobby'
@ -97,8 +99,21 @@ export const server = new class ServerClient {
const result = { id, media, episode, files: await native.playTorrent(id, media.id, episode) }
debug('torrent play result', result)
this.downloaded.value = this.cachedSet()
this._addNZBs(result.files[0]!.hash)
return result
}
async _addNZBs (hash: string) {
const nzbs = await extensions.getNZBResultsFromExtensions(hash)
for (const { nzb, options } of nzbs) {
try {
await native.createNZB(hash, nzb, options.domain!, Number(options.port!), options.username!, options.password!, Number(options.poolSize!))
} catch (e) {
toast.error('Failed to add NZB', { description: (e as Error).message })
}
}
}
}()
requestIdleCallback(() => {