feat(meshchat): improve error handling and cleanup processes

This commit is contained in:
2026-01-02 20:35:54 -06:00
parent e1cf6e4809
commit 686b6374bf

View File

@@ -2,8 +2,11 @@
import argparse
import asyncio
import atexit
import base64
import configparser
import copy
import gc
import io
import ipaddress
import json
@@ -11,15 +14,18 @@ import os
import platform
import secrets
import shutil
import socket
import ssl
import sys
import tempfile
import threading
import time
import traceback
import webbrowser
import zipfile
from collections.abc import Callable
from datetime import UTC, datetime, timedelta
import hashlib
import bcrypt
import LXMF
@@ -260,7 +266,8 @@ class ReticulumMeshChat:
self.database.initialize()
# Try to auto-migrate from legacy database if this is a fresh start
self.database.migrate_from_legacy(
self.reticulum_config_dir, identity.hash.hex()
self.reticulum_config_dir,
identity.hash.hex(),
)
self._tune_sqlite_pragmas()
except Exception as exc:
@@ -413,7 +420,7 @@ class ReticulumMeshChat:
# init RNStatus handler
self.rnstatus_handler = RNStatusHandler(
reticulum_instance=getattr(self, "reticulum", None)
reticulum_instance=getattr(self, "reticulum", None),
)
# init RNProbe handler
@@ -430,7 +437,8 @@ class ReticulumMeshChat:
# start background thread for auto announce loop
thread = threading.Thread(
target=asyncio.run, args=(self.announce_loop(session_id),)
target=asyncio.run,
args=(self.announce_loop(session_id),),
)
thread.daemon = True
thread.start()
@@ -657,7 +665,7 @@ class ReticulumMeshChat:
if match:
print(
f"Deregistering RNS destination {destination} ({RNS.prettyhexrep(destination.hash)})"
f"Deregistering RNS destination {destination} ({RNS.prettyhexrep(destination.hash)})",
)
RNS.Transport.deregister_destination(destination)
except Exception as e:
@@ -703,7 +711,7 @@ class ReticulumMeshChat:
# Deregister delivery destinations
if hasattr(self.message_router, "delivery_destinations"):
for dest_hash in list(
self.message_router.delivery_destinations.keys()
self.message_router.delivery_destinations.keys(),
):
dest = self.message_router.delivery_destinations[dest_hash]
RNS.Transport.deregister_destination(dest)
@@ -714,7 +722,7 @@ class ReticulumMeshChat:
and self.message_router.propagation_destination
):
RNS.Transport.deregister_destination(
self.message_router.propagation_destination
self.message_router.propagation_destination,
)
if hasattr(self, "telephone_manager") and self.telephone_manager:
@@ -727,7 +735,7 @@ class ReticulumMeshChat:
and self.telephone_manager.telephone.destination
):
RNS.Transport.deregister_destination(
self.telephone_manager.telephone.destination
self.telephone_manager.telephone.destination,
)
# Use the global helper for thorough cleanup
@@ -842,18 +850,27 @@ class ReticulumMeshChat:
except Exception:
pass
# For LocalServerInterface which Reticulum doesn't close properly
if hasattr(interface, "server") and interface.server:
try:
interface.server.shutdown()
interface.server.server_close()
except Exception:
pass
# TCPClientInterface/etc
if hasattr(interface, "socket") and interface.socket:
try:
import socket
# Check if socket is still valid before shutdown
if interface.socket.fileno() != -1:
if (
hasattr(interface.socket, "fileno")
and interface.socket.fileno() != -1
):
try:
interface.socket.shutdown(socket.SHUT_RDWR)
except Exception:
pass
interface.socket.close()
interface.socket.close()
except Exception:
pass
@@ -911,8 +928,6 @@ class ReticulumMeshChat:
# Unregister old exit handlers from atexit if possible
try:
import atexit
# Reticulum uses a staticmethod exit_handler
atexit.unregister(RNS.Reticulum.exit_handler)
except Exception:
@@ -932,43 +947,178 @@ class ReticulumMeshChat:
rpc_addrs = []
if old_reticulum:
if hasattr(old_reticulum, "rpc_addr") and old_reticulum.rpc_addr:
rpc_addrs.append((old_reticulum.rpc_addr, getattr(old_reticulum, "rpc_type", "AF_INET")))
rpc_addrs.append(
(
old_reticulum.rpc_addr,
getattr(old_reticulum, "rpc_type", "AF_INET"),
),
)
# Also check the config file for ports
try:
config_dir = getattr(self, "reticulum_config_dir", None)
if not config_dir:
if hasattr(RNS.Reticulum, "configdir") and RNS.Reticulum.configdir:
config_dir = RNS.Reticulum.configdir
else:
config_dir = os.path.expanduser("~/.reticulum")
config_path = os.path.join(config_dir, "config")
if os.path.isfile(config_path):
cp = configparser.ConfigParser()
cp.read(config_path)
if cp.has_section("reticulum"):
rpc_port = cp.getint("reticulum", "rpc_port", fallback=37429)
rpc_bind = cp.get("reticulum", "rpc_bind", fallback="127.0.0.1")
shared_port = cp.getint(
"reticulum",
"shared_instance_port",
fallback=37428,
)
shared_bind = cp.get(
"reticulum",
"shared_instance_bind",
fallback="127.0.0.1",
)
# Only add if not already there
if not any(
addr == (rpc_bind, rpc_port) for addr, _ in rpc_addrs
):
rpc_addrs.append(((rpc_bind, rpc_port), "AF_INET"))
if not any(
addr == (shared_bind, shared_port) for addr, _ in rpc_addrs
):
rpc_addrs.append(((shared_bind, shared_port), "AF_INET"))
except Exception as e:
print(f"Warning reading Reticulum config for ports: {e}")
if not rpc_addrs:
# Defaults
rpc_addrs.append((("127.0.0.1", 37429), "AF_INET"))
rpc_addrs.append((("127.0.0.1", 37428), "AF_INET"))
if platform.system() == "Linux":
rpc_addrs.append(("\0rns/default/rpc", "AF_UNIX"))
for i in range(10):
for i in range(15):
await asyncio.sleep(1)
all_free = True
for addr, family_str in rpc_addrs:
try:
import socket
family = socket.AF_INET if family_str == "AF_INET" else socket.AF_UNIX
family = (
socket.AF_INET
if family_str == "AF_INET"
else socket.AF_UNIX
)
s = socket.socket(family, socket.SOCK_STREAM)
s.settimeout(0.5)
try:
# Use SO_REUSEADDR to check if we can actually bind
if family == socket.AF_INET:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.close()
except OSError:
print(f"RPC addr {addr} still in use... (attempt {i+1}/10)")
print(
f"RPC addr {addr} still in use... (attempt {i + 1}/15)",
)
s.close()
all_free = False
# If we are stuck, try to force close the connection manually
if i > 5:
try:
current_process = psutil.Process()
# We use kind='all' to catch both TCP and UNIX sockets
for conn in current_process.connections(kind="all"):
try:
match = False
if conn.laddr:
if (
family_str == "AF_INET"
and isinstance(conn.laddr, tuple)
):
# Match IP and port for IPv4
if conn.laddr.port == addr[1] and (
conn.laddr.ip == addr[0]
or addr[0] == "0.0.0.0"
):
match = True
elif (
family_str == "AF_UNIX"
and conn.laddr == addr
):
# Match path for UNIX sockets
match = True
if match:
# If we found a match, force close the file descriptor
# to tell the OS to release the socket immediately.
status_str = getattr(
conn,
"status",
"UNKNOWN",
)
print(
f"Force closing lingering {family_str} connection {conn.laddr} (status: {status_str})",
)
try:
if (
hasattr(conn, "fd")
and conn.fd != -1
):
os.close(conn.fd)
except Exception as fd_err:
print(
f"Failed to close FD {getattr(conn, 'fd', 'N/A')}: {fd_err}",
)
except Exception:
pass
except Exception as e:
print(
f"Error during manual RPC connection kill: {e}",
)
break
except Exception as e:
print(f"Error checking RPC addr {addr}: {e}")
if all_free:
print("All RNS ports/sockets are free.")
break
if not all_free:
raise OSError("Timeout waiting for RNS ports to be released. Cannot restart.")
# One last attempt with a very short sleep before failing
await asyncio.sleep(2)
# Check again one last time
last_check_all_free = True
for addr, family_str in rpc_addrs:
try:
family = (
socket.AF_INET
if family_str == "AF_INET"
else socket.AF_UNIX
)
s = socket.socket(family, socket.SOCK_STREAM)
try:
s.bind(addr)
s.close()
except OSError:
last_check_all_free = False
s.close()
break
except Exception:
pass
if not last_check_all_free:
raise OSError(
"Timeout waiting for RNS ports to be released. Cannot restart.",
)
else:
print("RNS ports finally free after last-second check.")
# Final GC to ensure everything is released
import gc
gc.collect()
# Re-setup identity (this starts background loops again)
@@ -978,7 +1128,6 @@ class ReticulumMeshChat:
return True
except Exception as e:
print(f"Hot reload failed: {e}")
import traceback
traceback.print_exc()
@@ -1010,9 +1159,9 @@ class ReticulumMeshChat:
# 2. update main identity file
main_identity_file = self.identity_file_path or os.path.join(
self.storage_dir, "identity"
self.storage_dir,
"identity",
)
import shutil
shutil.copy2(identity_file, main_identity_file)
@@ -1033,14 +1182,13 @@ class ReticulumMeshChat:
if hasattr(self, "config")
else "Unknown"
),
}
},
),
)
return True
except Exception as e:
print(f"Hotswap failed: {e}")
import traceback
traceback.print_exc()
return False
@@ -1093,10 +1241,10 @@ class ReticulumMeshChat:
display_name = temp_config_dao.get("display_name", "Anonymous Peer")
icon_name = temp_config_dao.get("lxmf_user_icon_name")
icon_foreground_colour = temp_config_dao.get(
"lxmf_user_icon_foreground_colour"
"lxmf_user_icon_foreground_colour",
)
icon_background_colour = temp_config_dao.get(
"lxmf_user_icon_background_colour"
"lxmf_user_icon_background_colour",
)
temp_provider.close()
except Exception as e:
@@ -1110,7 +1258,7 @@ class ReticulumMeshChat:
"icon_foreground_colour": icon_foreground_colour,
"icon_background_colour": icon_background_colour,
"is_current": identity_hash == self.identity.hash.hex(),
}
},
)
return identities
@@ -1155,8 +1303,6 @@ class ReticulumMeshChat:
identity_dir = os.path.join(self.storage_dir, "identities", identity_hash)
if os.path.exists(identity_dir):
import shutil
shutil.rmtree(identity_dir)
return True
return False
@@ -1658,7 +1804,8 @@ class ReticulumMeshChat:
if self.telephone_manager.telephone:
# Use a small delay to ensure LXST state is ready for hangup
threading.Timer(
0.5, lambda: self.telephone_manager.telephone.hangup()
0.5,
lambda: self.telephone_manager.telephone.hangup(),
).start()
return
@@ -1669,7 +1816,8 @@ class ReticulumMeshChat:
print(f"Rejecting incoming call from non-contact: {caller_hash}")
if self.telephone_manager.telephone:
threading.Timer(
0.5, lambda: self.telephone_manager.telephone.hangup()
0.5,
lambda: self.telephone_manager.telephone.hangup(),
).start()
return
@@ -1758,7 +1906,7 @@ class ReticulumMeshChat:
is_filtered = True
elif self.config.telephone_allow_calls_from_contacts_only.get():
contact = self.database.contacts.get_contact_by_identity_hash(
remote_identity_hash
remote_identity_hash,
)
if not contact:
is_filtered = True
@@ -2992,7 +3140,9 @@ class ReticulumMeshChat:
),
"is_connected_to_shared_instance": (
getattr(
self.reticulum, "is_connected_to_shared_instance", False
self.reticulum,
"is_connected_to_shared_instance",
False,
)
if hasattr(self, "reticulum") and self.reticulum
else False
@@ -3322,21 +3472,19 @@ class ReticulumMeshChat:
# fallback to restart if hotswap failed
# (this part should probably be unreachable if hotswap is reliable)
main_identity_file = self.identity_file_path or os.path.join(
self.storage_dir, "identity"
self.storage_dir,
"identity",
)
identity_dir = os.path.join(
self.storage_dir, "identities", identity_hash
self.storage_dir,
"identities",
identity_hash,
)
identity_file = os.path.join(identity_dir, "identity")
import shutil
shutil.copy2(identity_file, main_identity_file)
def restart():
import os
import sys
import time
time.sleep(1)
try:
os.execv(sys.executable, [sys.executable] + sys.argv)
@@ -3344,8 +3492,6 @@ class ReticulumMeshChat:
print(f"Failed to restart: {e}")
os._exit(0)
import threading
threading.Thread(target=restart).start()
return web.json_response(
@@ -3432,11 +3578,10 @@ class ReticulumMeshChat:
success = await self.reload_reticulum()
if success:
return web.json_response({"message": "Reticulum reloaded successfully"})
else:
return web.json_response(
{"error": "Failed to reload Reticulum"},
status=500,
)
return web.json_response(
{"error": "Failed to reload Reticulum"},
status=500,
)
# serve telephone status
@routes.get("/api/v1/telephone/status")
@@ -3465,7 +3610,7 @@ class ReticulumMeshChat:
telephone_active_call.get_remote_identity().hash.hex()
)
contact = self.database.contacts.get_contact_by_identity_hash(
caller_hash
caller_hash,
)
if not contact:
# Don't report active call if contacts-only is on and caller is not a contact
@@ -3490,7 +3635,7 @@ class ReticulumMeshChat:
"delivery",
).hex()
remote_icon = self.database.misc.get_user_icon(
lxmf_destination_hash
lxmf_destination_hash,
)
active_call = {
@@ -3517,8 +3662,8 @@ class ReticulumMeshChat:
"call_start_time": self.telephone_manager.call_start_time,
"is_contact": bool(
self.database.contacts.get_contact_by_identity_hash(
remote_identity_hash
)
remote_identity_hash,
),
)
if remote_identity_hash
else False,
@@ -3634,7 +3779,7 @@ class ReticulumMeshChat:
remote_identity_hash = d.get("remote_identity_hash")
if remote_identity_hash:
lxmf_hash = self.get_lxmf_destination_hash_for_identity_hash(
remote_identity_hash
remote_identity_hash,
)
if lxmf_hash:
icon = self.database.misc.get_user_icon(lxmf_hash)
@@ -3642,8 +3787,8 @@ class ReticulumMeshChat:
d["remote_icon"] = dict(icon)
d["is_contact"] = bool(
self.database.contacts.get_contact_by_identity_hash(
remote_identity_hash
)
remote_identity_hash,
),
)
call_history.append(d)
@@ -3827,7 +3972,7 @@ class ReticulumMeshChat:
remote_identity_hash = d.get("remote_identity_hash")
if remote_identity_hash:
lxmf_hash = self.get_lxmf_destination_hash_for_identity_hash(
remote_identity_hash
remote_identity_hash,
)
if lxmf_hash:
icon = self.database.misc.get_user_icon(lxmf_hash)
@@ -4006,12 +4151,13 @@ class ReticulumMeshChat:
return web.json_response({"message": "Ringtone not found"}, status=404)
filepath = self.ringtone_manager.get_ringtone_path(
ringtone["storage_filename"]
ringtone["storage_filename"],
)
if os.path.exists(filepath):
return web.FileResponse(filepath)
return web.json_response(
{"message": "Ringtone audio file not found"}, status=404
{"message": "Ringtone audio file not found"},
status=404,
)
@routes.post("/api/v1/telephone/ringtones/upload")
@@ -4021,7 +4167,8 @@ class ReticulumMeshChat:
field = await reader.next()
if field.name != "file":
return web.json_response(
{"message": "File field required"}, status=400
{"message": "File field required"},
status=400,
)
filename = field.filename
@@ -4118,7 +4265,7 @@ class ReticulumMeshChat:
remote_identity_hash = d.get("remote_identity_hash")
if remote_identity_hash:
lxmf_hash = self.get_lxmf_destination_hash_for_identity_hash(
remote_identity_hash
remote_identity_hash,
)
if lxmf_hash:
icon = self.database.misc.get_user_icon(lxmf_hash)
@@ -4136,7 +4283,8 @@ class ReticulumMeshChat:
if not name or not remote_identity_hash:
return web.json_response(
{"message": "Name and identity hash required"}, status=400
{"message": "Name and identity hash required"},
status=400,
)
self.database.contacts.add_contact(name, remote_identity_hash)
@@ -4150,7 +4298,9 @@ class ReticulumMeshChat:
remote_identity_hash = data.get("remote_identity_hash")
self.database.contacts.update_contact(
contact_id, name, remote_identity_hash
contact_id,
name,
remote_identity_hash,
)
return web.json_response({"message": "Contact updated"})
@@ -4168,7 +4318,7 @@ class ReticulumMeshChat:
{
"is_contact": contact is not None,
"contact": dict(contact) if contact else None,
}
},
)
# announce
@@ -4594,7 +4744,7 @@ class ReticulumMeshChat:
if hasattr(self, "reticulum") and self.reticulum:
next_hop_bytes = self.reticulum.get_next_hop(destination_hash)
next_hop_interface = self.reticulum.get_next_hop_if_name(
destination_hash
destination_hash,
)
# ensure next hop provided
@@ -5543,7 +5693,7 @@ class ReticulumMeshChat:
# unless we stored the raw bytes. MeshChatX seems to store fields.
return web.json_response(
{
"message": "Original message bytes not available for URI generation"
"message": "Original message bytes not available for URI generation",
},
status=404,
)
@@ -5766,7 +5916,7 @@ class ReticulumMeshChat:
if destination_hashes:
# mark LXMF conversations as viewed
self.database.messages.mark_all_notifications_as_viewed(
destination_hashes
destination_hashes,
)
if notification_ids:
@@ -5783,13 +5933,14 @@ class ReticulumMeshChat:
async def notifications_get(request):
try:
filter_unread = ReticulumMeshChat.parse_bool_query_param(
request.query.get("unread", "false")
request.query.get("unread", "false"),
)
limit = int(request.query.get("limit", 50))
# 1. Fetch system notifications
system_notifications = self.database.misc.get_notifications(
filter_unread=filter_unread, limit=limit
filter_unread=filter_unread,
limit=limit,
)
# 2. Fetch unread LXMF conversations if requested
@@ -5797,7 +5948,8 @@ class ReticulumMeshChat:
if filter_unread:
local_hash = self.local_lxmf_destination.hexhash
db_conversations = self.message_handler.get_conversations(
local_hash, filter_unread=True
local_hash,
filter_unread=True,
)
for db_message in db_conversations:
# Convert to dict if needed
@@ -5812,11 +5964,11 @@ class ReticulumMeshChat:
# Determine display name
display_name = self.get_name_for_lxmf_destination_hash(
other_user_hash
other_user_hash,
)
custom_display_name = (
self.database.announces.get_custom_display_name(
other_user_hash
other_user_hash,
)
)
@@ -5840,9 +5992,10 @@ class ReticulumMeshChat:
"content"
][:100],
"updated_at": datetime.fromtimestamp(
latest_message_data["timestamp"] or 0, UTC
latest_message_data["timestamp"] or 0,
UTC,
).isoformat(),
}
},
)
# Combine and sort by timestamp
@@ -5859,7 +6012,7 @@ class ReticulumMeshChat:
if n["remote_hash"]:
# Try to find associated LXMF hash for telephony identity hash
lxmf_hash = self.get_lxmf_destination_hash_for_identity_hash(
n["remote_hash"]
n["remote_hash"],
)
if not lxmf_hash:
# Fallback to direct name lookup by identity hash
@@ -5869,7 +6022,7 @@ class ReticulumMeshChat:
)
else:
display_name = self.get_name_for_lxmf_destination_hash(
lxmf_hash
lxmf_hash,
)
icon = self.database.misc.get_user_icon(lxmf_hash)
@@ -5884,9 +6037,10 @@ class ReticulumMeshChat:
"content": n["content"],
"is_viewed": n["is_viewed"] == 1,
"updated_at": datetime.fromtimestamp(
n["timestamp"] or 0, UTC
n["timestamp"] or 0,
UTC,
).isoformat(),
}
},
)
all_notifications.extend(conversations)
@@ -5901,7 +6055,8 @@ class ReticulumMeshChat:
lxmf_unread_count = 0
local_hash = self.local_lxmf_destination.hexhash
unread_conversations = self.message_handler.get_conversations(
local_hash, filter_unread=True
local_hash,
filter_unread=True,
)
if unread_conversations:
lxmf_unread_count = len(unread_conversations)
@@ -5912,7 +6067,7 @@ class ReticulumMeshChat:
{
"notifications": all_notifications[:limit],
"unread_count": total_unread_count,
}
},
)
except Exception as e:
RNS.log(f"Error in notifications_get: {e}", RNS.LOG_ERROR)
@@ -6377,10 +6532,8 @@ class ReticulumMeshChat:
secret_key_bytes = secret_key_bytes[:32]
except Exception:
# Fallback to direct encoding and hashing to get exactly 32 bytes
import hashlib
secret_key_bytes = hashlib.sha256(
self.session_secret_key.encode("utf-8")
self.session_secret_key.encode("utf-8"),
).digest()
setup_session(
@@ -6484,19 +6637,19 @@ class ReticulumMeshChat:
if "auto_resend_failed_messages_when_announce_received" in data:
value = self._parse_bool(
data["auto_resend_failed_messages_when_announce_received"]
data["auto_resend_failed_messages_when_announce_received"],
)
self.config.auto_resend_failed_messages_when_announce_received.set(value)
if "allow_auto_resending_failed_messages_with_attachments" in data:
value = self._parse_bool(
data["allow_auto_resending_failed_messages_with_attachments"]
data["allow_auto_resending_failed_messages_with_attachments"],
)
self.config.allow_auto_resending_failed_messages_with_attachments.set(value)
if "auto_send_failed_messages_to_propagation_node" in data:
value = self._parse_bool(
data["auto_send_failed_messages_to_propagation_node"]
data["auto_send_failed_messages_to_propagation_node"],
)
self.config.auto_send_failed_messages_to_propagation_node.set(value)
@@ -6583,7 +6736,7 @@ class ReticulumMeshChat:
# update archiver settings
if "page_archiver_enabled" in data:
self.config.page_archiver_enabled.set(
self._parse_bool(data["page_archiver_enabled"])
self._parse_bool(data["page_archiver_enabled"]),
)
if "page_archiver_max_versions" in data:
@@ -6623,7 +6776,7 @@ class ReticulumMeshChat:
# update map settings
if "map_offline_enabled" in data:
self.config.map_offline_enabled.set(
self._parse_bool(data["map_offline_enabled"])
self._parse_bool(data["map_offline_enabled"]),
)
if "map_default_lat" in data:
@@ -6640,7 +6793,7 @@ class ReticulumMeshChat:
if "map_tile_cache_enabled" in data:
self.config.map_tile_cache_enabled.set(
self._parse_bool(data["map_tile_cache_enabled"])
self._parse_bool(data["map_tile_cache_enabled"]),
)
if "map_tile_server_url" in data:
@@ -6652,7 +6805,7 @@ class ReticulumMeshChat:
# update voicemail settings
if "voicemail_enabled" in data:
self.config.voicemail_enabled.set(
self._parse_bool(data["voicemail_enabled"])
self._parse_bool(data["voicemail_enabled"]),
)
if "voicemail_greeting" in data:
@@ -6671,7 +6824,7 @@ class ReticulumMeshChat:
# update ringtone settings
if "custom_ringtone_enabled" in data:
self.config.custom_ringtone_enabled.set(
self._parse_bool(data["custom_ringtone_enabled"])
self._parse_bool(data["custom_ringtone_enabled"]),
)
# send config to websocket clients
@@ -7157,7 +7310,9 @@ class ReticulumMeshChat:
elif _type == "lxmf.forwarding.rule.add":
rule_data = data.get("rule")
if not rule_data or "forward_to_hash" not in rule_data:
print("Missing rule data or forward_to_hash in lxmf.forwarding.rule.add")
print(
"Missing rule data or forward_to_hash in lxmf.forwarding.rule.add",
)
return
self.database.misc.create_forwarding_rule(
@@ -7208,7 +7363,7 @@ class ReticulumMeshChat:
try:
# ensure uri starts with lxmf:// or lxm://
if not uri.lower().startswith(
LXMF.LXMessage.URI_SCHEMA + "://"
LXMF.LXMessage.URI_SCHEMA + "://",
) and not uri.lower().startswith("lxm://"):
if ":" in uri and "//" not in uri:
uri = LXMF.LXMessage.URI_SCHEMA + "://" + uri
@@ -7271,16 +7426,16 @@ class ReticulumMeshChat:
if destination_identity is None:
# try to find in database
announce = self.database.announces.get_announce_by_hash(
destination_hash
destination_hash,
)
if announce and announce.get("identity_public_key"):
destination_identity = RNS.Identity.from_bytes(
base64.b64decode(announce["identity_public_key"])
base64.b64decode(announce["identity_public_key"]),
)
if destination_identity is None:
raise Exception(
"Recipient identity not found. Please wait for an announce or add them as a contact."
"Recipient identity not found. Please wait for an announce or add them as a contact.",
)
lxmf_destination = RNS.Destination(
@@ -7351,7 +7506,9 @@ class ReticulumMeshChat:
action = data["action"]
keys = json.dumps(data["keys"])
self.database.misc.upsert_keyboard_shortcut(
self.identity.hexhash, action, keys
self.identity.hexhash,
action,
keys,
)
# notify updated
AsyncUtils.run_async(
@@ -7780,7 +7937,7 @@ class ReticulumMeshChat:
# Also update display name if telephony one was empty
if not display_name or display_name == "Anonymous Peer":
display_name = self.parse_lxmf_display_name(
lxmf_a["app_data"]
lxmf_a["app_data"],
)
break
@@ -8673,7 +8830,9 @@ class ReticulumMeshChat:
):
# check if announced identity or its hash is missing
if not announced_identity or not announced_identity.hash:
print(f"Dropping announce with missing identity or hash: {RNS.prettyhexrep(destination_hash)}")
print(
f"Dropping announce with missing identity or hash: {RNS.prettyhexrep(destination_hash)}",
)
return
# check if source is blocked - drop announce and path if blocked