import { Observable } from 'rxjs' import type { DLMetadata, LiveStreamProgress, RPCRequest, RPCResponse, RPCResult } from '../types' import { WebSocketSubject, webSocket } from 'rxjs/webSocket' type DownloadRequestArgs = { url: string, args: string, pathOverride?: string, renameTo?: string, playlist?: boolean } export class RPCClient { private seq: number private httpEndpoint: string private readonly _socket$: WebSocketSubject private readonly token?: string constructor(httpEndpoint: string, webSocketEndpoint: string, token?: string) { this.seq = 0 this.httpEndpoint = httpEndpoint this._socket$ = webSocket({ url: token ? `${webSocketEndpoint}?token=${token}` : webSocketEndpoint }) this.token = token } public get socket$(): Observable> { return this._socket$ } private incrementSeq() { return String(this.seq++) } private send(req: RPCRequest) { this._socket$.next({ ...req, id: this.incrementSeq(), }) } private argsSanitizer(args: string): string[] { const splitOnlyWhitespaces = /[^\s"']+|"([^"]*)"|'([^']*)'/gm return args .match(splitOnlyWhitespaces) ?.map(a => a.trim()) .filter(Boolean) ?? [] } private async sendHTTP(req: RPCRequest) { const res = await fetch(this.httpEndpoint, { method: 'POST', headers: { 'X-Authentication': this.token ?? '' }, body: JSON.stringify({ ...req, id: this.incrementSeq(), }) }) const data: RPCResponse = await res.json() return data } public download(req: DownloadRequestArgs) { if (!req.url) { return } const rename = req.args.includes('-o') ? req.args .substring(req.args.indexOf('-o')) .replaceAll("'", '') .replaceAll('"', '') .split('-o') .map(s => s.trim()) .join('') .split(' ') .at(0) ?? '' : '' const sanitizedArgs = this.argsSanitizer( req.args .replace('-o', '') .replace(rename, '') ) if (req.playlist) { return this.sendHTTP({ method: 'Service.ExecPlaylist', params: [{ URL: req.url, Params: sanitizedArgs, Path: req.pathOverride, Rename: req.renameTo || rename, }] }) } this.sendHTTP({ method: 'Service.Exec', params: [{ URL: req.url.split('?list').at(0)!, Params: sanitizedArgs, Path: req.pathOverride, Rename: req.renameTo || rename, }] }) } public formats(url: string) { if (url) { return this.sendHTTP({ method: 'Service.Formats', params: [{ URL: url.split('?list').at(0)!, }] }) } } public running() { this.send({ method: 'Service.Running', params: [], }) } public kill(id: string) { return this.sendHTTP({ method: 'Service.Kill', params: [id], }) } public clear(id: string) { return this.sendHTTP({ method: 'Service.Clear', params: [id], }) } public killAll() { return this.sendHTTP({ method: 'Service.KillAll', params: [], }) } public freeSpace() { return this.sendHTTP({ method: 'Service.FreeSpace', params: [], }) } public directoryTree() { return this.sendHTTP({ method: 'Service.DirectoryTree', params: [], }) } public execLivestream(url: string) { return this.sendHTTP({ method: 'Service.ExecLivestream', params: [{ URL: url }] }) } public progressLivestream() { return this.sendHTTP({ method: 'Service.ProgressLivestream', params: [] }) } public killLivestream(url: string) { return this.sendHTTP({ method: 'Service.KillLivestream', params: [url] }) } public killAllLivestream() { return this.sendHTTP({ method: 'Service.KillAllLivestream', params: [] }) } public updateExecutable() { return this.sendHTTP({ method: 'Service.UpdateExecutable', params: [] }) } public clearCompleted() { return this.sendHTTP({ method: 'Service.ClearCompleted', params: [] }) } }