// ── PS Notify page ──────────────────────────────────────────────────────────── let _psnotifyConfigured = false; function applyPsNotifyAvailability(configured, detail = '') { _psnotifyConfigured = configured; ['btn-psn-send', 'btn-psn-test', 'btn-psn-push-payload'].forEach(id => { const button = $(id); if (!button) return; button.disabled = !configured; button.title = configured ? '' : 'Configure a PS5 host in Settings before using this action.'; }); document.querySelectorAll('#page-psnotify [id^="btn-psn-exp-"]').forEach(button => { button.disabled = !configured; button.title = configured ? '' : 'Configure a PS5 host in Settings before using this action.'; }); document.querySelectorAll('.psnotify-compose-card, .psnotify-payload-card').forEach(card => { card.classList.toggle('is-unavailable', !configured); }); const status = $('psn-status'); if (!configured && status && !status.textContent) { status.textContent = detail || 'Configure a PS5 host in Settings to enable console actions.'; } } function enhancePsNotifyLayout() { const page = $('page-psnotify'); if (!page || page.dataset.enhanced) return; page.dataset.enhanced = 'true'; const header = page.querySelector(':scope > .page-header'); header?.insertAdjacentHTML('beforeend', '

Compose, test and review PS5 notifications from one focused workspace.

Not checked'); const cards = [...page.querySelectorAll(':scope > .card')]; const [compose, settings, payload, lab, history] = cards; if (!compose || !settings || !payload || !lab || !history) return; compose.classList.add('psnotify-compose-card'); settings.classList.add('psnotify-settings-card'); payload.classList.add('psnotify-payload-card'); lab.classList.add('psnotify-lab', 'collapsed'); history.classList.add('psnotify-history-card'); compose.querySelector('.card-header h3').textContent = 'Compose notification'; compose.querySelector('.hint').textContent = 'Sent to the PS5 host from your active console profile.'; compose.querySelector('.form-grid')?.insertAdjacentHTML('beforebegin', `
`); compose.querySelector('.form-grid')?.insertAdjacentHTML('afterend', `
PS5 preview Your notification appears here Optional supporting detail
`); $('psn-message')?.insertAdjacentHTML('afterend', '0/120'); $('psn-submessage')?.insertAdjacentHTML('afterend', '0/120'); settings.querySelector('.card-header h3').textContent = 'Service connection'; payload.querySelector('.card-header h3').textContent = 'Notification payload'; history.querySelector('.card-header h3').textContent = 'Notification history'; lab.querySelector('.card-header')?.insertAdjacentHTML('beforeend', ''); const grid = document.createElement('div'); grid.className = 'psnotify-grid'; compose.insertAdjacentElement('beforebegin', grid); grid.append(compose); const side = document.createElement('div'); side.className = 'psnotify-side'; side.append(settings, payload); grid.append(side); history.insertAdjacentElement('afterend', lab); $('psn-lab-toggle')?.addEventListener('click', event => { const expanded = event.currentTarget.getAttribute('aria-expanded') === 'true'; event.currentTarget.setAttribute('aria-expanded', String(!expanded)); event.currentTarget.textContent = expanded ? 'Show advanced lab' : 'Hide advanced lab'; lab.classList.toggle('collapsed', expanded); }); const templates = { complete: ['Build complete', 'The latest Chimera task finished successfully.'], backup: ['Backup complete', 'Your PS5 data has been verified and stored.'], update: ['Update ready', 'A reviewed update is ready in Chimera.'], warning: ['Attention needed', 'Open Chimera for details and the recommended next step.'], }; compose.querySelector('.psnotify-templates')?.addEventListener('click', event => { const button = event.target.closest('[data-psn-template]'); if (!button) return; [$('psn-message').value, $('psn-submessage').value] = templates[button.dataset.psnTemplate]; updatePsNotifyPreview(); $('psn-message').focus(); }); } function updatePsNotifyPreview() { const message = $('psn-message')?.value || ''; const submessage = $('psn-submessage')?.value || ''; if ($('psn-message-count')) $('psn-message-count').textContent = `${message.length}/120`; if ($('psn-submessage-count')) $('psn-submessage-count').textContent = `${submessage.length}/120`; if ($('psn-preview-message')) $('psn-preview-message').textContent = message || 'Your notification appears here'; if ($('psn-preview-submessage')) $('psn-preview-submessage').textContent = submessage || 'Optional supporting detail'; } async function loadPsNotify() { enhancePsNotifyLayout(); // Populate settings fields from store try { const s = await window.pork.getSettings(); $('psn-enabled').checked = s.psnotifyEnabled !== false; $('psn-port').value = s.psnotifyPort || 6969; const readiness = $('psn-page-readiness'); if (readiness) { readiness.textContent = s.ftpHost ? `Configured · ${s.ftpHost}:${s.psnotifyPort || 6969}` : 'PS5 host not configured'; readiness.className = `operations-status ${s.ftpHost ? 'operations-status-compatible' : 'operations-status-warning'}`; } applyPsNotifyAvailability(Boolean(s.ftpHost)); } catch (_) { applyPsNotifyAvailability(false, 'Console configuration could not be loaded.'); } updatePsNotifyPreview(); await renderPsnHistory(); } $('psn-message').addEventListener('input', updatePsNotifyPreview); $('psn-submessage').addEventListener('input', updatePsNotifyPreview); $('psn-message').addEventListener('keydown', event => { if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') $('btn-psn-send').click(); }); async function renderPsnHistory() { const hist = await window.pork.psnotifyHistory(); const list = $('psn-history'); const empty = $('psn-history-empty'); if (!hist.length) { list.innerHTML = ''; empty.hidden = false; return; } empty.hidden = true; list.innerHTML = hist.slice().reverse().map(e => { const cls = e.ok !== false ? 'psn-ok' : 'psn-fail'; const ts = new Date(e.ts).toLocaleTimeString(); const sub = e.subMessage ? `\u2014 ${escHtml(e.subMessage)}` : ''; return `
${ts} ${escHtml(e.message)}${sub}
`; }).join(''); } $('btn-psn-send').addEventListener('click', async () => { if (!_psnotifyConfigured) return; const msg = $('psn-message').value.trim(); const sub = $('psn-submessage').value.trim(); if (!msg) { $('psn-status').textContent = 'Message is required.'; return; } const btn = $('btn-psn-send'); btn.disabled = true; $('psn-status').textContent = 'Sending\u2026'; try { await window.pork.psnotifySend(msg, sub); $('psn-status').textContent = 'Sent \u2713'; $('psn-message').value = ''; $('psn-submessage').value = ''; updatePsNotifyPreview(); await renderPsnHistory(); } catch (e) { $('psn-status').textContent = 'Error: ' + e.message; } btn.disabled = !_psnotifyConfigured; }); $('btn-psn-test').addEventListener('click', async () => { if (!_psnotifyConfigured) return; const btn = $('btn-psn-test'); btn.disabled = true; $('psn-status').textContent = 'Sending test\u2026'; try { await window.pork.psnotifyTest(); $('psn-status').textContent = 'Test sent \u2713'; if ($('psn-page-readiness')) { $('psn-page-readiness').textContent = 'Online'; $('psn-page-readiness').className = 'operations-status operations-status-compatible'; } await renderPsnHistory(); } catch (e) { $('psn-status').textContent = 'Error: ' + e.message; if ($('psn-page-readiness')) { $('psn-page-readiness').textContent = 'Offline'; $('psn-page-readiness').className = 'operations-status operations-status-warning'; } } btn.disabled = !_psnotifyConfigured; }); $('btn-psn-save-settings').addEventListener('click', async () => { const port = parseInt($('psn-port').value) || 6969; await window.pork.setSettings({ psnotifyEnabled: $('psn-enabled').checked, psnotifyPort: port, }); $('psn-port').value = port; $('psn-status').textContent = 'Settings saved \u2713'; }); $('btn-psn-refresh-history').addEventListener('click', renderPsnHistory); // ── PS Notify colour/encoding experiments ──────────────────────────────────────── async function _sendExp(msg, sub = '') { const el = $('psn-exp-status'); if (!_psnotifyConfigured) { el.textContent = 'Configure a PS5 host in Settings first.'; return; } el.textContent = 'Sending…'; try { await window.pork.psnotifySend(msg, sub); el.textContent = 'Sent \u2713 — check your PS5'; await renderPsnHistory(); } catch (e) { el.textContent = 'Error: ' + e.message; } } $('btn-psn-push-payload').addEventListener('click', async () => { if (!_psnotifyConfigured) return; const btn = $('btn-psn-push-payload'); const st = $('psn-push-status'); btn.disabled = true; st.textContent = 'Queuing upload\u2026'; try { const res = await window.pork.psnotifyPushPayload(); st.textContent = `Queued \u2713 \u2014 sending to ${res.remotePath}. Reload the payload on your PS5 then emoji will work.`; } catch (e) { st.textContent = 'Error: ' + e.message; } btn.disabled = !_psnotifyConfigured; }); // HTML markup $('btn-psn-exp-html-red').addEventListener('click', () => _sendExp('This is red text', 'HTML test')); $('btn-psn-exp-html-multi').addEventListener('click', () => _sendExp('Red Green Blue', 'HTML multi-colour test')); $('btn-psn-exp-sony-tag').addEventListener('click', () => _sendExp('$[color=ff0000]Red$[/color] $[color=00ff00]Green$[/color]', 'Sony $[color] tag test')); // Emoji colour squares (4-byte UTF-8 — need TextDecoder patch in customPSNotify.js to arrive intact) $('btn-psn-exp-sq-red').addEventListener('click', () => _sendExp('\uD83D\uDFE5 Red notification', 'Emoji \uD83D\uDFE5 (U+1F7E5, 4-byte UTF-8)')); $('btn-psn-exp-sq-green').addEventListener('click', () => _sendExp('\uD83D\uDFE9 Green notification', 'Emoji \uD83D\uDFE9 (U+1F7E9, 4-byte UTF-8)')); $('btn-psn-exp-sq-blue').addEventListener('click', () => _sendExp('\uD83D\uDFE6 Blue notification', 'Emoji \uD83D\uDFE6 (U+1F7E6, 4-byte UTF-8)')); $('btn-psn-exp-sq-yellow').addEventListener('click', () => _sendExp('\uD83D\uDFE8 Yellow notification', 'Emoji \uD83D\uDFE8 (U+1F7E8, 4-byte UTF-8)')); $('btn-psn-exp-sq-all').addEventListener('click', () => _sendExp('\uD83D\uDFE5\uD83D\uDFE7\uD83D\uDFE8\uD83D\uDFE9\uD83D\uDFE6\uD83D\uDFEA Colour squares test', 'All 6 colour squares')); // ANSI escape codes $('btn-psn-exp-ansi-red').addEventListener('click', () => _sendExp('\x1b[31mRed ANSI text\x1b[0m', 'ANSI \\x1b[31m colour test')); $('btn-psn-exp-ansi-bold').addEventListener('click', () => _sendExp('\x1b[1mBold ANSI text\x1b[0m', 'ANSI \\x1b[1m bold test'));