diff --git a/cx_setup.py b/cx_setup.py index c502d9c..694209f 100644 --- a/cx_setup.py +++ b/cx_setup.py @@ -20,7 +20,9 @@ changelog_path = ROOT / "CHANGELOG.md" if changelog_path.exists(): include_files.append((str(changelog_path), "CHANGELOG.md")) -frontend_licenses_path = ROOT / "meshchatx" / "src" / "backend" / "data" / "licenses_frontend.json" +frontend_licenses_path = ( + ROOT / "meshchatx" / "src" / "backend" / "data" / "licenses_frontend.json" +) if frontend_licenses_path.exists(): include_files.append((str(frontend_licenses_path), "licenses_frontend.json")) diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py index 2183bbe..111bcae 100644 --- a/meshchatx/meshchat.py +++ b/meshchatx/meshchat.py @@ -426,7 +426,9 @@ class ReticulumMeshChat: @property def rnpath_trace_handler(self): - return self.current_context.rnpath_trace_handler if self.current_context else None + return ( + self.current_context.rnpath_trace_handler if self.current_context else None + ) @rnpath_trace_handler.setter def rnpath_trace_handler(self, value): @@ -471,7 +473,11 @@ class ReticulumMeshChat: @property def community_interfaces_manager(self): - return self.current_context.community_interfaces_manager if self.current_context else None + return ( + self.current_context.community_interfaces_manager + if self.current_context + else None + ) @community_interfaces_manager.setter def community_interfaces_manager(self, value): @@ -480,7 +486,11 @@ class ReticulumMeshChat: @property def local_lxmf_destination(self): - return self.current_context.local_lxmf_destination if self.current_context else None + return ( + self.current_context.local_lxmf_destination + if self.current_context + else None + ) @local_lxmf_destination.setter def local_lxmf_destination(self, value): @@ -495,7 +505,11 @@ class ReticulumMeshChat: @property def storage_path(self): - return self.current_context.storage_path if self.current_context else self.storage_dir + return ( + self.current_context.storage_path + if self.current_context + else self.storage_dir + ) @storage_path.setter def storage_path(self, value): @@ -814,7 +828,10 @@ class ReticulumMeshChat: match = False # check if local identity or destination matches if hasattr(link, "destination") and link.destination: - if hasattr(link.destination, "identity") and link.destination.identity: + if ( + hasattr(link.destination, "identity") + and link.destination.identity + ): if link.destination.identity.hash == identity_hash_bytes: match = True @@ -971,7 +988,11 @@ class ReticulumMeshChat: else: continue - laddr_no_nul = laddr_bytes[1:] if laddr_bytes.startswith(b"\0") else laddr_bytes + laddr_no_nul = ( + laddr_bytes[1:] + if laddr_bytes.startswith(b"\0") + else laddr_bytes + ) if ( laddr_bytes in (target_bytes, target_no_nul) @@ -1239,9 +1260,13 @@ class ReticulumMeshChat: ) # Only add if not already there - if not any(addr == (rpc_bind, rpc_port) for addr, _ in rpc_addrs): + 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): + 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}") @@ -1256,7 +1281,11 @@ class ReticulumMeshChat: all_free = True for addr, family_str in rpc_addrs: try: - 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: @@ -1316,12 +1345,15 @@ class ReticulumMeshChat: try: current_process = psutil.Process() # We use kind='all' to catch both TCP and UNIX sockets - for conn in current_process.net_connections(kind="all"): + for conn in current_process.net_connections( + kind="all" + ): try: match = False if conn.laddr: - if family_str == "AF_INET" and isinstance( - conn.laddr, tuple + if ( + family_str == "AF_INET" + and isinstance(conn.laddr, tuple) ): # Match IP and port for IPv4 if conn.laddr.port == addr[1] and ( @@ -1357,13 +1389,15 @@ class ReticulumMeshChat: target_addr.startswith( b"\0", ) - and current_laddr == target_addr[1:] + and current_laddr + == target_addr[1:] ) or ( current_laddr.startswith( b"\0", ) - and target_addr == current_laddr[1:] + and target_addr + == current_laddr[1:] ) ): match = True @@ -1391,7 +1425,10 @@ class ReticulumMeshChat: ) try: - if hasattr(conn, "fd") and conn.fd != -1: + if ( + hasattr(conn, "fd") + and conn.fd != -1 + ): try: os.close(conn.fd) except Exception as fd_err: @@ -1420,14 +1457,22 @@ class ReticulumMeshChat: if not all_free: await asyncio.sleep(2) for addr, family_str in rpc_addrs: - if family_str == "AF_UNIX" and isinstance(addr, str) and addr.startswith("\0"): + if ( + family_str == "AF_UNIX" + and isinstance(addr, str) + and addr.startswith("\0") + ): with contextlib.suppress(Exception): self._force_close_abstract_unix_addr(addr) 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 + family = ( + socket.AF_INET + if family_str == "AF_INET" + else socket.AF_UNIX + ) s = socket.socket(family, socket.SOCK_STREAM) try: s.bind(addr) @@ -1465,7 +1510,9 @@ class ReticulumMeshChat: if abstract_unix_addr_in_use_after_wait: original_instance_name = self._read_reticulum_instance_name() base_name = original_instance_name or "default" - switched_instance_name = f"{base_name}-reload-{os.getpid()}-{int(time.time())}" + switched_instance_name = ( + f"{base_name}-reload-{os.getpid()}-{int(time.time())}" + ) self._write_reticulum_instance_name(switched_instance_name) print( "Abstract UNIX RPC address remained busy. " @@ -1560,7 +1607,9 @@ class ReticulumMeshChat: "type": "identity_switched", "identity_hash": identity_hash, "display_name": ( - self.config.display_name.get() if hasattr(self, "config") else "Unknown" + self.config.display_name.get() + if hasattr(self, "config") + else "Unknown" ), }, ), @@ -1635,7 +1684,9 @@ class ReticulumMeshChat: def list_identities(self): return self.identity_manager.list_identities( - self.identity.hash.hex() if hasattr(self, "identity") and self.identity else None, + self.identity.hash.hex() + if hasattr(self, "identity") and self.identity + else None, ) def create_identity(self, display_name=None): @@ -1643,7 +1694,9 @@ class ReticulumMeshChat: def delete_identity(self, identity_hash): current_hash = ( - self.identity.hash.hex() if hasattr(self, "identity") and self.identity else None + self.identity.hash.hex() + if hasattr(self, "identity") and self.identity + else None ) return self.identity_manager.delete_identity(identity_hash, current_hash) @@ -1843,7 +1896,9 @@ class ReticulumMeshChat: sanitized = [] seen = set() for pattern in ReticulumMeshChat.parse_discovery_patterns(value): - cleaned = pattern.replace("\r", "").replace("\n", "").replace(",", "").strip() + cleaned = ( + pattern.replace("\r", "").replace("\n", "").replace(",", "").strip() + ) if not cleaned: continue cleaned = "".join(ch for ch in cleaned if ch.isprintable()).strip() @@ -1891,7 +1946,11 @@ class ReticulumMeshChat: or interface.get("remote") or interface.get("listen_ip") ) - port = interface.get("port") or interface.get("target_port") or interface.get("listen_port") + port = ( + interface.get("port") + or interface.get("target_port") + or interface.get("listen_port") + ) if host and port: candidates.append(f"{host}:{port}") return candidates @@ -1901,7 +1960,8 @@ class ReticulumMeshChat: if not patterns: return False candidates = [ - value.lower() for value in ReticulumMeshChat.discovery_filter_candidates(interface) + value.lower() + for value in ReticulumMeshChat.discovery_filter_candidates(interface) ] for pattern in patterns: normalized_pattern = str(pattern).lower() @@ -1979,7 +2039,10 @@ class ReticulumMeshChat: interface for interface in interfaces if ( - (not whitelist or ReticulumMeshChat.matches_discovery_pattern(whitelist, interface)) + ( + not whitelist + or ReticulumMeshChat.matches_discovery_pattern(whitelist, interface) + ) and not ReticulumMeshChat.matches_discovery_pattern( blacklist, interface, @@ -2047,12 +2110,13 @@ class ReticulumMeshChat: return while self.running and ctx.running and ctx.session_id == session_id: - auto_sync_interval_seconds = ( - ctx.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get() + auto_sync_interval_seconds = ctx.config.lxmf_preferred_propagation_node_auto_sync_interval_seconds.get() + last_synced_at = ( + ctx.config.lxmf_preferred_propagation_node_last_synced_at.get() ) - last_synced_at = ctx.config.lxmf_preferred_propagation_node_last_synced_at.get() should_sync = interval_action_due( - auto_sync_interval_seconds is not None and auto_sync_interval_seconds > 0, + auto_sync_interval_seconds is not None + and auto_sync_interval_seconds > 0, last_synced_at, auto_sync_interval_seconds, time.time(), @@ -2079,11 +2143,16 @@ class ReticulumMeshChat: aspect="nomadnetwork.node", ) for node in known_nodes: - if not self.running or not ctx.running or ctx.session_id != session_id: + if ( + not self.running + or not ctx.running + or ctx.session_id != session_id + ): break self.queue_crawler_task( node["destination_hash"], - ctx.config.nomad_default_page_path.get() or "/page/index.mu", + ctx.config.nomad_default_page_path.get() + or "/page/index.mu", context=ctx, ) @@ -2097,7 +2166,10 @@ class ReticulumMeshChat: # process tasks concurrently up to the limit if tasks and self.running and ctx.running: await asyncio.gather( - *[self.process_crawler_task(task, context=ctx) for task in tasks], + *[ + self.process_crawler_task(task, context=ctx) + for task in tasks + ], ) except Exception as e: @@ -2270,8 +2342,8 @@ class ReticulumMeshChat: metrics["started_at"] = datetime.now(UTC).isoformat() metrics["baseline_total_messages"] = ctx.database.messages.count_lxmf_messages() - metrics["baseline_delivered_messages"] = ctx.database.messages.count_lxmf_messages_by_state( - "delivered" + metrics["baseline_delivered_messages"] = ( + ctx.database.messages.count_lxmf_messages_by_state("delivered") ) metrics["messages_stored"] = 0 metrics["delivery_confirmations"] = 0 @@ -2452,11 +2524,15 @@ class ReticulumMeshChat: "rx_bytes": peer_rx_bytes + unpeered_rx_bytes, "tx_bytes": peer_tx_bytes, "unpeered_rx_bytes": unpeered_rx_bytes, - "static_peers": stats.get("static_peers", 0) if isinstance(stats, dict) else 0, + "static_peers": stats.get("static_peers", 0) + if isinstance(stats, dict) + else 0, "discovered_peers": ( stats.get("discovered_peers", 0) if isinstance(stats, dict) else 0 ), - "total_peers": stats.get("total_peers", 0) if isinstance(stats, dict) else 0, + "total_peers": stats.get("total_peers", 0) + if isinstance(stats, dict) + else 0, "max_peers": stats.get("max_peers") if isinstance(stats, dict) else None, "delivery_limit_bytes": int(delivery_limit * 1000), "propagation_limit_bytes": int(propagation_limit * 1000), @@ -2603,7 +2679,11 @@ class ReticulumMeshChat: }, ) - if hasattr(self, "reticulum") and self.reticulum and not self.reticulum.transport_enabled(): + if ( + hasattr(self, "reticulum") + and self.reticulum + and not self.reticulum.transport_enabled() + ): guidance.append( { "id": "transport_disabled", @@ -2668,7 +2748,9 @@ class ReticulumMeshChat: matches.add(message["source_hash"]) # also check custom display names - custom_names = self.database.announces.get_announces() # Or more specific if needed + custom_names = ( + self.database.announces.get_announces() + ) # Or more specific if needed for announce in custom_names: custom_name = self.database.announces.get_custom_display_name( announce["destination_hash"], @@ -2993,7 +3075,17 @@ class ReticulumMeshChat: or path.startswith(("/assets/", "/favicons/")) or path in ("/manifest.json", "/service-worker.js") or path.endswith( - (".js", ".css", ".json", ".wasm", ".png", ".jpg", ".jpeg", ".ico", ".svg") + ( + ".js", + ".css", + ".json", + ".wasm", + ".png", + ".jpg", + ".jpeg", + ".ico", + ".svg", + ) ) ): return await handler(request) @@ -3026,7 +3118,17 @@ class ReticulumMeshChat: path == "/" or path.startswith(("/assets/", "/favicons/")) or path.endswith( - (".js", ".css", ".json", ".wasm", ".png", ".jpg", ".jpeg", ".ico", ".svg") + ( + ".js", + ".css", + ".json", + ".wasm", + ".png", + ".jpg", + ".jpeg", + ".ico", + ".svg", + ) ) ): is_public = True @@ -3408,7 +3510,8 @@ class ReticulumMeshChat: return web.json_response( { "auth_enabled": self.auth_enabled, - "password_set": self.config.auth_password_hash.get() is not None, + "password_set": self.config.auth_password_hash.get() + is not None, "authenticated": actually_authenticated, }, ) @@ -3417,7 +3520,8 @@ class ReticulumMeshChat: return web.json_response( { "auth_enabled": self.auth_enabled, - "password_set": self.config.auth_password_hash.get() is not None, + "password_set": self.config.auth_password_hash.get() + is not None, "authenticated": False, "error": str(e), }, @@ -3672,7 +3776,9 @@ class ReticulumMeshChat: async with session.get(url, allow_redirects=True) as response: if response.status != 200: return web.json_response( - {"error": f"Failed to fetch release: {response.status}"}, + { + "error": f"Failed to fetch release: {response.status}" + }, status=response.status, ) data = await response.json(content_type=None) @@ -3959,7 +4065,10 @@ class ReticulumMeshChat: interface_details["type"] = interface_type # if interface doesn't have enabled or interface_enabled setting already, enable it by default - if "enabled" not in interface_details and "interface_enabled" not in interface_details: + if ( + "enabled" not in interface_details + and "interface_enabled" not in interface_details + ): interface_details["interface_enabled"] = "true" # handle AutoInterface @@ -4100,7 +4209,8 @@ class ReticulumMeshChat: listen_ip_value = data.get("listen_ip") listen_device_value = data.get("device") if (listen_port_value not in (None, "")) and ( - listen_ip_value not in (None, "") or listen_device_value not in (None, "") + listen_ip_value not in (None, "") + or listen_device_value not in (None, "") ): if is_port_in_use( listen_ip_value, @@ -4145,7 +4255,10 @@ class ReticulumMeshChat: status=422, ) transport_identity = data.get("transport_identity") - if transport_identity is None or str(transport_identity).strip() == "": + if ( + transport_identity is None + or str(transport_identity).strip() == "" + ): return web.json_response( { "message": "Transport identity is required", @@ -4166,7 +4279,8 @@ class ReticulumMeshChat: else: interface_details["connectable"] = ( "True" - if str(connectable_value).lower() in {"true", "yes", "1", "on", "y"} + if str(connectable_value).lower() + in {"true", "yes", "1", "on", "y"} else "False" ) peers = data.get("peers") @@ -4175,7 +4289,9 @@ class ReticulumMeshChat: cleaned_peers = [str(p).strip() for p in peers if str(p).strip()] elif peers is not None and str(peers).strip() != "": cleaned_peers = [ - s.strip() for s in str(peers).replace(",", " ").split() if s.strip() + s.strip() + for s in str(peers).replace(",", " ").split() + if s.strip() ] if not cleaned_peers: return web.json_response( @@ -4614,7 +4730,10 @@ class ReticulumMeshChat: interfaces = self._get_interfaces_snapshot() for interface_name, interface in interfaces.items(): # skip interface if not selected - if selected_interface_names is not None and selected_interface_names != "": + if ( + selected_interface_names is not None + and selected_interface_names != "" + ): if interface_name not in selected_interface_names: continue @@ -4914,12 +5033,19 @@ class ReticulumMeshChat: for conn in process.net_connections(kind="all"): if conn.status == psutil.CONN_ESTABLISHED and conn.raddr: # Check for common Reticulum shared instance ports or UNIX sockets - if isinstance(conn.raddr, tuple) and conn.raddr[1] == 37428: - shared_instance_address = f"{conn.raddr[0]}:{conn.raddr[1]}" + if ( + isinstance(conn.raddr, tuple) + and conn.raddr[1] == 37428 + ): + shared_instance_address = ( + f"{conn.raddr[0]}:{conn.raddr[1]}" + ) break if ( isinstance(conn.raddr, str) - and ("rns" in conn.raddr or "reticulum" in conn.raddr) + and ( + "rns" in conn.raddr or "reticulum" in conn.raddr + ) and ".sock" in conn.raddr ): shared_instance_address = conn.raddr @@ -4949,7 +5075,9 @@ class ReticulumMeshChat: "shared_instance_bind", fallback="127.0.0.1", ) - shared_instance_address = f"{shared_bind}:{shared_port}" + shared_instance_address = ( + f"{shared_bind}:{shared_port}" + ) except Exception: pass @@ -5027,7 +5155,9 @@ class ReticulumMeshChat: "database_file_size": db_files["main_bytes"], "database_files": db_files, "sqlite": { - "journal_mode": _safe_sqlite_pragma("journal_mode", "unknown"), + "journal_mode": _safe_sqlite_pragma( + "journal_mode", "unknown" + ), "synchronous": _safe_sqlite_pragma("synchronous", None), "wal_autocheckpoint": _safe_sqlite_pragma( "wal_autocheckpoint", @@ -5073,7 +5203,8 @@ class ReticulumMeshChat: [], ), "user_guidance": _safe_user_guidance(), - "tutorial_seen": _safe_config_get("tutorial_seen", "false") == "true", + "tutorial_seen": _safe_config_get("tutorial_seen", "false") + == "true", "changelog_seen_version": _safe_config_get( "changelog_seen_version", "0.0.0", @@ -5282,7 +5413,9 @@ class ReticulumMeshChat: async def docs_export(request): try: zip_data = self.docs_manager.export_docs() - filename = f"meshchatx_docs_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}.zip" + filename = ( + f"meshchatx_docs_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}.zip" + ) return web.Response( body=zip_data, content_type="application/zip", @@ -5543,7 +5676,9 @@ class ReticulumMeshChat: if self.database: for item in identities: if item.get("is_current"): - item["message_count"] = self.database.messages.count_lxmf_messages() + item["message_count"] = ( + self.database.messages.count_lxmf_messages() + ) break return web.json_response( { @@ -5956,8 +6091,14 @@ class ReticulumMeshChat: parsed_host = None parsed_port = None - host = s.get("target_host") or s.get("remote") or parsed_host - port = s.get("target_port") or s.get("listen_port") or parsed_port + host = ( + s.get("target_host") or s.get("remote") or parsed_host + ) + port = ( + s.get("target_port") + or s.get("listen_port") + or parsed_port + ) transport_id = s.get("transport_id") if isinstance(transport_id, (bytes, bytearray)): transport_id = transport_id.hex() @@ -5992,8 +6133,10 @@ class ReticulumMeshChat: return [to_jsonable(v) for v in obj] return obj - normalized_interfaces = ReticulumMeshChat.normalize_discovered_ifac_fields( - to_jsonable(interfaces), + normalized_interfaces = ( + ReticulumMeshChat.normalize_discovered_ifac_fields( + to_jsonable(interfaces), + ) ) return web.json_response( @@ -6200,8 +6343,10 @@ class ReticulumMeshChat: remote_identity = telephone_active_call.get_remote_identity() if remote_identity: caller_hash = remote_identity.hash.hex() - contact = self.database.contacts.get_contact_by_identity_hash( - caller_hash, + contact = ( + self.database.contacts.get_contact_by_identity_hash( + caller_hash, + ) ) if not contact: # Don't report active call if contacts-only is on and caller is not a contact @@ -6928,7 +7073,11 @@ class ReticulumMeshChat: ringtone_id = None has_custom = ringtone_id is not None - ringtone = self.database.ringtones.get_by_id(ringtone_id) if has_custom else None + ringtone = ( + self.database.ringtones.get_by_id(ringtone_id) + if has_custom + else None + ) return web.json_response( { @@ -7287,7 +7436,9 @@ class ReticulumMeshChat: if sm is not None and sm > 0: search_max = min(int(sm), 10_000) - include_blocked = request.query.get("include_blocked", "false").lower() == "true" + include_blocked = ( + request.query.get("include_blocked", "false").lower() == "true" + ) blocked_identity_hashes = None if not include_blocked: @@ -7485,7 +7636,9 @@ class ReticulumMeshChat: results = self.database.announces.get_favourites(aspect=aspect) # process favourites - favourites = [convert_db_favourite_to_dict(favourite) for favourite in results] + favourites = [ + convert_db_favourite_to_dict(favourite) for favourite in results + ] return web.json_response( { @@ -7680,7 +7833,9 @@ class ReticulumMeshChat: * 100, # convert to percentage "messages_received": self.message_router.propagation_transfer_last_result, "messages_stored": sync_metrics["messages_stored"], - "delivery_confirmations": sync_metrics["delivery_confirmations"], + "delivery_confirmations": sync_metrics[ + "delivery_confirmations" + ], "messages_hidden": sync_metrics["messages_hidden"], }, "local_propagation_node": self.get_local_propagation_node_stats(), @@ -7787,19 +7942,25 @@ class ReticulumMeshChat: local_destination_hash = local_destination_hash_raw else: local_destination_hash = None - local_stats = self.get_local_propagation_node_stats(context=ctx) if ctx else None + local_stats = ( + self.get_local_propagation_node_stats(context=ctx) if ctx else None + ) for announce in results: # find an lxmf.delivery announce for the same identity hash, so we can use that as an "operater by" name lxmf_delivery_results = self.database.announces.get_filtered_announces( aspect="lxmf.delivery", identity_hash=announce["identity_hash"], ) - lxmf_delivery_announce = lxmf_delivery_results[0] if lxmf_delivery_results else None + lxmf_delivery_announce = ( + lxmf_delivery_results[0] if lxmf_delivery_results else None + ) # find a nomadnetwork.node announce for the same identity hash, so we can use that as an "operated by" name - nomadnetwork_node_results = self.database.announces.get_filtered_announces( - aspect="nomadnetwork.node", - identity_hash=announce["identity_hash"], + nomadnetwork_node_results = ( + self.database.announces.get_filtered_announces( + aspect="nomadnetwork.node", + identity_hash=announce["identity_hash"], + ) ) nomadnetwork_node_announce = ( nomadnetwork_node_results[0] if nomadnetwork_node_results else None @@ -7888,7 +8049,9 @@ class ReticulumMeshChat: else ctx.config.lxmf_local_propagation_node_enabled.get() ), "per_transfer_limit": int( - getattr(ctx.message_router, "propagation_per_transfer_limit", 0), + getattr( + ctx.message_router, "propagation_per_transfer_limit", 0 + ), ), "is_local_node": True, "local_node_stats": local_stats, @@ -8095,7 +8258,8 @@ class ReticulumMeshChat: # get signal metrics from latest lxmf message if it's more recent than the announce if latest_lxmf_message is not None and ( - latest_announce_at is None or latest_lxmf_message_at > latest_announce_at + latest_announce_at is None + or latest_lxmf_message_at > latest_announce_at ): snr = latest_lxmf_message["snr"] rssi = latest_lxmf_message["rssi"] @@ -8137,7 +8301,8 @@ class ReticulumMeshChat: # wait until we have a path, or give up after the configured timeout while ( - not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after_seconds + not RNS.Transport.has_path(destination_hash) + and time.time() < timeout_after_seconds ): await asyncio.sleep(0.1) @@ -8827,7 +8992,9 @@ class ReticulumMeshChat: if ts is not None and hasattr(ts, "isoformat"): bot["last_announce_at"] = ts.isoformat() else: - bot["last_announce_at"] = str(ts) if ts is not None else None + bot["last_announce_at"] = ( + str(ts) if ts is not None else None + ) return web.json_response( { "status": status, @@ -9077,8 +9244,8 @@ class ReticulumMeshChat: ) # get outbound ticket expiry for this lxmf destination - lxmf_outbound_ticket_expiry = self.message_router.get_outbound_ticket_expiry( - destination_hash_bytes + lxmf_outbound_ticket_expiry = ( + self.message_router.get_outbound_ticket_expiry(destination_hash_bytes) ) return web.json_response( @@ -9101,7 +9268,9 @@ class ReticulumMeshChat: # ensure transport_id is hex as json_response can't serialize bytes if "transport_id" in interface_stats: - interface_stats["transport_id"] = interface_stats["transport_id"].hex() + interface_stats["transport_id"] = interface_stats[ + "transport_id" + ].hex() # ensure probe_responder is hex as json_response can't serialize bytes if ( @@ -9126,7 +9295,9 @@ class ReticulumMeshChat: ].hex() if interface.get("ifac_signature"): - interface["ifac_signature"] = interface["ifac_signature"].hex() + interface["ifac_signature"] = interface[ + "ifac_signature" + ].hex() try: if interface.get("hash"): @@ -9167,7 +9338,9 @@ class ReticulumMeshChat: if destination_hashes: hash_set = {h.lower() for h in destination_hashes if isinstance(h, str)} - all_paths = [p for p in all_paths if p["hash"].hex().lower() in hash_set] + all_paths = [ + p for p in all_paths if p["hash"].hex().lower() in hash_set + ] total_count = len(all_paths) @@ -9235,7 +9408,9 @@ class ReticulumMeshChat: file_attachments_field = None if "file_attachments" in fields: file_attachments = [] - for file_attachment in data["lxmf_message"]["fields"]["file_attachments"]: + for file_attachment in data["lxmf_message"]["fields"][ + "file_attachments" + ]: file_name = file_attachment["file_name"] file_bytes = base64.b64decode(file_attachment["file_bytes"]) file_attachments.append(LxmfFileAttachment(file_name, file_bytes)) @@ -9274,7 +9449,9 @@ class ReticulumMeshChat: reply_to_hash = None if "reply_to_hash" in data["lxmf_message"]: reply_to_hash = data["lxmf_message"]["reply_to_hash"] - reply_quoted_content = data["lxmf_message"].get("reply_quoted_content") or None + reply_quoted_content = ( + data["lxmf_message"].get("reply_quoted_content") or None + ) try: # send lxmf message to destination @@ -9445,7 +9622,8 @@ class ReticulumMeshChat: # convert to response json lxmf_messages = [ - convert_db_lxmf_message_to_dict(db_lxmf_message) for db_lxmf_message in results + convert_db_lxmf_message_to_dict(db_lxmf_message) + for db_lxmf_message in results ] return web.json_response( @@ -9582,7 +9760,9 @@ class ReticulumMeshChat: data = await request.json() except Exception: return web.json_response({"message": "invalid json"}, status=400) - destination_hash = data.get("destination_hash") if isinstance(data, dict) else None + destination_hash = ( + data.get("destination_hash") if isinstance(data, dict) else None + ) if not destination_hash: return web.json_response( {"message": "missing destination_hash"}, @@ -9807,7 +9987,9 @@ class ReticulumMeshChat: @routes.get("/api/v1/lxmf/folders/export") async def lxmf_folders_export(request): folders = [dict(f) for f in self.database.messages.get_all_folders()] - mappings = [dict(m) for m in self.database.messages.get_all_conversation_folders()] + mappings = [ + dict(m) for m in self.database.messages.get_all_conversation_folders() + ] return web.json_response({"folders": folders, "mappings": mappings}) @routes.post("/api/v1/lxmf/folders/import") @@ -9936,10 +10118,8 @@ class ReticulumMeshChat: db_message.get("content"), db_message.get("title"), ): - latest_user_facing = ( - self.database.messages.get_latest_user_facing_incoming_message( - other_user_hash, - ) + latest_user_facing = self.database.messages.get_latest_user_facing_incoming_message( + other_user_hash, ) if latest_user_facing is None: continue @@ -9954,7 +10134,10 @@ class ReticulumMeshChat: last_read_dt = last_read_dt.replace( tzinfo=UTC, ) - if latest_user_facing["timestamp"] <= last_read_dt.timestamp(): + if ( + latest_user_facing["timestamp"] + <= last_read_dt.timestamp() + ): continue except (ValueError, TypeError): pass @@ -9973,8 +10156,10 @@ class ReticulumMeshChat: display_name = self.get_lxmf_conversation_name( other_user_hash, ) - custom_display_name = self.database.announces.get_custom_display_name( - other_user_hash, + custom_display_name = ( + self.database.announces.get_custom_display_name( + other_user_hash, + ) ) # Determine latest message data @@ -9993,9 +10178,9 @@ class ReticulumMeshChat: "display_name": display_name, "custom_display_name": custom_display_name, "lxmf_user_icon": dict(icon) if icon else None, - "latest_message_preview": (latest_message_data["content"] or "")[ - :100 - ], + "latest_message_preview": ( + latest_message_data["content"] or "" + )[:100], "updated_at": datetime.fromtimestamp( latest_message_data["timestamp"] or 0, UTC, @@ -10085,10 +10270,8 @@ class ReticulumMeshChat: conv.get("content"), conv.get("title"), ): - latest_user_facing = ( - self.database.messages.get_latest_user_facing_incoming_message( - other_user_hash, - ) + latest_user_facing = self.database.messages.get_latest_user_facing_incoming_message( + other_user_hash, ) if latest_user_facing is None: continue @@ -10102,7 +10285,10 @@ class ReticulumMeshChat: last_read_dt = last_read_dt.replace( tzinfo=UTC, ) - if latest_user_facing["timestamp"] <= last_read_dt.timestamp(): + if ( + latest_user_facing["timestamp"] + <= last_read_dt.timestamp() + ): continue except (ValueError, TypeError): pass @@ -10260,7 +10446,9 @@ class ReticulumMeshChat: formatted = {} for h, info in identities.items(): formatted[h.hex()] = { - "source": info.get("source", b"").hex() if info.get("source") else None, + "source": info.get("source", b"").hex() + if info.get("source") + else None, "until": info.get("until"), "reason": info.get("reason"), } @@ -10710,7 +10898,9 @@ class ReticulumMeshChat: pack_id = int(request.match_info.get("pack_id", "0")) except ValueError: return web.json_response({"error": "invalid_pack_id"}, status=400) - with_stickers = request.query.get("with_stickers", "false").lower() == "true" + with_stickers = ( + request.query.get("with_stickers", "false").lower() == "true" + ) if with_stickers: ok = self.database.sticker_packs.delete_with_stickers( pack_id, @@ -10979,7 +11169,9 @@ class ReticulumMeshChat: "destination_hash": r["destination_hash"], "timestamp": r["timestamp"], "telemetry": unpacked, - "physical_link": json.loads(r["physical_link"]) if r["physical_link"] else None, + "physical_link": json.loads(r["physical_link"]) + if r["physical_link"] + else None, "updated_at": r["updated_at"], }, ) @@ -11120,7 +11312,9 @@ class ReticulumMeshChat: if path.startswith("/api/"): return response if path.endswith((".js", ".mjs")): - response.headers["Content-Type"] = "application/javascript; charset=utf-8" + response.headers["Content-Type"] = ( + "application/javascript; charset=utf-8" + ) elif path.endswith(".css"): response.headers["Content-Type"] = "text/css; charset=utf-8" elif path.endswith(".json"): @@ -11228,7 +11422,9 @@ class ReticulumMeshChat: return sources = [ s.strip() - for s in extra_str.replace("\n", ",").replace(";", ",").split(",") + for s in extra_str.replace("\n", ",") + .replace(";", ",") + .split(",") if s.strip() ] for s in sources: @@ -11367,7 +11563,9 @@ class ReticulumMeshChat: def run(self, host, port, launch_browser: bool, enable_https: bool = True): # create route table routes = web.RouteTableDef() - auth_middleware, mime_type_middleware, security_middleware = self._define_routes(routes) + auth_middleware, mime_type_middleware, security_middleware = ( + self._define_routes(routes) + ) ssl_context = None use_https = enable_https @@ -11638,7 +11836,9 @@ class ReticulumMeshChat: # Local node selected as preferred: no transport path lookup is needed. # Mark sync as complete immediately to avoid getting stuck in PR_PATH_REQUESTED. with contextlib.suppress(Exception): - ctx.message_router.propagation_transfer_state = ctx.message_router.PR_COMPLETE + ctx.message_router.propagation_transfer_state = ( + ctx.message_router.PR_COMPLETE + ) ctx.message_router.propagation_transfer_progress = 1.0 ctx.message_router.propagation_transfer_last_result = 0 await self.send_config_to_websocket_clients(context=ctx) @@ -12876,7 +13076,9 @@ class ReticulumMeshChat: bytes.fromhex(destination_hash_hex) raw_bytes = bytes.fromhex(public_key_hex) - public_key_bytes = raw_bytes[:32] if len(raw_bytes) >= 32 else raw_bytes + public_key_bytes = ( + raw_bytes[:32] if len(raw_bytes) >= 32 else raw_bytes + ) identity = RNS.Identity(create_keys=False) if not identity.load_public_key(public_key_bytes): @@ -12887,8 +13089,10 @@ class ReticulumMeshChat: raise ValueError("Invalid LXMA public key") remote_identity_hash = identity.hash.hex() - existing_contact = self.database.contacts.get_contact_by_identity_hash( - remote_identity_hash, + existing_contact = ( + self.database.contacts.get_contact_by_identity_hash( + remote_identity_hash, + ) ) contact_name = ( existing_contact["name"] @@ -13560,7 +13764,10 @@ class ReticulumMeshChat: # ensure we're not storing the user's own icon with a peer's hash # only store icons for remote peers, not for the local user - if ctx.local_lxmf_destination and destination_hash == ctx.local_lxmf_destination.hexhash: + if ( + ctx.local_lxmf_destination + and destination_hash == ctx.local_lxmf_destination.hexhash + ): print(f"skipping icon update for local user's own hash: {destination_hash}") return @@ -13684,7 +13891,8 @@ class ReticulumMeshChat: and ( SidebandCommands.TELEMETRY_REQUEST in command or str(SidebandCommands.TELEMETRY_REQUEST) in command - or f"0x{SidebandCommands.TELEMETRY_REQUEST:02x}" in command + or f"0x{SidebandCommands.TELEMETRY_REQUEST:02x}" + in command ) ) or ( @@ -13760,7 +13968,9 @@ class ReticulumMeshChat: # check for spam keywords is_spam = False message_title = lxmf_message.title if hasattr(lxmf_message, "title") else "" - message_content = lxmf_message.content if hasattr(lxmf_message, "content") else "" + message_content = ( + lxmf_message.content if hasattr(lxmf_message, "content") else "" + ) if isinstance(message_content, bytes): message_content = message_content.decode("utf-8", errors="replace") elif message_content is None: @@ -13797,8 +14007,9 @@ class ReticulumMeshChat: ) return # strip attachments from strangers (non-contacts) if setting is enabled - if ctx.config.block_attachments_from_strangers.get() and not self._is_contact( - source_hash, context=ctx + if ( + ctx.config.block_attachments_from_strangers.get() + and not self._is_contact(source_hash, context=ctx) ): for key in ( LXMF.FIELD_FILE_ATTACHMENTS, @@ -13847,7 +14058,9 @@ class ReticulumMeshChat: for entry in stream: if isinstance(entry, (list, tuple)) and len(entry) >= 3: entry_source = ( - entry[0].hex() if isinstance(entry[0], bytes) else entry[0] + entry[0].hex() + if isinstance(entry[0], bytes) + else entry[0] ) entry_timestamp = entry[1] entry_data = entry[2] @@ -13871,7 +14084,9 @@ class ReticulumMeshChat: background_colour = "#" + icon_appearance[2].hex() local_hash = ( - ctx.local_lxmf_destination.hexhash if ctx.local_lxmf_destination else None + ctx.local_lxmf_destination.hexhash + if ctx.local_lxmf_destination + else None ) source_hash = lxmf_message.source_hash.hex() @@ -13882,8 +14097,12 @@ class ReticulumMeshChat: pass else: local_icon_name = ctx.config.lxmf_user_icon_name.get() - local_icon_fg = ctx.config.lxmf_user_icon_foreground_colour.get() - local_icon_bg = ctx.config.lxmf_user_icon_background_colour.get() + local_icon_fg = ( + ctx.config.lxmf_user_icon_foreground_colour.get() + ) + local_icon_bg = ( + ctx.config.lxmf_user_icon_background_colour.get() + ) # if incoming icon matches our own, skip storing and clear any mistaken stored copy # for now, but this will need to be updated later if two users do have the same icon @@ -13994,7 +14213,9 @@ class ReticulumMeshChat: self.send_message( destination_hash=mapping["original_sender_hash"], content=lxmf_message.content, - title=lxmf_message.title if hasattr(lxmf_message, "title") else "", + title=lxmf_message.title + if hasattr(lxmf_message, "title") + else "", image_field=image_field, audio_field=audio_field, file_attachments_field=file_attachments_field, @@ -14013,7 +14234,10 @@ class ReticulumMeshChat: for rule in rules: # check source filter if set - if rule["source_filter_hash"] and rule["source_filter_hash"] != source_hash: + if ( + rule["source_filter_hash"] + and rule["source_filter_hash"] != source_hash + ): continue # find or create mapping for this (Source, Final Recipient) pair @@ -14031,7 +14255,9 @@ class ReticulumMeshChat: self.send_message( destination_hash=rule["forward_to_hash"], content=lxmf_message.content, - title=lxmf_message.title if hasattr(lxmf_message, "title") else "", + title=lxmf_message.title + if hasattr(lxmf_message, "title") + else "", sender_identity_hash=mapping["alias_hash"], image_field=image_field, audio_field=audio_field, @@ -14130,7 +14356,10 @@ class ReticulumMeshChat: # resend message source_hash = lxmf_message.source_hash.hex() router = ctx.message_router - if ctx.forwarding_manager and source_hash in ctx.forwarding_manager.forwarding_routers: + if ( + ctx.forwarding_manager + and source_hash in ctx.forwarding_manager.forwarding_routers + ): router = ctx.forwarding_manager.forwarding_routers[source_hash] router.handle_outbound(lxmf_message) @@ -14174,13 +14403,19 @@ class ReticulumMeshChat: deadline = time.time() + self._lxmf_path_wait_seconds() if not RNS.Transport.has_path(destination_hash_bytes): RNS.Transport.request_path(destination_hash_bytes) - while not RNS.Transport.has_path(destination_hash_bytes) and time.time() < deadline: + while ( + not RNS.Transport.has_path(destination_hash_bytes) + and time.time() < deadline + ): await asyncio.sleep(0.1) if RNS.Transport.has_path(destination_hash_bytes): return True RNS.Transport.request_path(destination_hash_bytes) deadline = time.time() + max(15.0, self._lxmf_path_wait_seconds() * 0.5) - while not RNS.Transport.has_path(destination_hash_bytes) and time.time() < deadline: + while ( + not RNS.Transport.has_path(destination_hash_bytes) + and time.time() < deadline + ): await asyncio.sleep(0.1) return RNS.Transport.has_path(destination_hash_bytes) @@ -14278,7 +14513,8 @@ class ReticulumMeshChat: if sender_identity_hash is not None: if ( ctx.forwarding_manager - and sender_identity_hash in ctx.forwarding_manager.forwarding_destinations + and sender_identity_hash + in ctx.forwarding_manager.forwarding_destinations ): source_destination = ctx.forwarding_manager.forwarding_destinations[ sender_identity_hash diff --git a/meshchatx/src/backend/async_utils.py b/meshchatx/src/backend/async_utils.py index 86364b3..23bb5bc 100644 --- a/meshchatx/src/backend/async_utils.py +++ b/meshchatx/src/backend/async_utils.py @@ -71,7 +71,10 @@ class AsyncUtils: ) with AsyncUtils._futures_lock: AsyncUtils._pending_futures.append(future) - if len(AsyncUtils._pending_futures) >= AsyncUtils._FUTURES_SWEEP_THRESHOLD: + if ( + len(AsyncUtils._pending_futures) + >= AsyncUtils._FUTURES_SWEEP_THRESHOLD + ): AsyncUtils._pending_futures = [ f for f in AsyncUtils._pending_futures if not f.done() ] diff --git a/meshchatx/src/backend/audio_codec.py b/meshchatx/src/backend/audio_codec.py index ec65a7a..4ed86bf 100644 --- a/meshchatx/src/backend/audio_codec.py +++ b/meshchatx/src/backend/audio_codec.py @@ -79,9 +79,13 @@ def _decode_with_wave(data: bytes): if sample_width == 2: samples = np.frombuffer(raw, dtype=np.int16).astype(np.float32) / 32768.0 elif sample_width == 1: - samples = (np.frombuffer(raw, dtype=np.uint8).astype(np.float32) - 128.0) / 128.0 + samples = ( + np.frombuffer(raw, dtype=np.uint8).astype(np.float32) - 128.0 + ) / 128.0 elif sample_width == 4: - samples = np.frombuffer(raw, dtype=np.int32).astype(np.float32) / 2147483648.0 + samples = ( + np.frombuffer(raw, dtype=np.int32).astype(np.float32) / 2147483648.0 + ) else: return None @@ -109,7 +113,9 @@ def _decode_with_miniaudio(data: bytes): return None if decoded.num_frames <= 0 or decoded.nchannels <= 0: return None - samples = np.frombuffer(decoded.samples, dtype=np.int16).astype(np.float32) / 32768.0 + samples = ( + np.frombuffer(decoded.samples, dtype=np.int16).astype(np.float32) / 32768.0 + ) return DecodedAudio( samples=samples.reshape(-1, decoded.nchannels), samplerate=decoded.sample_rate, diff --git a/meshchatx/src/backend/auto_propagation_manager.py b/meshchatx/src/backend/auto_propagation_manager.py index b6d0b76..d9b1c9a 100644 --- a/meshchatx/src/backend/auto_propagation_manager.py +++ b/meshchatx/src/backend/auto_propagation_manager.py @@ -134,7 +134,9 @@ class AutoPropagationManager: if not sorted_candidates: return - previous_hex = self.config.lxmf_preferred_propagation_node_destination_hash.get() + previous_hex = ( + self.config.lxmf_preferred_propagation_node_destination_hash.get() + ) ordered: list[tuple[int, str]] = [] seen_hex: set[str] = set() if previous_hex and previous_hex in best_by_hex: diff --git a/meshchatx/src/backend/bot_handler.py b/meshchatx/src/backend/bot_handler.py index 984cdfc..47bcf2a 100644 --- a/meshchatx/src/backend/bot_handler.py +++ b/meshchatx/src/backend/bot_handler.py @@ -147,10 +147,16 @@ class BotHandler: # Try running instance first instance = self.running_bots.get(bot_id, {}).get("instance") - if instance and getattr(instance, "bot", None) and getattr(instance.bot, "local", None): + if ( + instance + and getattr(instance, "bot", None) + and getattr(instance.bot, "local", None) + ): with contextlib.suppress(Exception): lh = instance.bot.local.hash - address_full = lh.hex() if isinstance(lh, (bytes, bytearray)) else None + address_full = ( + lh.hex() if isinstance(lh, (bytes, bytearray)) else None + ) if address_full: address_full = self._normalize_lxmf_hash_hex(address_full) if address_full: @@ -164,7 +170,9 @@ class BotHandler: destination = RNS.Destination(identity, "lxmf", "delivery") address_full = self._normalize_lxmf_hash_hex(destination.hash) if address_full: - address_pretty = RNS.prettyhexrep(bytes.fromhex(address_full)) + address_pretty = RNS.prettyhexrep( + bytes.fromhex(address_full) + ) if address_full is None: address_full = self._read_lxmf_address_sidecar(entry.get("storage_dir")) diff --git a/meshchatx/src/backend/bot_process.py b/meshchatx/src/backend/bot_process.py index 9205289..fd3fc65 100644 --- a/meshchatx/src/backend/bot_process.py +++ b/meshchatx/src/backend/bot_process.py @@ -60,7 +60,9 @@ def main(): else: config_path = os.path.join(os.path.abspath(args.storage), "config") os.makedirs(config_path, exist_ok=True) - reticulum_config_dir = os.path.abspath(os.path.expanduser(args.reticulum_config_dir)) + reticulum_config_dir = os.path.abspath( + os.path.expanduser(args.reticulum_config_dir) + ) os.makedirs(reticulum_config_dir, exist_ok=True) BotCls = TEMPLATE_MAP[args.template] diff --git a/meshchatx/src/backend/bot_templates.py b/meshchatx/src/backend/bot_templates.py index 7edac8d..be6d089 100644 --- a/meshchatx/src/backend/bot_templates.py +++ b/meshchatx/src/backend/bot_templates.py @@ -162,17 +162,23 @@ class NoteBotTemplate(StoppableBot): if not ctx.args: response = "Your Notes:\n" for i, note in enumerate(notes[-10:], 1): - tags = " ".join(f"#{tag}" for tag in note["tags"]) if note["tags"] else "" + tags = ( + " ".join(f"#{tag}" for tag in note["tags"]) + if note["tags"] + else "" + ) response += f"{i}. {note['text']} {tags}\n" if len(notes) > 10: - response += ( - f"\nShowing last 10 of {len(notes)} notes. Use /notes all to see all." - ) + response += f"\nShowing last 10 of {len(notes)} notes. Use /notes all to see all." ctx.reply(response) elif ctx.args[0] == "all": response = "All Your Notes:\n" for i, note in enumerate(notes, 1): - tags = " ".join(f"#{tag}" for tag in note["tags"]) if note["tags"] else "" + tags = ( + " ".join(f"#{tag}" for tag in note["tags"]) + if note["tags"] + else "" + ) response += f"{i}. {note['text']} {tags}\n" ctx.reply(response) diff --git a/meshchatx/src/backend/config_manager.py b/meshchatx/src/backend/config_manager.py index 211cbc2..9f213f5 100644 --- a/meshchatx/src/backend/config_manager.py +++ b/meshchatx/src/backend/config_manager.py @@ -66,10 +66,12 @@ class ConfigManager: "lxmf_preferred_propagation_node_auto_select", False, ) - self.lxmf_preferred_propagation_node_auto_sync_interval_seconds = self.IntConfig( - self, - "lxmf_preferred_propagation_node_auto_sync_interval_seconds", - 0, + self.lxmf_preferred_propagation_node_auto_sync_interval_seconds = ( + self.IntConfig( + self, + "lxmf_preferred_propagation_node_auto_sync_interval_seconds", + 0, + ) ) self.lxmf_preferred_propagation_node_last_synced_at = self.IntConfig( self, diff --git a/meshchatx/src/backend/database/__init__.py b/meshchatx/src/backend/database/__init__.py index afc8640..ea1d3e0 100644 --- a/meshchatx/src/backend/database/__init__.py +++ b/meshchatx/src/backend/database/__init__.py @@ -180,7 +180,9 @@ class Database: _log.warning("DB open health check: no result") else: first = integrity_rows[0] - val = next(iter(first.values())) if isinstance(first, dict) else first[0] + val = ( + next(iter(first.values())) if isinstance(first, dict) else first[0] + ) if val != "ok": issues.append(f"Database integrity check failed: {val!s}") _log.warning("DB open health check: %s", val) @@ -227,7 +229,9 @@ class Database: _log.warning("DB close health check: no result") else: first = integrity_rows[0] - val = next(iter(first.values())) if isinstance(first, dict) else first[0] + val = ( + next(iter(first.values())) if isinstance(first, dict) else first[0] + ) if val != "ok": issues.append(f"Database integrity check failed: {val!s}") _log.warning("DB close health check: integrity failed") @@ -265,7 +269,9 @@ class Database: page_size = self._get_pragma_value("page_size", 0) or 0 page_count = self._get_pragma_value("page_count", 0) or 0 freelist_pages = self._get_pragma_value("freelist_count", 0) or 0 - freelist_bytes = page_size * freelist_pages if page_size > 0 and freelist_pages > 0 else 0 + freelist_bytes = ( + page_size * freelist_pages if page_size > 0 and freelist_pages > 0 else 0 + ) if freelist_bytes > 0: free_bytes = freelist_bytes else: diff --git a/meshchatx/src/backend/database/gifs.py b/meshchatx/src/backend/database/gifs.py index e396819..4c91ae5 100644 --- a/meshchatx/src/backend/database/gifs.py +++ b/meshchatx/src/backend/database/gifs.py @@ -219,7 +219,10 @@ class UserGifsDAO: (identity_hash, ch), ) - if self.count_for_identity(identity_hash) >= gif_utils.MAX_GIFS_PER_IDENTITY: + if ( + self.count_for_identity(identity_hash) + >= gif_utils.MAX_GIFS_PER_IDENTITY + ): errors.append("gif_limit_reached") break diff --git a/meshchatx/src/backend/database/legacy_migrator.py b/meshchatx/src/backend/database/legacy_migrator.py index 3c9d3b2..bce3331 100644 --- a/meshchatx/src/backend/database/legacy_migrator.py +++ b/meshchatx/src/backend/database/legacy_migrator.py @@ -104,9 +104,7 @@ class LegacyMigrator: try: # Check if table exists in legacy DB # We use a f-string here for the alias and table name, which are controlled by us - check_query = ( - f"SELECT name FROM {alias}.sqlite_master WHERE type='table' AND name=?" - ) + check_query = f"SELECT name FROM {alias}.sqlite_master WHERE type='table' AND name=?" res = self.provider.fetchone(check_query, (table,)) if res: diff --git a/meshchatx/src/backend/database/misc.py b/meshchatx/src/backend/database/misc.py index 94dc94c..7b34335 100644 --- a/meshchatx/src/backend/database/misc.py +++ b/meshchatx/src/backend/database/misc.py @@ -177,7 +177,9 @@ class MiscDAO: params.append(destination_hash) if query: like_term = f"%{query}%" - sql += " AND (destination_hash LIKE ? OR page_path LIKE ? OR content LIKE ?)" + sql += ( + " AND (destination_hash LIKE ? OR page_path LIKE ? OR content LIKE ?)" + ) params.extend([like_term, like_term, like_term]) sql += " ORDER BY created_at DESC" diff --git a/meshchatx/src/backend/database/provider.py b/meshchatx/src/backend/database/provider.py index 30d021c..af40695 100644 --- a/meshchatx/src/backend/database/provider.py +++ b/meshchatx/src/backend/database/provider.py @@ -74,10 +74,13 @@ class DatabaseProvider: if isinstance(params, dict): params = { - k: (v.isoformat() if isinstance(v, datetime) else v) for k, v in params.items() + k: (v.isoformat() if isinstance(v, datetime) else v) + for k, v in params.items() } else: - params = tuple((p.isoformat() if isinstance(p, datetime) else p) for p in params) + params = tuple( + (p.isoformat() if isinstance(p, datetime) else p) for p in params + ) if params: cursor.execute(query, params) diff --git a/meshchatx/src/backend/database/ringtones.py b/meshchatx/src/backend/database/ringtones.py index 0395a19..aa540ae 100644 --- a/meshchatx/src/backend/database/ringtones.py +++ b/meshchatx/src/backend/database/ringtones.py @@ -29,7 +29,9 @@ class RingtoneDAO: display_name = filename # check if this is the first ringtone, if so make it primary - count = self.provider.fetchone("SELECT COUNT(*) as count FROM ringtones")["count"] + count = self.provider.fetchone("SELECT COUNT(*) as count FROM ringtones")[ + "count" + ] is_primary = 1 if count == 0 else 0 cursor = self.provider.execute( diff --git a/meshchatx/src/backend/database/sticker_packs.py b/meshchatx/src/backend/database/sticker_packs.py index 2084303..dedc445 100644 --- a/meshchatx/src/backend/database/sticker_packs.py +++ b/meshchatx/src/backend/database/sticker_packs.py @@ -81,7 +81,10 @@ class UserStickerPacksDAO: is_strict: bool = True, ) -> dict: """Create a new pack. Raises ``ValueError`` on quota or short_name clash.""" - if self.count_for_identity(identity_hash) >= sticker_utils.MAX_STICKER_PACKS_PER_IDENTITY: + if ( + self.count_for_identity(identity_hash) + >= sticker_utils.MAX_STICKER_PACKS_PER_IDENTITY + ): msg = "pack_limit_reached" raise ValueError(msg) sn = sticker_pack_utils.sanitize_pack_short_name(short_name) @@ -150,7 +153,11 @@ class UserStickerPacksDAO: if pack_type is not None else existing["pack_type"] ) - new_cover = existing["cover_sticker_id"] if cover_sticker_id is ... else cover_sticker_id + new_cover = ( + existing["cover_sticker_id"] + if cover_sticker_id is ... + else cover_sticker_id + ) cur = self.provider.execute( """ UPDATE user_sticker_packs diff --git a/meshchatx/src/backend/database/stickers.py b/meshchatx/src/backend/database/stickers.py index c5cdfb1..15c641a 100644 --- a/meshchatx/src/backend/database/stickers.py +++ b/meshchatx/src/backend/database/stickers.py @@ -187,7 +187,10 @@ class UserStickersDAO: metadata so the picker can render the sticker correctly without re-parsing. """ - if self.count_for_identity(identity_hash) >= sticker_utils.MAX_STICKERS_PER_IDENTITY: + if ( + self.count_for_identity(identity_hash) + >= sticker_utils.MAX_STICKERS_PER_IDENTITY + ): msg = "sticker_limit_reached" raise ValueError(msg) @@ -345,7 +348,10 @@ class UserStickersDAO: (identity_hash, ch), ) - if self.count_for_identity(identity_hash) >= sticker_utils.MAX_STICKERS_PER_IDENTITY: + if ( + self.count_for_identity(identity_hash) + >= sticker_utils.MAX_STICKERS_PER_IDENTITY + ): errors.append("sticker_limit_reached") break diff --git a/meshchatx/src/backend/docs_manager.py b/meshchatx/src/backend/docs_manager.py index 6fe9c9d..1b859a1 100644 --- a/meshchatx/src/backend/docs_manager.py +++ b/meshchatx/src/backend/docs_manager.py @@ -264,7 +264,9 @@ class DocsManager: def has_meshchatx_docs(self): return ( - any(f.endswith((".md", ".txt")) for f in os.listdir(self.meshchatx_docs_dir)) + any( + f.endswith((".md", ".txt")) for f in os.listdir(self.meshchatx_docs_dir) + ) if os.path.exists(self.meshchatx_docs_dir) else False ) diff --git a/meshchatx/src/backend/forwarding_manager.py b/meshchatx/src/backend/forwarding_manager.py index 963d06c..0577363 100644 --- a/meshchatx/src/backend/forwarding_manager.py +++ b/meshchatx/src/backend/forwarding_manager.py @@ -144,7 +144,10 @@ class ForwardingManager: for link in list(RNS.Transport.active_links): match = False if hasattr(link, "destination") and link.destination: - if hasattr(link.destination, "identity") and link.destination.identity: + if ( + hasattr(link.destination, "identity") + and link.destination.identity + ): if link.destination.identity.hash == ih: match = True if match: diff --git a/meshchatx/src/backend/identity_context.py b/meshchatx/src/backend/identity_context.py index 6228901..9e34224 100644 --- a/meshchatx/src/backend/identity_context.py +++ b/meshchatx/src/backend/identity_context.py @@ -158,7 +158,10 @@ class IdentityContext: self.config = ConfigManager(self.database) # Apply overrides from CLI/ENV if provided - if hasattr(self.app, "gitea_base_url_override") and self.app.gitea_base_url_override: + if ( + hasattr(self.app, "gitea_base_url_override") + and self.app.gitea_base_url_override + ): self.config.gitea_base_url.set(self.app.gitea_base_url_override) self.message_handler = MessageHandler(self.database) @@ -235,7 +238,9 @@ class IdentityContext: # Restore preferred propagation node on startup with contextlib.suppress(Exception): - preferred_node = self.config.lxmf_preferred_propagation_node_destination_hash.get() + preferred_node = ( + self.config.lxmf_preferred_propagation_node_destination_hash.get() + ) if preferred_node: self.app.set_active_propagation_node(preferred_node, context=self) @@ -284,7 +289,9 @@ class IdentityContext: storage_dir=self.storage_path, db=self.database, ) - self.telephone_manager.get_name_for_identity_hash = self.app.get_name_for_identity_hash + self.telephone_manager.get_name_for_identity_hash = ( + self.app.get_name_for_identity_hash + ) self.telephone_manager.on_initiation_status_callback = lambda status, target: ( self.app.on_telephone_initiation_status( status, @@ -312,7 +319,9 @@ class IdentityContext: telephone_manager=self.telephone_manager, storage_dir=self.storage_path, ) - self.voicemail_manager.get_name_for_identity_hash = self.app.get_name_for_identity_hash + self.voicemail_manager.get_name_for_identity_hash = ( + self.app.get_name_for_identity_hash + ) self.voicemail_manager.on_new_voicemail_callback = lambda vm: ( self.app.on_new_voicemail_received(vm, context=self) ) @@ -356,7 +365,9 @@ class IdentityContext: # start background thread for auto syncing propagation nodes thread = threading.Thread( target=asyncio.run, - args=(self.app.announce_sync_propagation_nodes(self.session_id, context=self),), + args=( + self.app.announce_sync_propagation_nodes(self.session_id, context=self), + ), ) thread.daemon = True thread.start() @@ -419,24 +430,28 @@ class IdentityContext: ), AnnounceHandler( "lxmf.propagation", - lambda aspect, dh, ai, ad, aph: self.app.on_lxmf_propagation_announce_received( - aspect, - dh, - ai, - ad, - aph, - context=self, + lambda aspect, dh, ai, ad, aph: ( + self.app.on_lxmf_propagation_announce_received( + aspect, + dh, + ai, + ad, + aph, + context=self, + ) ), ), AnnounceHandler( "nomadnetwork.node", - lambda aspect, dh, ai, ad, aph: self.app.on_nomadnet_node_announce_received( - aspect, - dh, - ai, - ad, - aph, - context=self, + lambda aspect, dh, ai, ad, aph: ( + self.app.on_nomadnet_node_announce_received( + aspect, + dh, + ai, + ad, + aph, + context=self, + ) ), ), ] diff --git a/meshchatx/src/backend/identity_manager.py b/meshchatx/src/backend/identity_manager.py index 617dac3..f3826a2 100644 --- a/meshchatx/src/backend/identity_manager.py +++ b/meshchatx/src/backend/identity_manager.py @@ -148,7 +148,8 @@ class IdentityManager: "lxmf_address": lxmf_address, "lxst_address": lxst_address, "is_current": ( - current_identity_hash is not None and identity_hash == current_identity_hash + current_identity_hash is not None + and identity_hash == current_identity_hash ), }, ) diff --git a/meshchatx/src/backend/integrity_manager.py b/meshchatx/src/backend/integrity_manager.py index b032d41..a9f4bee 100644 --- a/meshchatx/src/backend/integrity_manager.py +++ b/meshchatx/src/backend/integrity_manager.py @@ -47,7 +47,10 @@ class IntegrityManager: # to avoid accidentally ignoring important files with similar names. if "lxmf_router" in path_parts: # Added more volatile LXMF patterns - if any(part in ["announces", "storage", "identities", "tmp"] for part in path_parts): + if any( + part in ["announces", "storage", "identities", "tmp"] + for part in path_parts + ): return True # Specifically ignore stamp costs which are frequently updated @@ -154,7 +157,10 @@ class IntegrityManager: actual_entropy = self._calculate_entropy(self.database_path) saved_entropy = manifest_metadata.get(db_rel, {}).get("entropy") - if saved_entropy is not None and abs(actual_entropy - saved_entropy) > 1.0: + if ( + saved_entropy is not None + and abs(actual_entropy - saved_entropy) > 1.0 + ): issues.append( f"Database structural anomaly (Entropy Δ: {abs(actual_entropy - saved_entropy):.2f})", ) @@ -190,7 +196,9 @@ class IntegrityManager: ) actual_size = full_path.stat().st_size - is_critical = any(c in rel_path for c in ["identity", "config"]) + is_critical = any( + c in rel_path for c in ["identity", "config"] + ) if is_critical: issues.append( diff --git a/meshchatx/src/backend/interfaces/WebsocketServerInterface.py b/meshchatx/src/backend/interfaces/WebsocketServerInterface.py index f8a7184..2b7a548 100644 --- a/meshchatx/src/backend/interfaces/WebsocketServerInterface.py +++ b/meshchatx/src/backend/interfaces/WebsocketServerInterface.py @@ -18,7 +18,9 @@ class WebsocketServerInterface(Interface): RESTART_DELAY_SECONDS = 5 def __str__(self): - return f"WebsocketServerInterface[{self.name}/{self.listen_ip}:{self.listen_port}]" + return ( + f"WebsocketServerInterface[{self.name}/{self.listen_ip}:{self.listen_port}]" + ) def __init__(self, owner, configuration): super().__init__() diff --git a/meshchatx/src/backend/lxmf_utils.py b/meshchatx/src/backend/lxmf_utils.py index eac3860..291e2d0 100644 --- a/meshchatx/src/backend/lxmf_utils.py +++ b/meshchatx/src/backend/lxmf_utils.py @@ -70,13 +70,17 @@ def is_user_facing_lxmf_payload(fields, content, title) -> bool: return True image = fields.get("image") - if isinstance(image, dict) and (image.get("image_size") or image.get("image_bytes")): + if isinstance(image, dict) and ( + image.get("image_size") or image.get("image_bytes") + ): return True if image is None and fields.get(LXMF_IMAGE_FIELD) is not None: return True audio = fields.get("audio") - if isinstance(audio, dict) and (audio.get("audio_size") or audio.get("audio_bytes")): + if isinstance(audio, dict) and ( + audio.get("audio_size") or audio.get("audio_bytes") + ): return True if audio is None and fields.get(LXMF_AUDIO_FIELD) is not None: return True @@ -110,7 +114,10 @@ def convert_lxmf_message_to_dict( if field_type == LXMF.FIELD_FILE_ATTACHMENTS and isinstance(value, list): file_attachments = [] for file_attachment in value: - if not isinstance(file_attachment, (list, tuple)) or len(file_attachment) < 2: + if ( + not isinstance(file_attachment, (list, tuple)) + or len(file_attachment) < 2 + ): continue file_name = file_attachment[0] file_data = file_attachment[1] @@ -133,7 +140,11 @@ def convert_lxmf_message_to_dict( fields["file_attachments"] = file_attachments # handle image field - if field_type == LXMF.FIELD_IMAGE and isinstance(value, (list, tuple)) and len(value) >= 2: + if ( + field_type == LXMF.FIELD_IMAGE + and isinstance(value, (list, tuple)) + and len(value) >= 2 + ): image_type = value[0] image_data = value[1] if isinstance(image_data, (bytes, bytearray)): @@ -148,7 +159,11 @@ def convert_lxmf_message_to_dict( } # handle audio field - if field_type == LXMF.FIELD_AUDIO and isinstance(value, (list, tuple)) and len(value) >= 2: + if ( + field_type == LXMF.FIELD_AUDIO + and isinstance(value, (list, tuple)) + and len(value) >= 2 + ): audio_mode = value[0] audio_data = value[1] if isinstance(audio_data, (bytes, bytearray)): @@ -196,7 +211,9 @@ def convert_lxmf_message_to_dict( fields["reply_to"] = value.hex() if isinstance(value, bytes) else value if field_type == 0x31: fields["reply_quoted_content"] = ( - value.decode("utf-8", errors="replace") if isinstance(value, bytes) else value + value.decode("utf-8", errors="replace") + if isinstance(value, bytes) + else value ) if field_type == LXMF_APP_EXTENSIONS_FIELD and isinstance(value, dict): @@ -231,7 +248,11 @@ def convert_lxmf_message_to_dict( val = message_fields[0x30] reply_to_hash = val.hex() if isinstance(val, bytes) else val - content = lxmf_message.content.decode("utf-8", errors="replace") if lxmf_message.content else "" + content = ( + lxmf_message.content.decode("utf-8", errors="replace") + if lxmf_message.content + else "" + ) # auto-detect reply from content if not present if not reply_to_hash and content and isinstance(content, str): @@ -255,7 +276,9 @@ def convert_lxmf_message_to_dict( "next_delivery_attempt", None, ), # attribute may not exist yet - "title": lxmf_message.title.decode("utf-8", errors="replace") if lxmf_message.title else "", + "title": lxmf_message.title.decode("utf-8", errors="replace") + if lxmf_message.title + else "", "content": content, "fields": fields, "timestamp": lxmf_message.timestamp, diff --git a/meshchatx/src/backend/map_manager.py b/meshchatx/src/backend/map_manager.py index 671eab6..65dfd81 100644 --- a/meshchatx/src/backend/map_manager.py +++ b/meshchatx/src/backend/map_manager.py @@ -315,7 +315,9 @@ class MapManager: ) return None - tasks = [asyncio.create_task(download_tile(tile)) for tile in tiles_to_download] + tasks = [ + asyncio.create_task(download_tile(tile)) for tile in tiles_to_download + ] for coro in asyncio.as_completed(tasks): if export_id in self._export_cancelled: @@ -337,7 +339,9 @@ class MapManager: (current_count / total_tiles) * 100, ) - if len(batch_data) >= batch_size or (current_count == total_tiles and batch_data): + if len(batch_data) >= batch_size or ( + current_count == total_tiles and batch_data + ): try: cursor.executemany( "INSERT INTO tiles VALUES (?, ?, ?, ?)", @@ -354,7 +358,9 @@ class MapManager: n = 2.0**zoom x = int((lon + 180.0) / 360.0 * n) y = int( - (1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n, + (1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) + / 2.0 + * n, ) return x, y diff --git a/meshchatx/src/backend/meshchat_utils.py b/meshchatx/src/backend/meshchat_utils.py index bfeda50..9f1e232 100644 --- a/meshchatx/src/backend/meshchat_utils.py +++ b/meshchatx/src/backend/meshchat_utils.py @@ -136,9 +136,9 @@ def parse_lxmf_display_name( # Try manual parsing first to avoid LXMF library call. if len(app_data_bytes) > 0: - if (app_data_bytes[0] >= 0x90 and app_data_bytes[0] <= 0x9F) or app_data_bytes[ - 0 - ] == 0xDC: + if ( + app_data_bytes[0] >= 0x90 and app_data_bytes[0] <= 0x9F + ) or app_data_bytes[0] == 0xDC: with contextlib.suppress(Exception): peer_data = msgpack.unpackb(app_data_bytes) if isinstance(peer_data, list) and len(peer_data) >= 1: diff --git a/meshchatx/src/backend/nomadnet_downloader.py b/meshchatx/src/backend/nomadnet_downloader.py index 737e589..3d75053 100644 --- a/meshchatx/src/backend/nomadnet_downloader.py +++ b/meshchatx/src/backend/nomadnet_downloader.py @@ -36,7 +36,11 @@ def get_cached_active_link(destination_hash: bytes): def sweep_stale_links(): """Evict all non-ACTIVE links from the global cache.""" with _nomadnet_links_lock: - stale = [k for k, v in nomadnet_cached_links.items() if v.status is not RNS.Link.ACTIVE] + stale = [ + k + for k, v in nomadnet_cached_links.items() + if v.status is not RNS.Link.ACTIVE + ] for k in stale: del nomadnet_cached_links[k] @@ -181,7 +185,9 @@ class NomadnetDownloader: timeout_after_seconds = time.time() + link_establishment_timeout - while link.status is not RNS.Link.ACTIVE and time.time() < timeout_after_seconds: + while ( + link.status is not RNS.Link.ACTIVE and time.time() < timeout_after_seconds + ): if self.is_cancelled: return await asyncio.sleep(_POLL_INTERVAL_S) @@ -301,7 +307,11 @@ class NomadnetFileDownloader(NomadnetDownloader): self.on_file_download_success(file_name, file_data) return - if isinstance(response, list) and len(response) > 1 and isinstance(response[1], dict): + if ( + isinstance(response, list) + and len(response) > 1 + and isinstance(response[1], dict) + ): file_data: bytes = response[0] metadata: dict = response[1] diff --git a/meshchatx/src/backend/page_node.py b/meshchatx/src/backend/page_node.py index 55d436e..7545e4e 100644 --- a/meshchatx/src/backend/page_node.py +++ b/meshchatx/src/backend/page_node.py @@ -336,7 +336,8 @@ class PageNode: return sorted( f for f in os.listdir(self.pages_dir) - if os.path.isfile(os.path.join(self.pages_dir, f)) and is_allowed_page_filename(f) + if os.path.isfile(os.path.join(self.pages_dir, f)) + and is_allowed_page_filename(f) ) def get_page_content(self, name): diff --git a/meshchatx/src/backend/persistent_log_handler.py b/meshchatx/src/backend/persistent_log_handler.py index b95059e..5c86a60 100644 --- a/meshchatx/src/backend/persistent_log_handler.py +++ b/meshchatx/src/backend/persistent_log_handler.py @@ -64,7 +64,9 @@ class PersistentLogHandler(logging.Handler): self._error_events.append(now_mono) # Periodically flush to database if available - if self.database and (time.time() - self.last_flush_time > self.flush_interval): + if self.database and ( + time.time() - self.last_flush_time > self.flush_interval + ): self._flush_to_db() except Exception: @@ -226,7 +228,9 @@ class PersistentLogHandler(logging.Handler): if level: logs = [log for log in logs if log["level"] == level] if is_anomaly is not None: - logs = [log for log in logs if log["is_anomaly"] == (1 if is_anomaly else 0)] + logs = [ + log for log in logs if log["is_anomaly"] == (1 if is_anomaly else 0) + ] # Sort descending logs.sort(key=lambda x: x["timestamp"], reverse=True) diff --git a/meshchatx/src/backend/recovery/crash_recovery.py b/meshchatx/src/backend/recovery/crash_recovery.py index 625bbf0..74184e7 100644 --- a/meshchatx/src/backend/recovery/crash_recovery.py +++ b/meshchatx/src/backend/recovery/crash_recovery.py @@ -383,7 +383,9 @@ class CrashRecovery: "no_table_config": "no such table: config" in error_msg, "in_memory_db": diagnosis.get("db_type") == "memory", "corrupt_in_msg": "corrupt" in error_msg or "malformed" in error_msg, - "async_in_msg": any(x in error_msg for x in ["asyncio", "event loop", "runtimeerror"]), + "async_in_msg": any( + x in error_msg for x in ["asyncio", "event loop", "runtimeerror"] + ), "no_loop_in_msg": "no current event loop" in error_msg or "no running event loop" in error_msg, "low_mem": diagnosis.get("low_memory", False), @@ -392,7 +394,8 @@ class CrashRecovery: "lxmf_in_msg": "lxmf" in error_msg or "lxmr" in error_msg, "identity_in_msg": "identity" in error_msg or "private key" in error_msg, "no_interfaces": diagnosis.get("active_interfaces", 0) == 0, - "old_python": py_version.major < 3 or (py_version.major == 3 and py_version.minor < 10), + "old_python": py_version.major < 3 + or (py_version.major == 3 and py_version.minor < 10), "legacy_kernel": "linux" in platform.system().lower() and ( (_m := re.search(r"(\d+\.\d+)", platform.release())) is not None diff --git a/meshchatx/src/backend/rncp_handler.py b/meshchatx/src/backend/rncp_handler.py index 8120252..fb095a0 100644 --- a/meshchatx/src/backend/rncp_handler.py +++ b/meshchatx/src/backend/rncp_handler.py @@ -178,7 +178,9 @@ class RNCPHandler: if transfer_id in self.active_transfers: self.active_transfers[transfer_id]["status"] = "completed" - self.active_transfers[transfer_id]["saved_path"] = saved_filename + self.active_transfers[transfer_id]["saved_path"] = ( + saved_filename + ) self.active_transfers[transfer_id]["filename"] = filename self._emit_receive_event( { @@ -277,7 +279,9 @@ class RNCPHandler: RNS.Transport.request_path(destination_hash) timeout_after = time.time() + timeout - while not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after: + while ( + not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after + ): await asyncio.sleep(0.1) if not RNS.Transport.has_path(destination_hash): @@ -369,7 +373,9 @@ class RNCPHandler: RNS.Transport.request_path(destination_hash) timeout_after = time.time() + timeout - while not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after: + while ( + not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after + ): await asyncio.sleep(0.1) if not RNS.Transport.has_path(destination_hash): diff --git a/meshchatx/src/backend/rnpath_handler.py b/meshchatx/src/backend/rnpath_handler.py index 94fda11..5ce208e 100644 --- a/meshchatx/src/backend/rnpath_handler.py +++ b/meshchatx/src/backend/rnpath_handler.py @@ -71,10 +71,18 @@ class RNPathHandler: total = len(formatted_table) responsive_count = len( - [e for e in formatted_table if e["state"] == RNS.Transport.STATE_RESPONSIVE], + [ + e + for e in formatted_table + if e["state"] == RNS.Transport.STATE_RESPONSIVE + ], ) unresponsive_count = len( - [e for e in formatted_table if e["state"] == RNS.Transport.STATE_UNRESPONSIVE], + [ + e + for e in formatted_table + if e["state"] == RNS.Transport.STATE_UNRESPONSIVE + ], ) # Pagination diff --git a/meshchatx/src/backend/rnpath_trace_handler.py b/meshchatx/src/backend/rnpath_trace_handler.py index 9b3215c..44d6a01 100644 --- a/meshchatx/src/backend/rnpath_trace_handler.py +++ b/meshchatx/src/backend/rnpath_trace_handler.py @@ -51,7 +51,11 @@ class RNPathTraceHandler: local_hash = "unknown" if self.identity and hasattr(self.identity, "hash"): local_hash = self.identity.hash.hex() - elif self.reticulum and hasattr(self.reticulum, "identity") and self.reticulum.identity: + elif ( + self.reticulum + and hasattr(self.reticulum, "identity") + and self.reticulum.identity + ): local_hash = self.reticulum.identity.hash.hex() path.append({"type": "local", "hash": local_hash, "name": "Local Node"}) diff --git a/meshchatx/src/backend/rnprobe_handler.py b/meshchatx/src/backend/rnprobe_handler.py index 8944044..4f49892 100644 --- a/meshchatx/src/backend/rnprobe_handler.py +++ b/meshchatx/src/backend/rnprobe_handler.py @@ -34,9 +34,13 @@ class RNProbeHandler: RNS.Transport.request_path(destination_hash) timeout_after = time.time() + ( - timeout or self.DEFAULT_TIMEOUT + self.reticulum.get_first_hop_timeout(destination_hash) + timeout + or self.DEFAULT_TIMEOUT + + self.reticulum.get_first_hop_timeout(destination_hash) ) - while not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after: + while ( + not RNS.Transport.has_path(destination_hash) and time.time() < timeout_after + ): await asyncio.sleep(0.1) if not RNS.Transport.has_path(destination_hash): @@ -79,9 +83,12 @@ class RNProbeHandler: timeout_after = time.time() + ( timeout - or self.DEFAULT_TIMEOUT + self.reticulum.get_first_hop_timeout(destination_hash) + or self.DEFAULT_TIMEOUT + + self.reticulum.get_first_hop_timeout(destination_hash) ) - while receipt.status == RNS.PacketReceipt.SENT and time.time() < timeout_after: + while ( + receipt.status == RNS.PacketReceipt.SENT and time.time() < timeout_after + ): await asyncio.sleep(0.1) result: dict = { diff --git a/meshchatx/src/backend/rnstatus_handler.py b/meshchatx/src/backend/rnstatus_handler.py index fea9258..d962710 100644 --- a/meshchatx/src/backend/rnstatus_handler.py +++ b/meshchatx/src/backend/rnstatus_handler.py @@ -196,7 +196,11 @@ class RNStatusHandler: name = ifstat.get("name", "") if name.startswith( - ("LocalInterface[", "TCPInterface[Client", "BackboneInterface[Client on") + ( + "LocalInterface[", + "TCPInterface[Client", + "BackboneInterface[Client on", + ) ): continue diff --git a/meshchatx/src/backend/sticker_pack_utils.py b/meshchatx/src/backend/sticker_pack_utils.py index 3e984ea..31ddce7 100644 --- a/meshchatx/src/backend/sticker_pack_utils.py +++ b/meshchatx/src/backend/sticker_pack_utils.py @@ -124,7 +124,9 @@ def validate_pack_document(data: object) -> dict: out_stickers.append( { "name": item.get("name") if isinstance(item.get("name"), str) else None, - "emoji": item.get("emoji") if isinstance(item.get("emoji"), str) else None, + "emoji": item.get("emoji") + if isinstance(item.get("emoji"), str) + else None, "image_type": item.get("image_type"), "image_bytes_b64": b64.strip(), }, @@ -135,7 +137,9 @@ def validate_pack_document(data: object) -> dict: "short_name": sanitize_pack_short_name(pack_meta.get("short_name")), "description": sanitize_pack_description(pack_meta.get("description")), "pack_type": sanitize_pack_type(pack_meta.get("type")), - "author": pack_meta.get("author") if isinstance(pack_meta.get("author"), str) else None, + "author": pack_meta.get("author") + if isinstance(pack_meta.get("author"), str) + else None, "is_strict": bool(pack_meta.get("is_strict", True)), }, "stickers": out_stickers, diff --git a/meshchatx/src/backend/sticker_utils.py b/meshchatx/src/backend/sticker_utils.py index baae94c..4d6c2e5 100644 --- a/meshchatx/src/backend/sticker_utils.py +++ b/meshchatx/src/backend/sticker_utils.py @@ -270,7 +270,9 @@ def parse_tgs(data: bytes) -> dict: } -def _ebml_read_vint(buf: bytes, pos: int, *, mask_marker: bool = True) -> tuple[int, int] | None: +def _ebml_read_vint( + buf: bytes, pos: int, *, mask_marker: bool = True +) -> tuple[int, int] | None: """Read an EBML variable-length integer at ``pos``; returns ``(value, next_pos)``.""" if pos >= len(buf): return None @@ -362,11 +364,15 @@ def parse_webm(data: bytes) -> dict: if f_id == 0x83: track_type = _ebml_read_uint(raw, fb, fe) elif f_id == 0x86: - track_codec = raw[fb:fe].decode("ascii", errors="replace") + track_codec = raw[fb:fe].decode( + "ascii", errors="replace" + ) elif f_id == 0x23E383: track_def_dur = _ebml_read_uint(raw, fb, fe) elif f_id == 0xE0: - for v_id, vb, ve in _ebml_iter_elements(raw, fb, fe): + for v_id, vb, ve in _ebml_iter_elements( + raw, fb, fe + ): if v_id == 0xB0: t_w = _ebml_read_uint(raw, vb, ve) elif v_id == 0xBA: @@ -382,7 +388,9 @@ def parse_webm(data: bytes) -> dict: elif seg_id == 0x1549A966: for inf_id, ib, ie in _ebml_iter_elements(raw, sb, se): if inf_id == 0x2AD7B1: - timecode_scale = _ebml_read_uint(raw, ib, ie) or timecode_scale + timecode_scale = ( + _ebml_read_uint(raw, ib, ie) or timecode_scale + ) elif inf_id == 0x4489: d = _ebml_read_float(raw, ib, ie) if d is not None: diff --git a/meshchatx/src/backend/telephone_manager.py b/meshchatx/src/backend/telephone_manager.py index b8f33f3..379ff69 100644 --- a/meshchatx/src/backend/telephone_manager.py +++ b/meshchatx/src/backend/telephone_manager.py @@ -60,7 +60,9 @@ class TelephoneManager: self.storage_dir = storage_dir self.db = db self.get_name_for_identity_hash = None - self.recordings_dir = os.path.join(storage_dir, "recordings") if storage_dir else None + self.recordings_dir = ( + os.path.join(storage_dir, "recordings") if storage_dir else None + ) if self.recordings_dir: os.makedirs(self.recordings_dir, exist_ok=True) @@ -443,7 +445,8 @@ class TelephoneManager: # to ensure the UI has something to show (either active_call or initiation_status) for _ in range(40): # Max 4 seconds of defensive waiting if self.telephone and ( - self.telephone.active_call or self.telephone.call_status in [0, 1, 3, 6] + self.telephone.active_call + or self.telephone.call_status in [0, 1, 3, 6] ): break await asyncio.sleep(self._status_poll_interval_s) diff --git a/meshchatx/src/backend/translator_handler.py b/meshchatx/src/backend/translator_handler.py index 787beba..4fb5e1b 100644 --- a/meshchatx/src/backend/translator_handler.py +++ b/meshchatx/src/backend/translator_handler.py @@ -261,9 +261,7 @@ class TranslatorHandler: if detected_lang: source_lang = detected_lang else: - msg = ( - "Could not auto-detect language. Please select a source language manually." - ) + msg = "Could not auto-detect language. Please select a source language manually." raise ValueError(msg) else: msg = ( @@ -363,7 +361,11 @@ class TranslatorHandler: "source": "argos", } except subprocess.CalledProcessError as e: - error_msg = e.stderr.decode() if isinstance(e.stderr, bytes) else (e.stderr or str(e)) + error_msg = ( + e.stderr.decode() + if isinstance(e.stderr, bytes) + else (e.stderr or str(e)) + ) msg = f"Argos Translate CLI error: {error_msg}" raise RuntimeError(msg) from e except Exception as e: diff --git a/meshchatx/src/backend/web_audio_bridge.py b/meshchatx/src/backend/web_audio_bridge.py index c96b4fb..1fab4e2 100644 --- a/meshchatx/src/backend/web_audio_bridge.py +++ b/meshchatx/src/backend/web_audio_bridge.py @@ -48,7 +48,9 @@ class WebAudioSource(LocalSource): def push_pcm(self, pcm_bytes: bytes): try: - samples = np.frombuffer(pcm_bytes, dtype=np.int16).astype(np.float32) / 32768.0 + samples = ( + np.frombuffer(pcm_bytes, dtype=np.int16).astype(np.float32) / 32768.0 + ) if samples.size == 0: return samples = samples.reshape(-1, 1) diff --git a/scripts/argos_translate.py b/scripts/argos_translate.py index eb26c3d..964705c 100755 --- a/scripts/argos_translate.py +++ b/scripts/argos_translate.py @@ -93,13 +93,17 @@ def ensure_package_installed(from_code, to_code): if pkg_to_install: print_info(f"Downloading package: {pkg_to_install}") argostranslate.package.install_from_path(pkg_to_install.download()) - print_success(f"Successfully installed package: {from_code} -> {to_code}") + print_success( + f"Successfully installed package: {from_code} -> {to_code}" + ) # Refresh installed languages installed = argostranslate.translate.get_installed_languages() installed_dict = {lang.code: lang for lang in installed} else: - print_error(f"Could not find a translation package for {from_code} -> {to_code}") + print_error( + f"Could not find a translation package for {from_code} -> {to_code}" + ) sys.exit(1) except Exception as e: print_error(f"Failed to install language package: {e}") @@ -174,7 +178,9 @@ def translate_dict(data, translate_func, target_name=None): translated_temp = translate_func(temp_text) return restore_vars_from_tokens(translated_temp, vars_found) except Exception as e: - print_warning(f"Failed to translate '{data}': {e}. Falling back to original.") + print_warning( + f"Failed to translate '{data}': {e}. Falling back to original." + ) return data else: return data @@ -184,7 +190,9 @@ def main(): parser = argparse.ArgumentParser( description="Translate JSON localization files using Argos Translate." ) - parser.add_argument("--from", dest="from_lang", help="Source language code (e.g. 'en')") + parser.add_argument( + "--from", dest="from_lang", help="Source language code (e.g. 'en')" + ) parser.add_argument("--to", dest="to_lang", help="Target language code (e.g. 'zh')") parser.add_argument("--input", dest="input_file", help="Path to input JSON file") parser.add_argument("--output", dest="output_file", help="Path to output JSON file") @@ -232,7 +240,9 @@ def main(): # Get Translator translate_func = get_translation_func(from_lang, to_lang) - print_info("Starting translation. This may take a moment depending on the file size...") + print_info( + "Starting translation. This may take a moment depending on the file size..." + ) translated_data = translate_dict(source_data, translate_func, target_name) # Ensure output directory exists diff --git a/scripts/build/fetch_reticulum_manual.py b/scripts/build/fetch_reticulum_manual.py index 6cb6e1c..696a626 100755 --- a/scripts/build/fetch_reticulum_manual.py +++ b/scripts/build/fetch_reticulum_manual.py @@ -240,7 +240,9 @@ def main(argv: list[str] | None = None) -> int: ) if _is_truthy(os.environ.get("MESHCHATX_SKIP_DOCS_FETCH")): - logging.info("MESHCHATX_SKIP_DOCS_FETCH is set; skipping Reticulum manual fetch.") + logging.info( + "MESHCHATX_SKIP_DOCS_FETCH is set; skipping Reticulum manual fetch." + ) return 0 sources: list[str] = [] diff --git a/scripts/ci/slsa-predicate.py b/scripts/ci/slsa-predicate.py index a269d32..f9205e5 100644 --- a/scripts/ci/slsa-predicate.py +++ b/scripts/ci/slsa-predicate.py @@ -14,7 +14,9 @@ def _source_uri() -> str: server = ( os.environ.get("GITHUB_SERVER_URL") or os.environ.get("GITEA_SERVER_URL") or "" ).rstrip("/") - repo = os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or "" + repo = ( + os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or "" + ) if not server or not repo: return "" if server.startswith(("https://", "http://")): @@ -29,7 +31,9 @@ def _build_type() -> str: server = ( os.environ.get("GITHUB_SERVER_URL") or os.environ.get("GITEA_SERVER_URL") or "" ).rstrip("/") - repo = os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or "" + repo = ( + os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or "" + ) if server and repo: return f"{server}/{repo}/.gitea/workflows/build.yml" return "https://slsa.dev/provenance/v1" diff --git a/tests/backend/benchmarking_utils.py b/tests/backend/benchmarking_utils.py index 8eb46b4..e9114ec 100644 --- a/tests/backend/benchmarking_utils.py +++ b/tests/backend/benchmarking_utils.py @@ -21,9 +21,7 @@ class BenchmarkResult: self.memory_delta_mb = memory_delta_mb def __repr__(self): - return ( - f"" - ) + return f"" def benchmark(name=None, iterations=1): diff --git a/tests/backend/test_audio_codec.py b/tests/backend/test_audio_codec.py index 5a02d65..f7fa51a 100644 --- a/tests/backend/test_audio_codec.py +++ b/tests/backend/test_audio_codec.py @@ -38,7 +38,9 @@ def _build_wav_pcm16( wf.setframerate(samplerate) frames = bytearray() for i in range(n_samples): - sample = int(0.3 * 32767 * math.sin(2 * math.pi * frequency * (i / samplerate))) + sample = int( + 0.3 * 32767 * math.sin(2 * math.pi * frequency * (i / samplerate)) + ) for _ in range(channels): frames.extend(struct.pack("= 2 @@ -169,7 +177,9 @@ async def test_local_preferred_propagation_sync_completes_without_remote_lookup( response = await sync_handler(None) assert response.status == 200 - status_data = json.loads((await status_handler(None)).body)["propagation_node_status"] + status_data = json.loads((await status_handler(None)).body)[ + "propagation_node_status" + ] assert status_data["state"] == "complete" assert status_data["progress"] == 100.0 assert fake_router.request_messages_calls == 0 diff --git a/tests/backend/test_lxmf_sync.py b/tests/backend/test_lxmf_sync.py index 743a92e..3216656 100644 --- a/tests/backend/test_lxmf_sync.py +++ b/tests/backend/test_lxmf_sync.py @@ -78,7 +78,10 @@ async def test_lxmf_sync_endpoints(mock_app): # 1. Test status endpoint initially idle handler = None for route in mock_app.get_routes(): - if route.path == "/api/v1/lxmf/propagation-node/status" and route.method == "GET": + if ( + route.path == "/api/v1/lxmf/propagation-node/status" + and route.method == "GET" + ): handler = route.handler break @@ -104,7 +107,9 @@ async def test_lxmf_sync_endpoints(mock_app): mock_app.current_context.message_router.request_messages_from_propagation_node.assert_called_once() # 3. Test status change to complete - mock_app.current_context.message_router.propagation_transfer_state = LXMF.LXMRouter.PR_COMPLETE + mock_app.current_context.message_router.propagation_transfer_state = ( + LXMF.LXMRouter.PR_COMPLETE + ) response = await handler(None) data = json.loads(response.body) assert data["propagation_node_status"]["state"] == "complete" @@ -135,9 +140,7 @@ async def test_specific_node_hash_validation(mock_app): break # Ensure it's considered configured - mock_app.current_context.message_router.get_outbound_propagation_node.return_value = ( - expected_bytes - ) + mock_app.current_context.message_router.get_outbound_propagation_node.return_value = expected_bytes await sync_handler(None) mock_app.current_context.message_router.request_messages_from_propagation_node.assert_called_once() @@ -146,10 +149,14 @@ async def test_specific_node_hash_validation(mock_app): @pytest.mark.asyncio async def test_status_includes_sync_storage_and_confirmation_metrics(mock_app): status_handler = next( - r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/status" + r.handler + for r in mock_app.get_routes() + if r.path == "/api/v1/lxmf/propagation-node/status" ) sync_handler = next( - r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/sync" + r.handler + for r in mock_app.get_routes() + if r.path == "/api/v1/lxmf/propagation-node/sync" ) mock_app.current_context.message_router.get_outbound_propagation_node.return_value = b"somehash" @@ -180,7 +187,9 @@ async def test_status_includes_sync_storage_and_confirmation_metrics(mock_app): @pytest.mark.asyncio async def test_status_metrics_default_to_zero_before_any_sync(mock_app): status_handler = next( - r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/status" + r.handler + for r in mock_app.get_routes() + if r.path == "/api/v1/lxmf/propagation-node/status" ) response = await status_handler(None) data = json.loads(response.body)["propagation_node_status"] @@ -194,10 +203,14 @@ async def test_status_metrics_default_to_zero_before_any_sync(mock_app): @pytest.mark.asyncio async def test_status_hidden_metric_is_clamped_to_zero(mock_app): status_handler = next( - r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/status" + r.handler + for r in mock_app.get_routes() + if r.path == "/api/v1/lxmf/propagation-node/status" ) sync_handler = next( - r.handler for r in mock_app.get_routes() if r.path == "/api/v1/lxmf/propagation-node/sync" + r.handler + for r in mock_app.get_routes() + if r.path == "/api/v1/lxmf/propagation-node/sync" ) mock_app.current_context.message_router.get_outbound_propagation_node.return_value = b"somehash" diff --git a/tests/backend/test_lxmf_utils_extended.py b/tests/backend/test_lxmf_utils_extended.py index fb4b1ad..ea2c168 100644 --- a/tests/backend/test_lxmf_utils_extended.py +++ b/tests/backend/test_lxmf_utils_extended.py @@ -76,9 +76,15 @@ def test_convert_lxmf_message_to_dict_with_attachments(): == base64.b64encode(b"content1").decode() ) assert result["fields"]["image"]["image_type"] == "png" - assert result["fields"]["image"]["image_bytes"] == base64.b64encode(b"image_data").decode() + assert ( + result["fields"]["image"]["image_bytes"] + == base64.b64encode(b"image_data").decode() + ) assert result["fields"]["audio"]["audio_mode"] == "voice" - assert result["fields"]["audio"]["audio_bytes"] == base64.b64encode(b"audio_data").decode() + assert ( + result["fields"]["audio"]["audio_bytes"] + == base64.b64encode(b"audio_data").decode() + ) def test_convert_lxmf_state_to_string(): diff --git a/tests/backend/test_markdown_renderer.py b/tests/backend/test_markdown_renderer.py index c608aae..8014d27 100644 --- a/tests/backend/test_markdown_renderer.py +++ b/tests/backend/test_markdown_renderer.py @@ -26,7 +26,8 @@ class TestMarkdownRenderer(unittest.TestCase): self.assertIn(" sticker_utils.MAX_ANIMATED_BYTES: raw = raw[: sticker_utils.MAX_ANIMATED_BYTES] try: @@ -116,7 +118,9 @@ def test_extract_metadata_fuzz_never_raises(image_type, raw): typ=st.one_of( st.none(), st.text(max_size=48), - st.sampled_from(["png", "jpeg", "jpg", "webp", "gif", "bmp", "tgs", "webm", "svg", ""]), + st.sampled_from( + ["png", "jpeg", "jpg", "webp", "gif", "bmp", "tgs", "webm", "svg", ""] + ), ), strict=st.booleans(), ) @@ -161,7 +165,9 @@ def test_mime_for_image_type_fuzz_never_raises(t): description=st.one_of(st.none(), st.text(max_size=400)), pack_type=st.one_of(st.none(), st.text(max_size=40)), ) -def test_sticker_pack_sanitizers_fuzz_never_raises(title, short_name, description, pack_type): +def test_sticker_pack_sanitizers_fuzz_never_raises( + title, short_name, description, pack_type +): sticker_pack_utils.sanitize_pack_title(title) sticker_pack_utils.sanitize_pack_short_name(short_name) sticker_pack_utils.sanitize_pack_description(description) diff --git a/tests/backend/test_meshchat_coverage.py b/tests/backend/test_meshchat_coverage.py index c747537..31bc97d 100644 --- a/tests/backend/test_meshchat_coverage.py +++ b/tests/backend/test_meshchat_coverage.py @@ -452,7 +452,9 @@ def _build_wav_pcm16(samplerate=48000, duration_seconds=0.5, frequency=440.0): wf.setframerate(samplerate) frames = bytearray() for i in range(n_samples): - sample = int(0.3 * 32767 * math.sin(2 * math.pi * frequency * (i / samplerate))) + sample = int( + 0.3 * 32767 * math.sin(2 * math.pi * frequency * (i / samplerate)) + ) frames.extend(struct.pack(" bytes: +def _build_tgs( + width: int = 512, height: int = 512, fps: float = 30.0, frames: int = 60 +) -> bytes: import gzip import json @@ -234,7 +247,9 @@ def test_parse_tgs_invalid_metadata(): import gzip import json - raw = gzip.compress(json.dumps({"w": 0, "h": 0, "fr": 0, "ip": 0, "op": 0}).encode()) + raw = gzip.compress( + json.dumps({"w": 0, "h": 0, "fr": 0, "ip": 0, "op": 0}).encode() + ) with pytest.raises(ValueError, match="invalid_tgs_metadata"): sticker_utils.parse_tgs(raw) @@ -290,7 +305,11 @@ def test_validate_strict_webm_too_large(): def test_detect_image_dimensions_png(): - raw = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR" + struct_pack(512, 512) + b"\x08\x06\x00\x00\x00" + raw = ( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR" + + struct_pack(512, 512) + + b"\x08\x06\x00\x00\x00" + ) assert sticker_utils.detect_image_dimensions("png", raw) == (512, 512) @@ -329,7 +348,11 @@ def test_extract_metadata_tgs(): def test_extract_metadata_static_png(): - raw = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR" + struct_pack(512, 256) + b"\x08\x06\x00\x00\x00" + raw = ( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR" + + struct_pack(512, 256) + + b"\x08\x06\x00\x00\x00" + ) meta = sticker_utils.extract_metadata("png", raw) assert meta["width"] == 512 assert meta["height"] == 256 diff --git a/tests/backend/test_telemetry_integration.py b/tests/backend/test_telemetry_integration.py index 0ef2fb8..85ea40e 100644 --- a/tests/backend/test_telemetry_integration.py +++ b/tests/backend/test_telemetry_integration.py @@ -36,8 +36,8 @@ def mock_app(): # Attach the actual method we want to test if possible, # but since it's an instance method, we might need to bind it. - app.process_incoming_telemetry = ReticulumMeshChat.process_incoming_telemetry.__get__( - app, ReticulumMeshChat + app.process_incoming_telemetry = ( + ReticulumMeshChat.process_incoming_telemetry.__get__(app, ReticulumMeshChat) ) return app diff --git a/tests/backend/test_telephone_api_json_contracts.py b/tests/backend/test_telephone_api_json_contracts.py index baebb4d..f95f3ed 100644 --- a/tests/backend/test_telephone_api_json_contracts.py +++ b/tests/backend/test_telephone_api_json_contracts.py @@ -73,14 +73,18 @@ class _Query: @pytest.mark.asyncio -async def test_api_v1_telephone_voicemail_status_json_contract(mock_rns_minimal, temp_dir): +async def test_api_v1_telephone_voicemail_status_json_contract( + mock_rns_minimal, temp_dir +): with patch("meshchatx.meshchat.generate_ssl_certificate"): app_instance = ReticulumMeshChat( identity=mock_rns_minimal, storage_dir=temp_dir, reticulum_config_dir=temp_dir, ) - handler = _find_handler(app_instance, "/api/v1/telephone/voicemail/status", "GET") + handler = _find_handler( + app_instance, "/api/v1/telephone/voicemail/status", "GET" + ) assert handler is not None request = MagicMock() response = await handler(request) @@ -106,7 +110,9 @@ async def test_api_v1_telephone_voicemails_json_contract(mock_rns_minimal, temp_ @pytest.mark.asyncio -async def test_api_v1_telephone_ringtones_list_json_contract(mock_rns_minimal, temp_dir): +async def test_api_v1_telephone_ringtones_list_json_contract( + mock_rns_minimal, temp_dir +): with patch("meshchatx.meshchat.generate_ssl_certificate"): app_instance = ReticulumMeshChat( identity=mock_rns_minimal, @@ -122,14 +128,18 @@ async def test_api_v1_telephone_ringtones_list_json_contract(mock_rns_minimal, t @pytest.mark.asyncio -async def test_api_v1_telephone_ringtones_status_json_contract(mock_rns_minimal, temp_dir): +async def test_api_v1_telephone_ringtones_status_json_contract( + mock_rns_minimal, temp_dir +): with patch("meshchatx.meshchat.generate_ssl_certificate"): app_instance = ReticulumMeshChat( identity=mock_rns_minimal, storage_dir=temp_dir, reticulum_config_dir=temp_dir, ) - handler = _find_handler(app_instance, "/api/v1/telephone/ringtones/status", "GET") + handler = _find_handler( + app_instance, "/api/v1/telephone/ringtones/status", "GET" + ) assert handler is not None request = MagicMock() request.query = _Query({}) @@ -156,7 +166,9 @@ async def test_api_v1_telephone_contacts_list_json_contract(mock_rns_minimal, te @pytest.mark.asyncio -async def test_api_v1_telephone_contacts_check_json_contract(mock_rns_minimal, temp_dir): +async def test_api_v1_telephone_contacts_check_json_contract( + mock_rns_minimal, temp_dir +): with patch("meshchatx.meshchat.generate_ssl_certificate"): app_instance = ReticulumMeshChat( identity=mock_rns_minimal, diff --git a/tests/backend/test_telephone_initiation.py b/tests/backend/test_telephone_initiation.py index f2d24a5..53688e9 100644 --- a/tests/backend/test_telephone_initiation.py +++ b/tests/backend/test_telephone_initiation.py @@ -21,7 +21,9 @@ def telephone_manager(): tm._path_retry_interval_s = 0.01 tm._status_poll_interval_s = 0.01 tm._status_events = [] - tm.on_initiation_status_callback = lambda status, _target: tm._status_events.append(status) + tm.on_initiation_status_callback = lambda status, _target: tm._status_events.append( + status + ) return tm @@ -48,7 +50,9 @@ async def test_initiate_retries_path_requests_during_lookup(telephone_manager): "meshchatx.src.backend.telephone_manager.RNS.Transport.has_path", side_effect=has_path, ), - patch("meshchatx.src.backend.telephone_manager.RNS.Transport.request_path") as request_path, + patch( + "meshchatx.src.backend.telephone_manager.RNS.Transport.request_path" + ) as request_path, ): await telephone_manager.initiate(destination_hash, timeout_seconds=1) @@ -79,7 +83,9 @@ async def test_initiate_cancels_quickly_while_finding_path_identity(telephone_ma side_effect=request_path_and_cancel, ), ): - task = asyncio.create_task(telephone_manager.initiate(destination_hash, timeout_seconds=5)) + task = asyncio.create_task( + telephone_manager.initiate(destination_hash, timeout_seconds=5) + ) result = await asyncio.wait_for(task, timeout=0.3) assert result is None @@ -105,7 +111,9 @@ async def test_initiate_cancels_quickly_while_dialling(telephone_manager): return_value=True, ), ): - task = asyncio.create_task(telephone_manager.initiate(destination_hash, timeout_seconds=5)) + task = asyncio.create_task( + telephone_manager.initiate(destination_hash, timeout_seconds=5) + ) for _ in range(200): if telephone_manager.initiation_status in ( "Establishing link...", @@ -170,7 +178,9 @@ async def test_cancel_after_path_found_before_dialling_stabilizes(telephone_mana return_value=True, ), ): - task = asyncio.create_task(telephone_manager.initiate(destination_hash, timeout_seconds=2)) + task = asyncio.create_task( + telephone_manager.initiate(destination_hash, timeout_seconds=2) + ) for _ in range(200): if telephone_manager.initiation_status == "Establishing link...": break @@ -275,7 +285,9 @@ async def test_call_thread_exception_surfaces_without_hanging(telephone_manager) "meshchatx.src.backend.telephone_manager.RNS.Transport.has_path", return_value=True, ), - patch("meshchatx.src.backend.telephone_manager.asyncio.sleep", side_effect=no_wait), + patch( + "meshchatx.src.backend.telephone_manager.asyncio.sleep", side_effect=no_wait + ), ): result = await asyncio.wait_for( telephone_manager.initiate(destination_hash, timeout_seconds=1), @@ -307,7 +319,9 @@ async def test_inconsistent_call_status_finishes_within_timeout(telephone_manage "meshchatx.src.backend.telephone_manager.RNS.Transport.has_path", return_value=True, ), - patch("meshchatx.src.backend.telephone_manager.asyncio.sleep", side_effect=no_wait), + patch( + "meshchatx.src.backend.telephone_manager.asyncio.sleep", side_effect=no_wait + ), ): result = await asyncio.wait_for( telephone_manager.initiate(destination_hash, timeout_seconds=0.2), @@ -360,8 +374,10 @@ async def test_lxst_busy_and_rejected_end_without_stuck_status(telephone_manager for terminal_state in (0, 1): telephone_manager._status_events.clear() telephone_manager.telephone.call_status = 3 - telephone_manager.telephone.call.side_effect = lambda _identity, state=terminal_state: ( - setattr(telephone_manager.telephone, "call_status", state) + telephone_manager.telephone.call.side_effect = ( + lambda _identity, state=terminal_state: setattr( + telephone_manager.telephone, "call_status", state + ) ) with ( diff --git a/tests/backend/test_telephone_manager_boost.py b/tests/backend/test_telephone_manager_boost.py index b873a6a..77732eb 100644 --- a/tests/backend/test_telephone_manager_boost.py +++ b/tests/backend/test_telephone_manager_boost.py @@ -42,12 +42,16 @@ def test_init_telephone(mock_tel_class, tel_manager): @patch("meshchatx.src.backend.telephone_manager.Telephone") -def test_init_telephone_applies_config_audio_profile(mock_tel_class, mock_identity, tmp_path): +def test_init_telephone_applies_config_audio_profile( + mock_tel_class, mock_identity, tmp_path +): storage_dir = tmp_path / "tel" storage_dir.mkdir() cfg = MagicMock() cfg.telephone_audio_profile_id.get.return_value = 96 - tm = TelephoneManager(mock_identity, config_manager=cfg, storage_dir=str(storage_dir)) + tm = TelephoneManager( + mock_identity, config_manager=cfg, storage_dir=str(storage_dir) + ) tm.init_telephone() mock_tel_class.return_value.switch_profile.assert_called_with(96) diff --git a/tests/backend/test_user_guidance_autointerface.py b/tests/backend/test_user_guidance_autointerface.py index 29e50b0..9a65036 100644 --- a/tests/backend/test_user_guidance_autointerface.py +++ b/tests/backend/test_user_guidance_autointerface.py @@ -20,7 +20,9 @@ def _make_app(config_interfaces): transport_enabled=lambda: True, ) app._get_interfaces_section = ReticulumMeshChat._get_interfaces_section.__get__(app) - app._detect_failed_autointerfaces = ReticulumMeshChat._detect_failed_autointerfaces.__get__(app) + app._detect_failed_autointerfaces = ( + ReticulumMeshChat._detect_failed_autointerfaces.__get__(app) + ) return app @@ -102,7 +104,9 @@ def test_guidance_message_emitted_for_failed_autointerface(): ) app.config = MagicMock() app.config.auto_announce_enabled.get.return_value = True - app.build_user_guidance_messages = ReticulumMeshChat.build_user_guidance_messages.__get__(app) + app.build_user_guidance_messages = ( + ReticulumMeshChat.build_user_guidance_messages.__get__(app) + ) with patch("meshchatx.meshchat.RNS.Transport") as transport: transport.interfaces = [] @@ -125,7 +129,9 @@ def test_guidance_message_absent_when_autointerface_running(): ) app.config = MagicMock() app.config.auto_announce_enabled.get.return_value = True - app.build_user_guidance_messages = ReticulumMeshChat.build_user_guidance_messages.__get__(app) + app.build_user_guidance_messages = ( + ReticulumMeshChat.build_user_guidance_messages.__get__(app) + ) with patch("meshchatx.meshchat.RNS.Transport") as transport: transport.interfaces = [_FakeAutoInterface()]