Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions bridge-nw/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,61 @@ const startBtn = document.getElementById("startBtn");
const stopBtn = document.getElementById("stopBtn");
const healthBtn = document.getElementById("healthBtn");
const probeBtn = document.getElementById("probeBtn");
const nerdModeToggleEl = document.getElementById("nerdModeToggle");
const updateIntervalInputEl = document.getElementById("updateIntervalInput");
const statusEl = document.getElementById("status");
const logEl = document.getElementById("log");

let bridgeProc = null;
let tray = null;
let isQuitting = false;
let healthMonitorTimer = null;
const SETTINGS_FILE = path.join(getAppDataHome(), APP_NAME, "data", "bridge-settings.json");
const DEFAULT_UPDATE_INTERVAL_MS = 10000;

function normalizeUpdateIntervalMs(value) {
const n = Number.parseInt(String(value || ""), 10);
if (!Number.isFinite(n)) return DEFAULT_UPDATE_INTERVAL_MS;
return Math.max(1000, Math.min(60000, Math.round(n)));
}

function ensureBridgeDataDir() {
fs.mkdirSync(path.dirname(SETTINGS_FILE), { recursive: true });
}

function loadBridgeSettings() {
try {
if (!fs.existsSync(SETTINGS_FILE)) {
return { nerdModeEnabled: false, updateIntervalMs: DEFAULT_UPDATE_INTERVAL_MS };
}
const parsed = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf8"));
return {
nerdModeEnabled: Boolean(parsed && parsed.nerdModeEnabled),
updateIntervalMs: normalizeUpdateIntervalMs(parsed && parsed.updateIntervalMs)
};
} catch (_) {
return { nerdModeEnabled: false, updateIntervalMs: DEFAULT_UPDATE_INTERVAL_MS };
}
}

