upstream: integrar Oasis 1.3.3

Diferencia 1.3.1 -> 1.3.3 del codigo de la APK oficial aplicada a tres
bandas encima de la rama: karma en el perfil, salas con directo, fotos
selladas, visibilidad de menciones, telefono por stream en Android y
las traducciones nuevas. Sin dependencias nuevas.

OasisMobile.css sigue siendo el del fork; del oficial solo se traen al
final las reglas de las piezas nuevas (karma, directo a pantalla
completa, salas en vivo, graphos).
This commit is contained in:
oasis-bot 2026-10-19 00:30:25 +02:00
parent bc64565e9c
commit 948b6460d9
95 changed files with 3613 additions and 1356 deletions

View file

@ -24,7 +24,8 @@ const normalizeTags = (raw) => {
return String(raw).split(",").map(t => t.trim()).filter(Boolean)
}
const normalizeStatus = (v) => (STATUSES.includes(String(v || "").toUpperCase()) ? String(v).toUpperCase() : "OPEN")
const blobRef = (v) => (BLOB_ID.test(safeText(v)) ? safeText(v) : "")
const SEALED_REF = /^\/blob\/sealed\/%26[A-Za-z0-9%]+\.sha256\?k=[0-9a-f]{64}&t=[^\s"<>]*$/
const blobRef = (v) => (BLOB_ID.test(safeText(v)) || SEALED_REF.test(safeText(v)) ? safeText(v) : "")
const asP = (fn, ...args) => new Promise((resolve, reject) => fn(...args, (err, v) => err ? reject(err) : resolve(v)))
const rpcValue = (fn, ...args) => new Promise((resolve) => {
try {
@ -352,6 +353,8 @@ module.exports = ({ cooler, tribeCrypto, roomCrypto, tribesModel }) => {
return { title: "", description: "", image: "", tags: [], rid: "", _undec: true }
}
const roomTypeOf = (c) => c.status === "INVITE-ONLY" || (c.status === "CLOSED" && c.encrypted === true && !c.tribeId) ? "INVITE-ONLY" : "OPEN"
const buildRoom = (node, rootId, tribeKeys, members) => {
const c = node.c || {}
if (c.type !== "room") return null
@ -368,7 +371,7 @@ module.exports = ({ cooler, tribeCrypto, roomCrypto, tribesModel }) => {
image: f.image,
tags: f.tags,
status: ["OPEN", "INVITE-ONLY", "CLOSED"].includes(c.status) ? c.status : "OPEN",
type: c.status === "INVITE-ONLY" || (c.status === "CLOSED" && c.encrypted === true && !c.tribeId) ? "INVITE-ONLY" : "OPEN",
type: roomTypeOf(c),
author,
members: Array.isArray(members) ? members : (Array.isArray(c.members) ? c.members : []),
invites: Array.isArray(c.invites) ? c.invites : [],
@ -555,6 +558,10 @@ module.exports = ({ cooler, tribeCrypto, roomCrypto, tribesModel }) => {
return republish(id, (c) => ({ ...c, status: "CLOSED" }))
},
async reopenRoomById(id) {
return republish(id, (c) => ({ ...c, status: roomTypeOf(c) }))
},
async setRoomLine(id, line) {
return republish(id, (c) => ({ ...c, line: line === "SWITCHBOARD" ? "SWITCHBOARD" : "DND" }))
},
@ -782,6 +789,38 @@ module.exports = ({ cooler, tribeCrypto, roomCrypto, tribesModel }) => {
return asP(ph.camera, !!flag)
},
async stream(opts) {
const ph = await phone()
if (!ph || typeof ph.stream !== "function") return null
return asP(ph.stream, opts || {})
},
async viewerJoin(room) {
const ph = await phone()
if (!ph || typeof ph.viewerJoin !== "function") return null
const params = await this.liveParams(room)
if (!params || params.secrets) return null
return asP(ph.viewerJoin, params)
},
async viewerLeave(rid) {
const ph = await phone()
if (!ph || typeof ph.viewerLeave !== "function") return null
return asP(ph.viewerLeave, { rid: String(rid || "") })
},
async viewerRooms() {
const ph = await phone()
if (!ph || typeof ph.viewerRooms !== "function") return []
const list = await rpcValue(ph.viewerRooms)
return Array.isArray(list) ? list : []
},
async publicFeed(rid, kind) {
const ph = await phone()
return ph && typeof ph.publicFeed === "function" ? ph.publicFeed({ rid: String(rid || ""), kind: kind === "sound" ? "sound" : "grid" }) : null
},
async recStart() {
const ph = await phone()
if (!ph || typeof ph.roomRecStart !== "function") return null