OASIS_MOBILE/src/backend/desktopNotify.js
oasis-bot c332afd6ea upstream: integrar Oasis 1.3.1
Base del merge a tres bandas: git 0.9.5 de epsylon, esta rama y el src de la
APK oficial 1.3.1 (la variante movil: OASIS_MOBILE, regex sin \p{}, audio y
video de Android). Corre en el mismo nodejs-mobile 18.20.4 que ya usabamos.

Se conserva de la rama: Karvan, identidades y multicuenta, topbar y barra
inferior, borrado de EXIF sin sharp, filtro hot del foro, RTC sin STUN y los
modulos apagados en movil (wallet, banking, torrents).

Se toma de upstream lo que ya hace de serie: respuesta citada en chats, ssb-lan
en movil, configuracion en ~/.ssb/oasis (sobrevive a reinstalar el backend).
El asistente de bienvenida deja elegir workflow tambien en movil.
2026-10-18 02:52:17 +02:00

80 lines
3.4 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const { getConfig } = require('../configs/config-manager.js');
const REPEAT_MS = 60000;
const ICON = path.join(__dirname, '..', 'client', 'assets', 'images', 'snh-oasis.jpg');
const recent = new Map();
const onPath = (bin) => (process.env.PATH || '').split(path.delimiter).some(d => d && fs.existsSync(path.join(d, bin)));
const i18nNow = () => {
const all = require('../client/assets/translations/i18n');
let lang = 'en';
try { lang = getConfig().language || 'en'; } catch (_) {}
return { ...all.en, ...(all[lang] || {}) };
};
const displayName = (id, name) => {
const n = String(name || '').trim();
if (!n || n === 'Redacted' || n === String(id || '').replace(/^@/, '').slice(0, 8)) return String(id || '');
return n.startsWith('@') ? n : `@${n}`;
};
const MOBILE_QUEUE = path.join(__dirname, '..', '..', '.notifications.json');
const queueForAndroid = (keyword, body, line, key) => {
try {
let list = [];
try { list = JSON.parse(fs.readFileSync(MOBILE_QUEUE, 'utf8')); } catch (_) { list = []; }
if (!Array.isArray(list)) list = [];
const t = i18nNow();
const k = String(keyword || '');
const route = k === t.phoneTitle ? '/phone' : k === t.inbox ? '/inbox' : k === t.emergenciesTitle ? '/emergencies' : '/';
const entry = { id: Date.now() + '-' + Math.random().toString(36).slice(2, 8), keyword: k || 'Oasis', body, line, route, key: key ? String(key) : null, ts: Date.now() };
if (entry.key && entry.key.indexOf('call:') === 0) {
const ringing = String(t.phoneIsCalling || '');
entry.caller = ringing && body.endsWith(ringing) ? body.slice(0, body.length - ringing.length).trim() : body;
entry.accept = String(t.phoneAnswer || '');
entry.reject = String(t.phoneReject || '');
}
list.push(entry);
while (list.length > 20) list.shift();
fs.writeFileSync(MOBILE_QUEUE, JSON.stringify(list));
return true;
} catch (_) {
return false;
}
};
const notify = (keyword, text, key = null, opts = {}) => {
const onMobile = process.env.OASIS_MOBILE === '1';
if (!onMobile && !onPath('notify-send')) return false;
const body = String(text || '').replace(/\s+/g, ' ').trim().replace(/[.。!…]+$/, '').slice(0, 200);
const line = `${String(keyword || 'Oasis').toUpperCase()}: ${body}...`;
const now = Date.now();
const dedupe = key || line;
for (const [k, t] of recent) if (now - t > REPEAT_MS) recent.delete(k);
if (recent.has(dedupe)) return false;
recent.set(dedupe, now);
if (onMobile) return queueForAndroid(keyword, body, line, key);
const actions = Array.isArray(opts.actions) ? opts.actions.filter(x => x && x.key && x.label) : [];
const extra = [
...(opts.timeoutMs > 0 ? ['-t', String(Math.round(opts.timeoutMs))] : []),
...actions.flatMap(x => ['-A', `${x.key}=${x.label}`])
];
try {
const p = spawn('notify-send', ['-a', 'Oasis', ...(fs.existsSync(ICON) ? ['-i', ICON] : []), ...extra, line], { stdio: ['ignore', actions.length ? 'pipe' : 'ignore', 'ignore'] });
p.on('error', () => {});
if (actions.length && typeof opts.onAction === 'function') {
let out = '';
p.stdout.on('data', (d) => { out += d; });
p.on('close', () => { const chosen = out.trim(); if (actions.some(x => x.key === chosen)) opts.onAction(chosen); });
}
return true;
} catch (_) {
return false;
}
};
module.exports = { notify, i18nNow, displayName };