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.
This commit is contained in:
oasis-bot 2026-10-18 02:52:17 +02:00
parent 8d838dd8b1
commit c332afd6ea
265 changed files with 80970 additions and 29167 deletions

View file

@ -5,9 +5,18 @@ const { getConfig } = require('../configs/config-manager.js');
const { dedupeBy, norm } = require('../backend/dedupe');
const { buildVoteTally } = require('../backend/vote_tally');
const categories = require('../backend/opinion_categories');
const { readTyped, memoIndex } = require('./typed_log');
const logLimit = getConfig().ssbLogStream?.limit || 1000;
const MIN_VOTE_DAYS = 7;
const INSTITUTIONAL_VOTE_TAG = /^(gov|govMethod|courtsCase|courtsMethod):/;
const isVoteLocked = (v) => !!v && (
(parseInt(v.totalVotes || 0, 10) || 0) > 0
|| Object.values(v.opinions || {}).some(n => (Number(n) || 0) > 0)
|| (Array.isArray(v.tags) && v.tags.some(t => INSTITUTIONAL_VOTE_TAG.test(String(t))))
);
const lockedError = () => Object.assign(new Error('Vote locked'), { code: 'VOTE_LOCKED' });
module.exports = ({ cooler }) => {
let ssb;
const openSsb = async () => {
@ -17,13 +26,10 @@ module.exports = ({ cooler }) => {
const TYPE = 'votes';
const VOTES_TYPES = [TYPE, 'votesVote', 'votesOpinion', 'tombstone'];
async function getAllMessages(ssbClient) {
return new Promise((resolve, reject) => {
pull(
ssbClient.createLogStream({ limit: logLimit }),
pull.collect((err, results) => (err ? reject(err) : resolve(results)))
);
});
return readTyped(ssbClient, VOTES_TYPES, { limit: logLimit });
}
function buildIndex(messages) {
@ -220,6 +226,7 @@ module.exports = ({ cooler }) => {
ssbClient.get(tipId, (err, msg) => (err || !msg ? rej(new Error('Vote not found')) : res(msg)))
);
if (!vote.content || vote.content.createdBy !== userId) throw new Error('Not the author');
if (isVoteLocked(vote.content) || isVoteLocked(await this.getVoteById(id).catch(() => null))) throw lockedError();
const tombstone = {
type: 'tombstone',
target: tipId,
@ -246,7 +253,7 @@ module.exports = ({ cooler }) => {
if (c.createdBy !== userId) throw new Error('Not the author');
const agg = await this.getVoteById(id);
const aggTotalVotes = agg ? (parseInt(agg.totalVotes || 0, 10) || 0) : 0;
if (agg && Object.keys(agg.opinions || {}).some(k => (agg.opinions[k] || 0) > 0)) throw new Error('Cannot edit vote after it has received opinions.');
if (isVoteLocked(c) || isVoteLocked(agg)) throw lockedError();
let newDeadline = c.deadline;
if (deadline != null && deadline !== '') {
@ -324,7 +331,7 @@ module.exports = ({ cooler }) => {
async getVoteById(id) {
const ssbClient = await openSsb();
const messages = await getAllMessages(ssbClient);
const index = buildIndex(messages);
const index = memoIndex('votes', messages, buildIndex);
const activeList = computeActiveVotes(index);
const byId = new Map(activeList.map(v => [v.id, v]));
@ -339,19 +346,7 @@ module.exports = ({ cooler }) => {
return candidate;
}
const msg = await new Promise((res, rej) =>
ssbClient.get(id, (err, vote) => (err || !vote ? rej(new Error('Vote not found')) : res(vote)))
);
const content = msg.content || {};
const status = statusFromContent(content, moment());
return {
id,
latestId: id,
...content,
status
};
throw new Error('Vote not found');
},
async listAll(filter = 'all') {
@ -363,6 +358,8 @@ module.exports = ({ cooler }) => {
if (filter === 'mine') {
list = list.filter(v => v.createdBy === userId);
} else if (filter === 'recent') {
list = list.filter(v => (Date.parse(v.createdAt || '') || 0) >= Date.now() - 86400000);
} else if (filter === 'open') {
list = list.filter(v => v.status === 'OPEN');
} else if (filter === 'closed') {
@ -389,4 +386,4 @@ module.exports = ({ cooler }) => {
}
};
};
module.exports.isVoteLocked = isVoteLocked;