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:
parent
8d838dd8b1
commit
c332afd6ea
265 changed files with 80970 additions and 29167 deletions
|
|
@ -1,116 +1,119 @@
|
|||
const buildVoteTally = (messages) => {
|
||||
const nodes = new Map();
|
||||
const ballots = [];
|
||||
const INSTITUTIONAL_VOTE_TAG = /^(gov|govMethod|courtsCase|courtsMethod):/;
|
||||
|
||||
for (const m of messages) {
|
||||
const msOf = (v) => {
|
||||
const t = Date.parse(v);
|
||||
return Number.isFinite(t) ? t : null;
|
||||
};
|
||||
|
||||
const isLater = (a, b) => a.ts > b.ts || (a.ts === b.ts && a.seq > b.seq);
|
||||
|
||||
const legacyChoice = (kid, parent, options) => {
|
||||
const kVoters = Array.isArray(kid.content.voters) ? kid.content.voters : [];
|
||||
const pVoters = Array.isArray(parent.content.voters) ? parent.content.voters : [];
|
||||
if (!kVoters.includes(kid.author) || pVoters.includes(kid.author)) return null;
|
||||
const kVotes = kid.content.votes || {};
|
||||
const pVotes = parent.content.votes || {};
|
||||
let choice = null;
|
||||
for (const o of options) {
|
||||
const d = (Number(kVotes[o]) || 0) - (Number(pVotes[o]) || 0);
|
||||
if (d === 0) continue;
|
||||
if (d !== 1 || choice !== null) return null;
|
||||
choice = o;
|
||||
}
|
||||
return choice;
|
||||
};
|
||||
|
||||
const buildVoteResults = (messages) => {
|
||||
const nodes = new Map();
|
||||
const ballotsByTarget = new Map();
|
||||
|
||||
for (const m of messages || []) {
|
||||
const v = m && m.value;
|
||||
const c = v && v.content;
|
||||
if (!c) continue;
|
||||
if (c.type === 'votesVote') { if (c.target) ballots.push({ target: c.target, author: v.author, choice: c.choice }); continue; }
|
||||
const ts = Number(v.timestamp || m.timestamp || 0) || 0;
|
||||
const seq = Number(v.sequence || 0) || 0;
|
||||
if (c.type === 'votesVote') {
|
||||
if (typeof c.target !== 'string' || !v.author) continue;
|
||||
if (!ballotsByTarget.has(c.target)) ballotsByTarget.set(c.target, []);
|
||||
ballotsByTarget.get(c.target).push({ author: v.author, choice: c.choice, ts, seq });
|
||||
continue;
|
||||
}
|
||||
if (c.type !== 'votes') continue;
|
||||
nodes.set(m.key, { key: m.key, author: v.author, content: c, ts: v.timestamp || m.timestamp || 0 });
|
||||
nodes.set(m.key, { key: m.key, author: v.author, content: c, ts, seq });
|
||||
}
|
||||
|
||||
const prev = new Map();
|
||||
for (const [k, n] of nodes) {
|
||||
const t = n.content.replaces;
|
||||
if (typeof t === 'string' && nodes.has(t)) prev.set(k, t);
|
||||
}
|
||||
const childrenOf = new Map();
|
||||
for (const [k, p] of prev) {
|
||||
if (!childrenOf.has(p)) childrenOf.set(p, []);
|
||||
childrenOf.get(p).push(k);
|
||||
if (typeof t === 'string' && t !== k && nodes.has(t)) prev.set(k, t);
|
||||
}
|
||||
const rootOf = (k) => { let x = k, g = 0; while (prev.has(x) && g++ < 100000) x = prev.get(x); return x; };
|
||||
|
||||
const groups = new Map();
|
||||
for (const k of nodes.keys()) { const r = rootOf(k); if (!groups.has(r)) groups.set(r, []); groups.get(r).push(k); }
|
||||
|
||||
const tallyByKey = new Map();
|
||||
const results = new Map();
|
||||
for (const [root, keys] of groups.entries()) {
|
||||
const rootNode = nodes.get(root);
|
||||
if (!rootNode) continue;
|
||||
const options = Array.isArray(rootNode.content.options) ? rootNode.content.options : [];
|
||||
let votesMap = Object.assign({}, rootNode.content.votes || {});
|
||||
let voters = Array.isArray(rootNode.content.voters) ? rootNode.content.voters.slice() : [];
|
||||
for (const o of options) if (!(o in votesMap)) votesMap[o] = 0;
|
||||
|
||||
let cur = root;
|
||||
let g = 0;
|
||||
let creatorChoice = null;
|
||||
let creatorCounted = false;
|
||||
while (g++ < 100000) {
|
||||
const kids = (childrenOf.get(cur) || []).map(k => nodes.get(k)).filter(Boolean).sort((a, b) => a.ts - b.ts);
|
||||
if (!kids.length) break;
|
||||
let advanced = false;
|
||||
for (const kid of kids) {
|
||||
const kVotes = Object.assign({}, kid.content.votes || {});
|
||||
const kVoters = Array.isArray(kid.content.voters) ? kid.content.voters.slice() : [];
|
||||
for (const o of options) if (!(o in kVotes)) kVotes[o] = 0;
|
||||
if (kid.author === rootNode.author) {
|
||||
if (!creatorCounted && kVoters.includes(rootNode.author) && !voters.includes(rootNode.author)) {
|
||||
creatorCounted = true;
|
||||
let inc = null;
|
||||
let incCount = 0;
|
||||
for (const o of options) {
|
||||
const d = (Number(kVotes[o]) || 0) - (Number(votesMap[o]) || 0);
|
||||
if (d >= 1) { inc = o; incCount++; }
|
||||
}
|
||||
if (incCount === 1) creatorChoice = inc;
|
||||
}
|
||||
votesMap = kVotes;
|
||||
voters = kVoters;
|
||||
cur = kid.key;
|
||||
advanced = true;
|
||||
break;
|
||||
}
|
||||
if (voters.includes(kid.author)) continue;
|
||||
if (kVoters.length !== voters.length + 1) continue;
|
||||
const added = kVoters.filter(x => !voters.includes(x));
|
||||
if (added.length !== 1 || added[0] !== kid.author) continue;
|
||||
let deltas = 0;
|
||||
let deltaOk = true;
|
||||
for (const o of options) {
|
||||
const d = (Number(kVotes[o]) || 0) - (Number(votesMap[o]) || 0);
|
||||
if (d === 0) continue;
|
||||
if (d === 1) { deltas++; continue; }
|
||||
deltaOk = false;
|
||||
break;
|
||||
}
|
||||
if (!deltaOk || deltas !== 1) continue;
|
||||
votesMap = kVotes;
|
||||
voters = kVoters;
|
||||
cur = kid.key;
|
||||
advanced = true;
|
||||
break;
|
||||
const creator = rootNode.author;
|
||||
const tags = Array.isArray(rootNode.content.tags) ? rootNode.content.tags : [];
|
||||
const frozen = tags.some(t => INSTITUTIONAL_VOTE_TAG.test(String(t)));
|
||||
let current = rootNode;
|
||||
if (!frozen) {
|
||||
for (const k of keys) {
|
||||
const n = nodes.get(k);
|
||||
if (n.author === creator && isLater(n, current)) current = n;
|
||||
}
|
||||
if (!advanced) break;
|
||||
}
|
||||
const options = Array.isArray(current.content.options) ? current.content.options.filter(o => typeof o === 'string') : [];
|
||||
const deadline = typeof current.content.deadline === 'string' ? current.content.deadline : null;
|
||||
const opensAt = rootNode.ts;
|
||||
const closesAt = deadline ? msOf(deadline) : null;
|
||||
|
||||
const cast = [];
|
||||
for (const k of keys) {
|
||||
for (const b of ballotsByTarget.get(k) || []) cast.push(b);
|
||||
const n = nodes.get(k);
|
||||
if (n.author === creator || !prev.has(k)) continue;
|
||||
const choice = legacyChoice(n, nodes.get(prev.get(k)), options);
|
||||
if (choice !== null) cast.push({ author: n.author, choice, ts: n.ts, seq: n.seq });
|
||||
}
|
||||
|
||||
if (voters.includes(rootNode.author)) {
|
||||
voters = voters.filter(x => x !== rootNode.author);
|
||||
if (creatorChoice && (Number(votesMap[creatorChoice]) || 0) > 0) votesMap[creatorChoice] = votesMap[creatorChoice] - 1;
|
||||
}
|
||||
|
||||
const voterSet = new Set(voters);
|
||||
let totalVotes = voters.length;
|
||||
const chainKeys = new Set(keys);
|
||||
for (const b of ballots) {
|
||||
if (!chainKeys.has(b.target)) continue;
|
||||
if (b.author === rootNode.author) continue;
|
||||
if (!b.author || voterSet.has(b.author)) continue;
|
||||
const latest = new Map();
|
||||
for (const b of cast) {
|
||||
if (!b.author || b.author === creator) continue;
|
||||
if (!options.includes(b.choice)) continue;
|
||||
voterSet.add(b.author);
|
||||
voters.push(b.author);
|
||||
votesMap[b.choice] = (votesMap[b.choice] || 0) + 1;
|
||||
totalVotes += 1;
|
||||
if (b.ts < opensAt) continue;
|
||||
if (closesAt !== null && b.ts > closesAt) continue;
|
||||
const known = latest.get(b.author);
|
||||
if (!known || isLater(b, known)) latest.set(b.author, b);
|
||||
}
|
||||
|
||||
const tally = { votes: votesMap, voters, totalVotes };
|
||||
for (const k of keys) tallyByKey.set(k, tally);
|
||||
const votes = {};
|
||||
for (const o of options) votes[o] = 0;
|
||||
const voters = [];
|
||||
for (const b of [...latest.values()].sort((a, b) => a.ts - b.ts || a.seq - b.seq)) {
|
||||
votes[b.choice] = (votes[b.choice] || 0) + 1;
|
||||
voters.push(b.author);
|
||||
}
|
||||
|
||||
const result = { votes, voters, totalVotes: voters.length, creator, rootId: root, deadline, options };
|
||||
for (const k of keys) results.set(k, result);
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
const buildVoteTally = (messages) => {
|
||||
const shared = new Map();
|
||||
const tallyByKey = new Map();
|
||||
for (const [k, r] of buildVoteResults(messages)) {
|
||||
if (!shared.has(r)) shared.set(r, { votes: r.votes, voters: r.voters, totalVotes: r.totalVotes });
|
||||
tallyByKey.set(k, shared.get(r));
|
||||
}
|
||||
return tallyByKey;
|
||||
};
|
||||
|
||||
module.exports = { buildVoteTally };
|
||||
module.exports = { buildVoteTally, buildVoteResults };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue