feat(service-integrations): phases 9-10 — media integrations + Planka
- Emby: now playing, library stats, recently added, active streams - Immich: library stats, recent uploads with formatted storage - Deluge: active torrents with progress, transfer speed, disk space gauge - MeTube: download queue progress (no auth required) - Planka: my cards, overdue cards with red badges, board summary - All 11 integrations registered in registry
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { fetchWithTimeout } from '../base.js';
|
||||
|
||||
export interface DelugeTorrent {
|
||||
readonly name: string;
|
||||
readonly progress: number;
|
||||
readonly state: string;
|
||||
readonly download_payload_rate: number;
|
||||
readonly upload_payload_rate: number;
|
||||
readonly eta: number;
|
||||
readonly total_size: number;
|
||||
}
|
||||
|
||||
export interface DelugeUIResponse {
|
||||
readonly result: {
|
||||
readonly torrents: Record<string, DelugeTorrent>;
|
||||
readonly stats: {
|
||||
readonly download_rate: number;
|
||||
readonly upload_rate: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface DelugeFreeSpaceResponse {
|
||||
readonly result: number;
|
||||
}
|
||||
|
||||
function rpcUrl(appUrl: string): string {
|
||||
return `${appUrl.replace(/\/$/, '')}/json`;
|
||||
}
|
||||
|
||||
async function rpcCall(
|
||||
url: string,
|
||||
method: string,
|
||||
params: unknown[],
|
||||
id: number,
|
||||
cookie?: string
|
||||
): Promise<Response> {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (cookie) {
|
||||
headers['Cookie'] = cookie;
|
||||
}
|
||||
const res = await fetchWithTimeout(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ method, params, id })
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Deluge API returned ${res.status}`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function extractCookie(res: Response): string {
|
||||
const setCookie = res.headers.get('set-cookie');
|
||||
if (!setCookie) return '';
|
||||
const match = setCookie.match(/^([^;]+)/);
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
|
||||
export async function authenticate(appUrl: string, password: string): Promise<string> {
|
||||
const url = rpcUrl(appUrl);
|
||||
const res = await rpcCall(url, 'auth.login', [password], 1);
|
||||
const body = await res.json();
|
||||
|
||||
if (!body.result) {
|
||||
throw new Error('Deluge authentication failed — invalid password');
|
||||
}
|
||||
|
||||
const cookie = extractCookie(res);
|
||||
if (!cookie) {
|
||||
throw new Error('Deluge did not return a session cookie');
|
||||
}
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
export async function fetchUIData(
|
||||
appUrl: string,
|
||||
cookie: string
|
||||
): Promise<DelugeUIResponse> {
|
||||
const url = rpcUrl(appUrl);
|
||||
const fields = [
|
||||
'name',
|
||||
'progress',
|
||||
'state',
|
||||
'download_payload_rate',
|
||||
'upload_payload_rate',
|
||||
'eta',
|
||||
'total_size'
|
||||
];
|
||||
const res = await rpcCall(url, 'web.update_ui', [fields, {}], 2, cookie);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function fetchFreeSpace(
|
||||
appUrl: string,
|
||||
cookie: string,
|
||||
path = '/'
|
||||
): Promise<DelugeFreeSpaceResponse> {
|
||||
const url = rpcUrl(appUrl);
|
||||
const res = await rpcCall(url, 'core.get_free_space', [path], 3, cookie);
|
||||
return res.json();
|
||||
}
|
||||
Reference in New Issue
Block a user