diff --git a/extensions/js.neutralino.discordrpc/bridge.ps1 b/extensions/js.neutralino.discordrpc/bridge.ps1 index 73b074e..c21e46d 100644 --- a/extensions/js.neutralino.discordrpc/bridge.ps1 +++ b/extensions/js.neutralino.discordrpc/bridge.ps1 @@ -1,8 +1,8 @@ -# bridge.ps1 - JSON Depth Fix -# $Log = Join-Path $PSScriptRoot "bridge_final.log" -function Log($m) { } +# bridge.ps1 - Diagnostic Version +$Log = Join-Path $PSScriptRoot "bridge.log" +function Log($m) { Add-Content $Log "$(Get-Date -f 'HH:mm:ss') - $m" } -Log "--- START (DEPTH FIX) ---" +Log "--- START (DIAGNOSTIC) ---" # 1. PID $p = Get-Process Monochrome -ErrorAction SilentlyContinue | Select-Object -First 1 @@ -37,16 +37,27 @@ $h = New-Object byte[] 8; if ($pipe.Read($h, 0, 8) -eq 8) { Log "Handshake OK" } -function Set-Activity($d, $s, $img) { +function Set-Activity($d, $s, $img, $start, $end, $large_text, $small_img, $small_txt) { $activity = @{ details = [string]$d state = [string]$s type = 2 assets = @{ large_image = if ($img -and $img.StartsWith("http")) { [string]$img } else { "monochrome" } - large_text = "Monochrome" + large_text = if ($large_text) { [string]$large_text } else { "Monochrome" } } } + + if ($small_img) { + $activity.assets.small_image = [string]$small_img + $activity.assets.small_text = [string]$small_txt + } + + if ($start -or $end) { + $activity.timestamps = @{} + if ($start) { $activity.timestamps.start = [long]$start } + if ($end) { $activity.timestamps.end = [long]$end } + } # CRITICAL: -Depth 10 ensures 'assets' is not stringified as a class name $payload = @{ @@ -59,7 +70,7 @@ function Set-Activity($d, $s, $img) { } Start-Sleep -Seconds 1 -Set-Activity "Idling" "Monochrome" $null +Set-Activity "Idling" "Monochrome" $null $null $null $null $null $null # 4. Config & WS $line = [Console]::In.ReadLine() @@ -83,9 +94,9 @@ while ($ws.State -eq "Open") { $raw = [System.Text.Encoding]::UTF8.GetString($buf, 0, $task.Result.Count) $msg = $raw | ConvertFrom-Json if ($msg.event -eq "discord:update") { - Set-Activity $msg.data.details $msg.data.state $msg.data.largeImageKey + Set-Activity $msg.data.details $msg.data.state $msg.data.largeImageKey $msg.data.startTimestamp $msg.data.endTimestamp $msg.data.largeImageText $msg.data.smallImageKey $msg.data.smallImageText } - elseif ($msg.event -eq "discord:clear") { Set-Activity "Idling" "Monochrome" $null } + elseif ($msg.event -eq "discord:clear") { Set-Activity "Idling" "Monochrome" $null $null $null $null $null $null } } catch {} } } diff --git a/extensions/js.neutralino.discordrpc/bridge.py b/extensions/js.neutralino.discordrpc/bridge.py index 9faed82..240bf23 100644 --- a/extensions/js.neutralino.discordrpc/bridge.py +++ b/extensions/js.neutralino.discordrpc/bridge.py @@ -24,9 +24,9 @@ def recv_packet(s): return json.loads(payload.decode('utf-8')) except: return None -def set_activity(ds, pid, details, state, img=None): +def set_activity(ds, pid, details, state, img=None, start=None, end=None, large_text=None, small_img=None, small_txt=None): global LAST_STATUS - current = f"{details}-{state}-{img}" + current = f"{details}-{state}-{img}-{start}-{end}-{large_text}-{small_img}-{small_txt}" if current == LAST_STATUS: return LAST_STATUS = current @@ -36,9 +36,18 @@ def set_activity(ds, pid, details, state, img=None): "type": 2, # Listening "assets": { "large_image": img if img and img.startswith('http') else "monochrome", - "large_text": "Monochrome" + "large_text": str(large_text or "Monochrome") } } + + if small_img: + activity["assets"]["small_image"] = str(small_img) + activity["assets"]["small_text"] = str(small_txt or "") + + if start or end: + activity["timestamps"] = {} + if start: activity["timestamps"]["start"] = int(start) + if end: activity["timestamps"]["end"] = int(end) send_packet(ds, 1, { "cmd": "SET_ACTIVITY", @@ -119,7 +128,7 @@ def main(): msg = json.loads(data.decode('utf-8')) if msg['event'] == 'discord:update': d = msg['data'] - set_activity(ds, ppid, d.get('details'), d.get('state'), d.get('largeImageKey')) + set_activity(ds, ppid, d.get('details'), d.get('state'), d.get('largeImageKey'), d.get('startTimestamp'), d.get('endTimestamp'), d.get('largeImageText'), d.get('smallImageKey'), d.get('smallImageText')) elif msg['event'] == 'discord:clear': set_activity(ds, ppid, "Idling", "Monochrome") elif msg['event'] == 'windowClose': diff --git a/js/app.js b/js/app.js index 8200282..76b3af7 100644 --- a/js/app.js +++ b/js/app.js @@ -1,5 +1,4 @@ //js/app.js -console.log('[App] Script loaded'); import { LosslessAPI } from './api.js'; import { apiSettings, diff --git a/js/discord-rpc.js b/js/discord-rpc.js index eb2708f..e3983cf 100644 --- a/js/discord-rpc.js +++ b/js/discord-rpc.js @@ -1,8 +1,6 @@ import { getTrackTitle, getTrackArtists } from './utils.js'; export function initializeDiscordRPC(player) { - console.log('[DiscordRPC] Initializing...'); - const EXTENSION_ID = 'js.neutralino.discordrpc'; function sendUpdate(track, isPaused = false) { @@ -27,10 +25,12 @@ export function initializeDiscordRPC(player) { if (!isPaused && track.duration) { const now = Date.now(); const elapsed = player.audio.currentTime * 1000; + const remaining = (track.duration - player.audio.currentTime) * 1000; + data.startTimestamp = Math.floor((now - elapsed) / 1000); + data.endTimestamp = Math.floor((now + remaining) / 1000); } - console.log('[DiscordRPC] Dispatching to', EXTENSION_ID, data); Neutralino.events.broadcast('discord:update', data).catch((e) => console.error('Broadcast failed', e)); Neutralino.extensions .dispatch(EXTENSION_ID, 'discord:update', data) @@ -42,7 +42,6 @@ export function initializeDiscordRPC(player) { if (player.currentTrack) { sendUpdate(player.currentTrack, player.audio.paused); } else { - console.log('[DiscordRPC] Sending idling heartbeat...'); const idlingData = { details: 'Idling', state: 'Monochrome', diff --git a/js/neutralino-bridge.js b/js/neutralino-bridge.js new file mode 100644 index 0000000..d94b72e --- /dev/null +++ b/js/neutralino-bridge.js @@ -0,0 +1,79 @@ +// js/neutralino-bridge.js + +const listeners = new Map(); + +// Listen for events from the Shell (Parent) +window.addEventListener('message', (event) => { + if (event.data?.type === 'NL_EVENT') { + const { eventName, detail } = event.data; + if (listeners.has(eventName)) { + listeners.get(eventName).forEach((handler) => { + try { + handler(detail); + } catch (e) { + console.error('[Bridge] Error in event handler:', e); + } + }); + } + } +}); + +export const init = async () => { + // Notify Shell we are ready + window.parent.postMessage({ type: 'NL_INIT' }, '*'); +}; + +export const events = { + on: (eventName, handler) => { + if (!listeners.has(eventName)) { + listeners.set(eventName, []); + } + listeners.get(eventName).push(handler); + }, + off: (eventName, handler) => { + if (!listeners.has(eventName)) return; + const handlers = listeners.get(eventName); + const index = handlers.indexOf(handler); + if (index > -1) handlers.splice(index, 1); + }, + broadcast: async (eventName, data) => { + window.parent.postMessage({ type: 'NL_BROADCAST', eventName, data }, '*'); + }, +}; + +export const extensions = { + dispatch: async (extensionId, eventName, data) => { + window.parent.postMessage({ type: 'NL_EXTENSION', extensionId, eventName, data }, '*'); + }, +}; + +export const app = { + exit: async () => { + window.parent.postMessage({ type: 'NL_APP_EXIT' }, '*'); + }, +}; + +const _window = { + minimize: async () => { + window.parent.postMessage({ type: 'NL_WINDOW_MIN' }, '*'); + }, + maximize: async () => { + window.parent.postMessage({ type: 'NL_WINDOW_MAX' }, '*'); + }, + isVisible: async () => { + return true; // Mock response + }, + setTitle: async (title) => { + window.parent.postMessage({ type: 'NL_WINDOW_SET_TITLE', title }, '*'); + } +}; + +// Expose generically for other modules +export { _window as window }; +export default { + init, + events, + extensions, + app, + window: _window +}; diff --git a/public/neutralino_loader.html b/public/neutralino_loader.html new file mode 100644 index 0000000..fc890fd --- /dev/null +++ b/public/neutralino_loader.html @@ -0,0 +1,147 @@ + + + +
+ +