function saveBridgeSettings(nextSettings) {
ensureBridgeDataDir();
const payload = {
nerdModeEnabled: Boolean(nextSettings && nextSettings.nerdModeEnabled),
updateIntervalMs: normalizeUpdateIntervalMs(nextSettings && nextSettings.updateIntervalMs)
};
fs.writeFileSync(SETTINGS_FILE, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
return payload;
}

function applyBridgeSettingsToUi(settings) {
if (nerdModeToggleEl) {
nerdModeToggleEl.checked = Boolean(settings && settings.nerdModeEnabled);
}
if (updateIntervalInputEl) {
updateIntervalInputEl.value = String(normalizeUpdateIntervalMs(settings && settings.updateIntervalMs));
}
}

function getBundleRoot() {
try {
Expand Down Expand Up @@ -343,6 +391,12 @@ async function checkHealth() {
try {
const health = await httpJson(`${BRIDGE_URL}/health`);
setStatus(health && health.ok ? "running" : "degraded");
if (health && (typeof health.nerdModeEnabled === "boolean" || health.updateIntervalMs != null)) {
applyBridgeSettingsToUi({
nerdModeEnabled: health.nerdModeEnabled,
updateIntervalMs: health.updateIntervalMs
});
}
log(`Health: ${JSON.stringify(health)}`);
} catch (err) {
setStatus("offline");
Expand All @@ -368,6 +422,25 @@ startBtn.addEventListener("click", startBridge);
stopBtn.addEventListener("click", stopBridge);
healthBtn.addEventListener("click", checkHealth);
probeBtn.addEventListener("click", probeNowPlaying);
if (nerdModeToggleEl) {
nerdModeToggleEl.addEventListener("change", () => {
const settings = saveBridgeSettings({
nerdModeEnabled: Boolean(nerdModeToggleEl.checked),
updateIntervalMs: updateIntervalInputEl ? updateIntervalInputEl.value : DEFAULT_UPDATE_INTERVAL_MS
});
log(`Nerd Mode ${settings.nerdModeEnabled ? "enabled" : "disabled"}.`);
});
}
if (updateIntervalInputEl) {
updateIntervalInputEl.addEventListener("change", () => {
const settings = saveBridgeSettings({
nerdModeEnabled: nerdModeToggleEl ? Boolean(nerdModeToggleEl.checked) : false,
updateIntervalMs: updateIntervalInputEl.value
});
applyBridgeSettingsToUi(settings);
log(`Update interval set to ${settings.updateIntervalMs}ms.`);
});
}

window.addEventListener("beforeunload", () => {
stopHealthMonitor();
Expand Down Expand Up @@ -463,5 +536,6 @@ function setupTrayBehavior() {
}

setStatus("idle");
applyBridgeSettingsToUi(loadBridgeSettings());
log("Bridge NW app ready.");
setupTrayBehavior();
16 changes: 16 additions & 0 deletions bridge-nw/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
button { background: #1f2430; color: #e7e9ee; border: 1px solid #2e3544; border-radius: 8px; padding: 8px 12px; cursor: pointer; }
button:hover { background: #2a3142; }
.status { background: #121622; border: 1px solid #2e3544; border-radius: 8px; padding: 10px; margin-bottom: 12px; }
.toggleRow { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; padding: 10px 12px; background: #121622; border: 1px solid #2e3544; border-radius: 8px; }
.toggleRow label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.toggleHint { color: #9aa4b4; font-size: 12px; }
.settingInput { background: #0b0d14; color: #e7e9ee; border: 1px solid #2e3544; border-radius: 6px; padding: 6px 8px; width: 120px; }
pre { background: #0b0d14; border: 1px solid #2e3544; border-radius: 8px; padding: 12px; height: 460px; overflow: auto; white-space: pre-wrap; }
code { color: #8ad1ff; }
</style>
Expand All @@ -24,6 +28,18 @@ <h2>BetterFluxer Bridge (NW.js)</h2>
<button id="healthBtn">Check Health</button>
<button id="probeBtn">Probe Now Playing</button>
</div>
<div class="toggleRow">
<label>
<input type="checkbox" id="nerdModeToggle" />
<span>Enable Nerd Mode</span>
</label>
<span class="toggleHint">When nothing is playing, publish system stats instead.</span>
</div>
<div class="toggleRow">
<label for="updateIntervalInput">Update interval (ms)</label>
<input class="settingInput" type="number" id="updateIntervalInput" min="1000" max="60000" step="1000" />
<span class="toggleHint">Suggested poll interval for status sync.</span>
</div>
<div class="status" id="status">Status: idle</div>
<pre id="log"></pre>
</div>
Expand Down
98 changes: 89 additions & 9 deletions bridge-nw/scripts/local-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const BRIDGE_BASE_DIR = getBridgeBaseDir();
const DATA_DIR = path.join(BRIDGE_BASE_DIR, "data");
const TOKEN_FILE = path.join(DATA_DIR, "bridge-token.txt");
const CACHE_FILE = path.join(DATA_DIR, "bridge-cache.json");
const SETTINGS_FILE = path.join(DATA_DIR, "bridge-settings.json");

const HOST = "127.0.0.1";
const PORT = Number.parseInt(process.env.BF_BRIDGE_PORT || "21864", 10);
Expand All @@ -43,6 +44,7 @@ const BRIDGE_VERSION = BRIDGE_VERSION_BY_OS[process.platform] || "2026-03-10-rpc
const REQUEST_TIMEOUT_MS = Number.parseInt(process.env.BF_BRIDGE_TIMEOUT_MS || "12000", 10);
const DEFAULT_TTL_SECONDS = Number.parseInt(process.env.BF_BRIDGE_DEFAULT_TTL || "120", 10);
const MAX_TTL_SECONDS = Number.parseInt(process.env.BF_BRIDGE_MAX_TTL || "1800", 10);
const DEFAULT_UPDATE_INTERVAL_MS = Number.parseInt(process.env.BF_BRIDGE_UPDATE_INTERVAL_MS || "10000", 10);
const WINDOWS_RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
const WINDOWS_RUN_VALUE = "BetterFluxerBridge";
const LINUX_BIN_DIR = path.join(os.homedir(), ".local", "bin");
Expand Down Expand Up @@ -116,6 +118,12 @@ function normalizeTtlSeconds(input) {
return Math.max(1, Math.min(MAX_TTL_SECONDS, n));
}

function normalizeUpdateIntervalMs(input) {
const n = Number.parseInt(String(input || ""), 10);
if (!Number.isFinite(n) || n <= 0) return DEFAULT_UPDATE_INTERVAL_MS;
return Math.max(1000, Math.min(60000, Math.round(n)));
}

function readJsonBody(req) {
return new Promise((resolve) => {
let raw = "";
Expand Down Expand Up @@ -153,6 +161,21 @@ function saveCache(cache) {
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf8");
}

function loadBridgeSettings() {
if (!fs.existsSync(SETTINGS_FILE)) {
return { nerdModeEnabled: false, updateIntervalMs: DEFAULT_UPDATE_INTERVAL_MS };
}
try {
const parsed = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf8"));
return {
nerdModeEnabled: Boolean(parsed && parsed.nerdModeEnabled),
updateIntervalMs: normalizeUpdateIntervalMs(parsed && parsed.updateIntervalMs)
};
} catch (_) {
return { nerdModeEnabled: false, updateIntervalMs: DEFAULT_UPDATE_INTERVAL_MS };
}
}

function parseArgv(argv) {
const out = {};
for (const item of argv || []) {
Expand Down Expand Up @@ -392,6 +415,41 @@ function normalizeNowPlayingPayload(payload, source) {
};
}

function formatDurationShort(totalSeconds) {
const sec = Math.max(0, Math.floor(Number(totalSeconds) || 0));
const hours = Math.floor(sec / 3600);
const minutes = Math.floor((sec % 3600) / 60);
const seconds = sec % 60;
if (hours > 0) return `${hours}h ${minutes}m`;
if (minutes > 0) return `${minutes}m ${seconds}s`;
return `${seconds}s`;
}

function buildNerdModeNowPlaying() {
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = Math.max(0, totalMem - freeMem);
const memPercent = totalMem > 0 ? Math.round((usedMem / totalMem) * 100) : 0;
const loadAvg = os.loadavg();
const cpuCount = Array.isArray(os.cpus()) ? os.cpus().length : 0;
const hostname = String(os.hostname() || "").trim();
const details = `CPU ${Number(loadAvg[0] || 0).toFixed(2)} | RAM ${memPercent}%`;
const state = `${hostname || process.platform} | up ${formatDurationShort(os.uptime())}`;
return normalizeNowPlayingPayload(
{
source: "nerd-mode",
kind: "system",
title: "Nerd Mode",
details,
state,
appId: hostname || process.platform,
playbackStatus: "idle",
name: `System Stats (${cpuCount} cores)`
},
"nerd-mode"
);
}

function makeRpcFrame(op, payloadObject) {
const payload = Buffer.from(JSON.stringify(payloadObject || {}), "utf8");
const frame = Buffer.allocUnsafe(8 + payload.length);
Expand Down Expand Up @@ -809,6 +867,7 @@ async function queryMacMedia() {

async function queryUniversalNowPlaying(state) {
const now = Date.now();
let rpcFallback = null;
if (state && state.lastRpcActivity && now - Number(state.lastRpcActivityAt || 0) < 180000) {
const raw = state.lastRpcActivity.raw && typeof state.lastRpcActivity.raw === "object" ? state.lastRpcActivity.raw : {};
const base = state.lastRpcActivity.normalized && typeof state.lastRpcActivity.normalized === "object"
Expand All @@ -823,31 +882,49 @@ async function queryUniversalNowPlaying(state) {
if (startMs != null && endMs != null && endMs > startMs) {
base.durationMs = endMs - startMs;
}
return base;
if (base && base.ok && base.hasSession) {
return base;
}
rpcFallback = {
ok: true,
hasSession: false,
source: String(base.source || "discord-rpc-pipe"),
error: String(base.error || "No active RPC session")
};
}

let fallback = null;
if (process.platform === "win32") {
const tuna = await queryTunaNowPlaying(state);
if (tuna && tuna.ok && tuna.hasSession) return tuna;
return tuna && tuna.ok
fallback = tuna && tuna.ok
? tuna
: { ok: false, hasSession: false, source: "windows-now-playing", error: "No active RPC or Tuna session" };
}
if (process.platform === "linux") {
} else if (process.platform === "linux") {
const tuna = await queryTunaNowPlaying(state);
if (tuna && tuna.ok && tuna.hasSession) return tuna;
return tuna && tuna.ok
fallback = tuna && tuna.ok
? tuna
: { ok: false, hasSession: false, source: "linux-now-playing", error: "No active Tuna session" };
}
if (process.platform === "darwin") {
} else if (process.platform === "darwin") {
const mac = await queryMacMedia();
if (mac.ok && mac.hasSession) return mac;
const tuna = await queryTunaNowPlaying(state);
if (tuna && tuna.ok && tuna.hasSession) return tuna;
return mac.ok ? mac : tuna.ok ? tuna : mac;
fallback = mac.ok ? mac : tuna.ok ? tuna : mac;
} else {
fallback = { ok: false, error: `Unsupported platform: ${process.platform}` };
}

const preferredFallback =
fallback && fallback.hasSession
? fallback
: rpcFallback || fallback;

if (loadBridgeSettings().nerdModeEnabled && (!preferredFallback || !preferredFallback.hasSession)) {
return buildNerdModeNowPlaying();
}
return { ok: false, error: `Unsupported platform: ${process.platform}` };
return preferredFallback;
}

function formatNowPlayingForLog(np) {
Expand Down Expand Up @@ -988,6 +1065,7 @@ async function main() {

const url = new URL(req.url || "/", `http://${HOST}:${PORT}`);
if (url.pathname === "/health") {
const settings = loadBridgeSettings();
return sendJson(
res,
200,
Expand All @@ -999,6 +1077,8 @@ async function main() {
rpcPipesListening: (bridgeState.rpcServers || []).length,
rpcPipeErrors: (bridgeState.rpcBindErrors || []).length,
tunaPath: bridgeState.tunaPath,
nerdModeEnabled: Boolean(settings.nerdModeEnabled),
updateIntervalMs: settings.updateIntervalMs,
allowlist,
uptimeSec: Math.floor(process.uptime())
},
Expand Down
1 change: 1 addition & 0 deletions do_not_edit/fluxer
Submodule fluxer added at 03813b
Loading
Loading