Fix Signal compose live updates and self-chat direction
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
from urllib.parse import quote_plus, urlparse
|
from urllib.parse import quote_plus, urlparse
|
||||||
|
|
||||||
@@ -198,10 +199,18 @@ def _identifier_candidates(*values):
|
|||||||
seen = set()
|
seen = set()
|
||||||
for value in values:
|
for value in values:
|
||||||
cleaned = str(value or "").strip()
|
cleaned = str(value or "").strip()
|
||||||
if not cleaned or cleaned in seen:
|
if not cleaned:
|
||||||
continue
|
continue
|
||||||
seen.add(cleaned)
|
candidates = [cleaned]
|
||||||
out.append(cleaned)
|
digits = re.sub(r"[^0-9]", "", cleaned)
|
||||||
|
# Add basic E.164 variants for phone-shaped values.
|
||||||
|
if digits and cleaned.count("-") < 4:
|
||||||
|
candidates.extend([digits, f"+{digits}"])
|
||||||
|
for candidate in candidates:
|
||||||
|
if not candidate or candidate in seen:
|
||||||
|
continue
|
||||||
|
seen.add(candidate)
|
||||||
|
out.append(candidate)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -349,12 +358,32 @@ class HandleMessage(Command):
|
|||||||
ts = c.message.timestamp
|
ts = c.message.timestamp
|
||||||
source_value = c.message.source
|
source_value = c.message.source
|
||||||
envelope = raw.get("envelope", {})
|
envelope = raw.get("envelope", {})
|
||||||
|
destination_number = sent_message.get("destination")
|
||||||
|
|
||||||
|
bot_uuid = str(getattr(c.bot, "bot_uuid", "") or "").strip()
|
||||||
|
bot_phone = str(getattr(c.bot, "phone_number", "") or "").strip()
|
||||||
|
source_uuid_norm = str(source_uuid or "").strip()
|
||||||
|
source_number_norm = str(source_number or "").strip()
|
||||||
|
dest_norm = str(dest or "").strip()
|
||||||
|
destination_number_norm = str(destination_number or "").strip()
|
||||||
|
|
||||||
|
bot_phone_digits = re.sub(r"[^0-9]", "", bot_phone)
|
||||||
|
source_phone_digits = re.sub(r"[^0-9]", "", source_number_norm)
|
||||||
|
dest_phone_digits = re.sub(r"[^0-9]", "", destination_number_norm or dest_norm)
|
||||||
|
|
||||||
# Message originating from us
|
# Message originating from us
|
||||||
same_recipient = source_uuid == dest
|
same_recipient = source_uuid == dest
|
||||||
|
|
||||||
is_from_bot = source_uuid == c.bot.bot_uuid
|
is_from_bot = bool(bot_uuid and source_uuid_norm and source_uuid_norm == bot_uuid)
|
||||||
is_to_bot = dest == c.bot.bot_uuid or dest is None
|
if (not is_from_bot) and bot_phone_digits and source_phone_digits:
|
||||||
|
is_from_bot = source_phone_digits == bot_phone_digits
|
||||||
|
|
||||||
|
# For non-sync incoming events destination is usually absent and points to us.
|
||||||
|
is_to_bot = bool(bot_uuid and dest_norm and dest_norm == bot_uuid)
|
||||||
|
if (not is_to_bot) and bot_phone_digits and dest_phone_digits:
|
||||||
|
is_to_bot = dest_phone_digits == bot_phone_digits
|
||||||
|
if (not is_to_bot) and (not dest_norm) and (not destination_number_norm):
|
||||||
|
is_to_bot = True
|
||||||
|
|
||||||
reply_to_self = same_recipient and is_from_bot # Reply
|
reply_to_self = same_recipient and is_from_bot # Reply
|
||||||
reply_to_others = is_to_bot and not same_recipient # Reply
|
reply_to_others = is_to_bot and not same_recipient # Reply
|
||||||
@@ -363,7 +392,6 @@ class HandleMessage(Command):
|
|||||||
envelope_source_uuid = envelope.get("sourceUuid")
|
envelope_source_uuid = envelope.get("sourceUuid")
|
||||||
envelope_source_number = envelope.get("sourceNumber")
|
envelope_source_number = envelope.get("sourceNumber")
|
||||||
envelope_source = envelope.get("source")
|
envelope_source = envelope.get("source")
|
||||||
destination_number = sent_message.get("destination")
|
|
||||||
|
|
||||||
primary_identifier = dest if is_from_bot else source_uuid
|
primary_identifier = dest if is_from_bot else source_uuid
|
||||||
if dest or destination_number:
|
if dest or destination_number:
|
||||||
@@ -450,6 +478,20 @@ class HandleMessage(Command):
|
|||||||
len(identifiers),
|
len(identifiers),
|
||||||
)
|
)
|
||||||
for identifier in identifiers:
|
for identifier in identifiers:
|
||||||
|
try:
|
||||||
|
await history.apply_reaction(
|
||||||
|
identifier.user,
|
||||||
|
identifier,
|
||||||
|
target_message_id="",
|
||||||
|
target_ts=int(reaction_payload.get("target_ts") or 0),
|
||||||
|
emoji=str(reaction_payload.get("emoji") or ""),
|
||||||
|
source_service="signal",
|
||||||
|
actor=(source_uuid or source_number or ""),
|
||||||
|
remove=bool(reaction_payload.get("remove")),
|
||||||
|
payload=reaction_payload.get("raw") or {},
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
log.warning("Signal reaction history apply failed: %s", exc)
|
||||||
try:
|
try:
|
||||||
await self.ur.xmpp.client.apply_external_reaction(
|
await self.ur.xmpp.client.apply_external_reaction(
|
||||||
identifier.user,
|
identifier.user,
|
||||||
|
|||||||
@@ -22,6 +22,24 @@ def _safe_int(value, default=0):
|
|||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _message_fingerprint(message_row):
|
||||||
|
row = dict(message_row or {})
|
||||||
|
fields = {
|
||||||
|
"id": str(row.get("id") or ""),
|
||||||
|
"ts": int(_safe_int(row.get("ts"), 0)),
|
||||||
|
"text": str(row.get("text") or ""),
|
||||||
|
"display_text": str(row.get("display_text") or ""),
|
||||||
|
"outgoing": bool(row.get("outgoing")),
|
||||||
|
"read_ts": int(_safe_int(row.get("read_ts"), 0)),
|
||||||
|
"delivered_ts": int(_safe_int(row.get("delivered_ts"), 0)),
|
||||||
|
"receipt_payload": row.get("receipt_payload") or {},
|
||||||
|
"reactions": row.get("reactions") or [],
|
||||||
|
"source_service": str(row.get("source_service") or ""),
|
||||||
|
"source_label": str(row.get("source_label") or ""),
|
||||||
|
}
|
||||||
|
return json.dumps(fields, sort_keys=True, separators=(",", ":"))
|
||||||
|
|
||||||
|
|
||||||
def _load_since(user_id, service, identifier, person_id, after_ts, limit):
|
def _load_since(user_id, service, identifier, person_id, after_ts, limit):
|
||||||
person = None
|
person = None
|
||||||
person_identifier = None
|
person_identifier = None
|
||||||
@@ -174,7 +192,7 @@ async def compose_ws_application(scope, receive, send):
|
|||||||
last_ts = 0
|
last_ts = 0
|
||||||
limit = 100
|
limit = 100
|
||||||
last_typing_key = ""
|
last_typing_key = ""
|
||||||
sent_message_ids = set()
|
sent_message_state = {}
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event = None
|
event = None
|
||||||
@@ -205,10 +223,11 @@ async def compose_ws_application(scope, receive, send):
|
|||||||
messages = []
|
messages = []
|
||||||
for msg in raw_messages:
|
for msg in raw_messages:
|
||||||
message_id = str((msg or {}).get("id") or "").strip()
|
message_id = str((msg or {}).get("id") or "").strip()
|
||||||
if message_id and message_id in sent_message_ids:
|
|
||||||
continue
|
|
||||||
if message_id:
|
if message_id:
|
||||||
sent_message_ids.add(message_id)
|
fingerprint = _message_fingerprint(msg)
|
||||||
|
if sent_message_state.get(message_id) == fingerprint:
|
||||||
|
continue
|
||||||
|
sent_message_state[message_id] = fingerprint
|
||||||
messages.append(msg)
|
messages.append(msg)
|
||||||
latest = _safe_int(payload.get("last_ts"), last_ts)
|
latest = _safe_int(payload.get("last_ts"), last_ts)
|
||||||
if resolved_person_id <= 0:
|
if resolved_person_id <= 0:
|
||||||
|
|||||||
@@ -2047,8 +2047,13 @@
|
|||||||
|
|
||||||
const appendBubble = function (msg) {
|
const appendBubble = function (msg) {
|
||||||
const messageId = String(msg && msg.id ? msg.id : "").trim();
|
const messageId = String(msg && msg.id ? msg.id : "").trim();
|
||||||
if (messageId && panelState.seenMessageIds.has(messageId)) {
|
if (messageId) {
|
||||||
return;
|
const existingRow = Array.from(thread.querySelectorAll(".compose-row")).find(function (row) {
|
||||||
|
return String((row.dataset && row.dataset.messageId) || "") === messageId;
|
||||||
|
});
|
||||||
|
if (existingRow) {
|
||||||
|
existingRow.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const row = document.createElement("div");
|
const row = document.createElement("div");
|
||||||
const outgoing = !!msg.outgoing;
|
const outgoing = !!msg.outgoing;
|
||||||
@@ -2114,6 +2119,22 @@
|
|||||||
fallback.textContent = "(no text)";
|
fallback.textContent = "(no text)";
|
||||||
bubble.appendChild(fallback);
|
bubble.appendChild(fallback);
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(msg.reactions) && msg.reactions.length) {
|
||||||
|
const reactionsWrap = document.createElement("div");
|
||||||
|
reactionsWrap.className = "compose-reactions";
|
||||||
|
reactionsWrap.setAttribute("aria-label", "Message reactions");
|
||||||
|
msg.reactions.forEach(function (reaction) {
|
||||||
|
const chip = document.createElement("span");
|
||||||
|
chip.className = "compose-reaction-chip";
|
||||||
|
chip.textContent = String(reaction && reaction.emoji ? reaction.emoji : "");
|
||||||
|
chip.title =
|
||||||
|
String((reaction && reaction.actor) || "Unknown")
|
||||||
|
+ " via "
|
||||||
|
+ String((reaction && reaction.source_service) || "unknown").toUpperCase();
|
||||||
|
reactionsWrap.appendChild(chip);
|
||||||
|
});
|
||||||
|
bubble.appendChild(reactionsWrap);
|
||||||
|
}
|
||||||
|
|
||||||
const meta = document.createElement("p");
|
const meta = document.createElement("p");
|
||||||
meta.className = "compose-msg-meta";
|
meta.className = "compose-msg-meta";
|
||||||
|
|||||||
@@ -129,7 +129,36 @@ def _format_ts_label(ts_value: int) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _is_outgoing(msg: Message) -> bool:
|
def _is_outgoing(msg: Message) -> bool:
|
||||||
return str(msg.custom_author or "").upper() in {"USER", "BOT"}
|
is_outgoing = str(msg.custom_author or "").upper() in {"USER", "BOT"}
|
||||||
|
if not is_outgoing:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Signal self-chat special case:
|
||||||
|
# platform-originated Signal sync events can carry our own sender id and
|
||||||
|
# are currently stored as USER; render them as counterpart-side so the
|
||||||
|
# thread reads naturally when messaging ourselves.
|
||||||
|
try:
|
||||||
|
session = getattr(msg, "session", None)
|
||||||
|
identifier_obj = getattr(session, "identifier", None)
|
||||||
|
service = str(getattr(identifier_obj, "service", "") or "").strip().lower()
|
||||||
|
if service != "signal":
|
||||||
|
return True
|
||||||
|
|
||||||
|
sender_uuid = str(getattr(msg, "sender_uuid", "") or "").strip()
|
||||||
|
identifier_value = str(getattr(identifier_obj, "identifier", "") or "").strip()
|
||||||
|
if not sender_uuid or not identifier_value:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if sender_uuid.lower() == identifier_value.lower():
|
||||||
|
return False
|
||||||
|
sender_digits = re.sub(r"[^0-9]", "", sender_uuid)
|
||||||
|
identifier_digits = re.sub(r"[^0-9]", "", identifier_value)
|
||||||
|
if sender_digits and identifier_digits and sender_digits == identifier_digits:
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return is_outgoing
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _clean_url(candidate: str) -> str:
|
def _clean_url(candidate: str) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user