Pipris is an extensible MPRIS scrobbler written with Deno.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

handle rpc reconnection

Clay 681983e3 07826b55

+42 -5
+1 -1
README.md
··· 1 - # Pipris ![Version 0.1.0](https://img.shields.io/badge/version-0.1.0-blue) 1 + # Pipris ![Version 0.1.1](https://img.shields.io/badge/version-0.1.1-blue) 2 2 3 3 Pipris is an extensible MPRIS scrobbler written with Deno. It was originally created for 4 4 [teal.fm](https://teal.fm), but can be extended to support a wide variety of
+40 -3
src/modules/discord.js
··· 10 10 let client; 11 11 let config; 12 12 let lastArtUrl = null; 13 + let lastData = null; 14 + let intentionallyClosed = false; 15 + let reconnectTimer = null; 13 16 14 17 // Cache: local file path → { url, uploadedAt } 15 18 const artCache = new Map(); ··· 56 59 } 57 60 } 58 61 62 + async function connect() { 63 + client = new RPCClient(config.clientId, false); 64 + await client.init(); 65 + console.log("[discord] Connected to Discord RPC"); 66 + } 67 + 68 + function scheduleReconnect(delayMs = 5000) { 69 + if (intentionallyClosed || reconnectTimer) return; 70 + console.log(`[discord] Scheduling reconnect in ${delayMs / 1000}s...`); 71 + reconnectTimer = setTimeout(async () => { 72 + reconnectTimer = null; 73 + if (intentionallyClosed) return; 74 + try { 75 + await connect(); 76 + console.log("[discord] Reconnected to Discord RPC"); 77 + if (lastData) { 78 + await onData(lastData); 79 + } 80 + } catch (err) { 81 + console.error(`[discord] Reconnect failed: ${err.message}`); 82 + scheduleReconnect(Math.min(delayMs * 2, 60000)); 83 + } 84 + }, delayMs); 85 + } 86 + 59 87 export async function init(cfg) { 60 88 if (!cfg || !cfg.clientId) { 61 89 throw new Error("Missing config/discord.json (needs clientId)"); 62 90 } 63 91 config = cfg; 92 + intentionallyClosed = false; 64 93 65 - client = new RPCClient(config.clientId, false); 66 - await client.init(); 67 - console.log("[discord] Connected to Discord RPC"); 94 + await connect(); 68 95 } 69 96 70 97 export async function onData(data) { 71 98 if (!client) return; 99 + lastData = data; 72 100 73 101 const artistString = data.artists.map((a) => a.artistName).join(", "); 74 102 const paused = data.playbackStatus !== "Playing"; ··· 125 153 client.setActivity(buildActivity(cachedUrl, false)); 126 154 } catch (err) { 127 155 console.error(`[discord] Failed to set activity: ${err.message}`); 156 + client = null; 157 + scheduleReconnect(); 158 + return; 128 159 } 129 160 130 161 // If artUrl is a local file and wasn't cached, upload in background and update ··· 144 175 } 145 176 146 177 export function onClear() { 178 + intentionallyClosed = true; 179 + lastData = null; 180 + if (reconnectTimer) { 181 + clearTimeout(reconnectTimer); 182 + reconnectTimer = null; 183 + } 147 184 if (!client) return; 148 185 try { 149 186 client.close();
+1 -1
src/modules/teal.js
··· 1 1 import { Client } from "@atcute/client"; 2 2 import { PasswordSession } from "@atcute/password-session"; 3 3 4 - const AGENT_STRING = "pipris/0.1.0 (https://tangled.org/clay.rip/pipris)"; 4 + const AGENT_STRING = "pipris/0.1.1 (https://tangled.org/clay.rip/pipris)"; 5 5 const PLAY_FINISH_TOLERANCE_MS = 5000; 6 6 7 7 let rpc;