mmrelay 1.2.2__tar.gz → 1.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mmrelay might be problematic. Click here for more details.

Files changed (112) hide show
  1. {mmrelay-1.2.2/src/mmrelay.egg-info → mmrelay-1.2.3}/PKG-INFO +1 -1
  2. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/__init__.py +1 -1
  3. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/cli.py +21 -20
  4. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/config.py +12 -12
  5. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/e2ee_utils.py +7 -2
  6. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/main.py +2 -2
  7. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/matrix_utils.py +58 -58
  8. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/meshtastic_utils.py +20 -21
  9. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/message_queue.py +11 -11
  10. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/mesh_relay_plugin.py +6 -5
  11. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/setup_utils.py +3 -3
  12. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/windows_utils.py +1 -1
  13. {mmrelay-1.2.2 → mmrelay-1.2.3/src/mmrelay.egg-info}/PKG-INFO +1 -1
  14. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_auth_flow_fixes.py +1 -2
  15. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_e2ee_unified.py +152 -10
  16. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_main_entry_point.py +1 -1
  17. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_matrix_utils.py +331 -79
  18. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_matrix_utils_error_handling.py +2 -2
  19. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_performance_stress.py +10 -12
  20. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_plugin_loader.py +2 -2
  21. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_setup_utils.py +2 -2
  22. {mmrelay-1.2.2 → mmrelay-1.2.3}/LICENSE +0 -0
  23. {mmrelay-1.2.2 → mmrelay-1.2.3}/MANIFEST.in +0 -0
  24. {mmrelay-1.2.2 → mmrelay-1.2.3}/README.md +0 -0
  25. {mmrelay-1.2.2 → mmrelay-1.2.3}/pyproject.toml +0 -0
  26. {mmrelay-1.2.2 → mmrelay-1.2.3}/requirements.txt +0 -0
  27. {mmrelay-1.2.2 → mmrelay-1.2.3}/setup.cfg +0 -0
  28. {mmrelay-1.2.2 → mmrelay-1.2.3}/setup.py +0 -0
  29. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/__main__.py +0 -0
  30. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/cli_utils.py +0 -0
  31. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/constants/__init__.py +0 -0
  32. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/constants/app.py +0 -0
  33. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/constants/config.py +0 -0
  34. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/constants/database.py +0 -0
  35. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/constants/formats.py +0 -0
  36. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/constants/messages.py +0 -0
  37. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/constants/network.py +0 -0
  38. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/constants/queue.py +0 -0
  39. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/db_utils.py +0 -0
  40. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/log_utils.py +0 -0
  41. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugin_loader.py +0 -0
  42. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/__init__.py +0 -0
  43. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/base_plugin.py +0 -0
  44. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/debug_plugin.py +0 -0
  45. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/drop_plugin.py +0 -0
  46. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/health_plugin.py +0 -0
  47. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/help_plugin.py +0 -0
  48. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/map_plugin.py +0 -0
  49. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/nodes_plugin.py +0 -0
  50. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/ping_plugin.py +0 -0
  51. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/telemetry_plugin.py +0 -0
  52. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/plugins/weather_plugin.py +0 -0
  53. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/runtime_utils.py +0 -0
  54. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/tools/__init__.py +0 -0
  55. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/tools/mmrelay.service +0 -0
  56. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/tools/sample-docker-compose-prebuilt.yaml +0 -0
  57. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/tools/sample-docker-compose.yaml +0 -0
  58. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/tools/sample.env +0 -0
  59. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay/tools/sample_config.yaml +0 -0
  60. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay.egg-info/SOURCES.txt +0 -0
  61. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay.egg-info/dependency_links.txt +0 -0
  62. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay.egg-info/entry_points.txt +0 -0
  63. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay.egg-info/requires.txt +0 -0
  64. {mmrelay-1.2.2 → mmrelay-1.2.3}/src/mmrelay.egg-info/top_level.txt +0 -0
  65. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_async_patterns.py +0 -0
  66. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_base_plugin.py +0 -0
  67. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_cli.py +0 -0
  68. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_cli_diagnose.py +0 -0
  69. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_cli_edge_cases.py +0 -0
  70. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_cli_utils.py +0 -0
  71. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_cli_windows_integration.py +0 -0
  72. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_config.py +0 -0
  73. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_config_checker.py +0 -0
  74. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_config_edge_cases.py +0 -0
  75. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_constants.py +0 -0
  76. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_core_utils_coverage.py +0 -0
  77. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_db_utils.py +0 -0
  78. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_db_utils_edge_cases.py +0 -0
  79. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_debug_plugin.py +0 -0
  80. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_drop_plugin.py +0 -0
  81. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_e2ee_encryption.py +0 -0
  82. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_e2ee_integration.py +0 -0
  83. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_e2ee_runner.py +0 -0
  84. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_error_boundaries.py +0 -0
  85. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_health_plugin.py +0 -0
  86. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_help_plugin.py +0 -0
  87. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_imports.py +0 -0
  88. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_integration_scenarios.py +0 -0
  89. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_log_utils.py +0 -0
  90. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_main.py +0 -0
  91. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_map_plugin.py +0 -0
  92. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_matrix_utils_edge_cases.py +0 -0
  93. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_mesh_relay_plugin.py +0 -0
  94. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_meshtastic_broadcast_enabled.py +0 -0
  95. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_meshtastic_utils.py +0 -0
  96. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_meshtastic_utils_edge_cases.py +0 -0
  97. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_message_queue.py +0 -0
  98. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_message_queue_edge_cases.py +0 -0
  99. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_network_reliability.py +0 -0
  100. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_nodes_plugin.py +0 -0
  101. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_patch_coverage_improvements.py +0 -0
  102. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_ping_plugin.py +0 -0
  103. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_plugin_loader_edge_cases.py +0 -0
  104. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_prefix_customization.py +0 -0
  105. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_runtime_no_errors.py +0 -0
  106. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_setup_utils_edge_cases.py +0 -0
  107. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_setup_utils_execstart_improvements.py +0 -0
  108. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_setup_utils_systemctl.py +0 -0
  109. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_telemetry_plugin.py +0 -0
  110. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_tools_init.py +0 -0
  111. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_weather_plugin.py +0 -0
  112. {mmrelay-1.2.2 → mmrelay-1.2.3}/tests/test_windows_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmrelay
3
- Version: 1.2.2
3
+ Version: 1.2.3
4
4
  Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
5
5
  Home-page: https://github.com/jeremiah-k/meshtastic-matrix-relay
6
6
  Author: Geoff Whittington, Jeremiah K., and contributors
@@ -2,4 +2,4 @@
2
2
  Meshtastic Matrix Relay - Bridge between Meshtastic mesh networks and Matrix chat rooms.
3
3
  """
4
4
 
5
- __version__ = "1.2.2"
5
+ __version__ = "1.2.3"
@@ -238,11 +238,11 @@ def print_version():
238
238
  def _validate_e2ee_dependencies():
239
239
  """
240
240
  Check whether end-to-end encryption (E2EE) is usable on the current platform.
241
-
241
+
242
242
  Returns:
243
243
  bool: True if the platform is supported and required E2EE libraries can be imported;
244
244
  False otherwise.
245
-
245
+
246
246
  Notes:
247
247
  - This function performs only local checks (platform and importability) and does not perform
248
248
  network I/O.
@@ -1499,13 +1499,13 @@ def handle_auth_logout(args):
1499
1499
  def handle_service_command(args):
1500
1500
  """
1501
1501
  Dispatch service-related subcommands.
1502
-
1502
+
1503
1503
  Currently supports the "install" subcommand which imports and runs mmrelay.setup_utils.install_service().
1504
1504
  Returns 0 on successful installation, 1 on failure or for unknown subcommands.
1505
-
1505
+
1506
1506
  Parameters:
1507
1507
  args: argparse.Namespace with a `service_command` attribute indicating the requested action.
1508
-
1508
+
1509
1509
  Returns:
1510
1510
  int: Exit code (0 on success, 1 on error).
1511
1511
  """
@@ -1526,13 +1526,13 @@ def _diagnose_config_paths(args):
1526
1526
  """
1527
1527
  Print a diagnostic summary of resolved configuration file search paths and their directory accessibility.
1528
1528
 
1529
- Uses get_config_paths(args) to compute the ordered list of candidate config file locations, then prints each path with a concise directory status icon:
1529
+ Computes the ordered list of candidate config file locations via get_config_paths(args) and prints each path with a short directory status icon:
1530
1530
  - ✅ directory exists and is writable
1531
1531
  - ⚠️ directory exists but is not writable
1532
1532
  - ❌ directory does not exist
1533
1533
 
1534
1534
  Parameters:
1535
- args (argparse.Namespace): Parsed CLI arguments used to determine the config search order.
1535
+ args (argparse.Namespace): CLI arguments used to determine the config search order (passed to get_config_paths).
1536
1536
  """
1537
1537
  print("1. Testing configuration paths...")
1538
1538
  from mmrelay.config import get_config_paths
@@ -1551,11 +1551,11 @@ def _diagnose_config_paths(args):
1551
1551
  def _diagnose_sample_config_accessibility():
1552
1552
  """
1553
1553
  Check availability of the bundled sample configuration and print a short diagnostic.
1554
-
1554
+
1555
1555
  Performs two non-destructive checks and prints human-readable results:
1556
1556
  1) Verifies whether the sample config file exists at the path returned by mmrelay.tools.get_sample_config_path().
1557
1557
  2) Attempts to read the embedded resource "sample_config.yaml" from the mmrelay.tools package via importlib.resources and reports success and the content length.
1558
-
1558
+
1559
1559
  Returns:
1560
1560
  bool: True if a filesystem sample config exists at the resolved path; False otherwise.
1561
1561
  """
@@ -1586,18 +1586,19 @@ def _diagnose_sample_config_accessibility():
1586
1586
 
1587
1587
  def _diagnose_platform_specific(args):
1588
1588
  """
1589
- Run platform-specific diagnostic checks.
1589
+ Run platform-specific diagnostic checks and report results.
1590
1590
 
1591
- On Windows, imports and runs Windows-specific requirement checks and a configuration-generation
1592
- test from mmrelay.windows_utils, printing per-component results and any warnings. On non-Windows
1593
- platforms this reports that platform-specific tests are not required.
1591
+ On Windows, attempts to import and run Windows-specific requirement checks and a
1592
+ configuration-generation test, printing per-component statuses and any warnings.
1593
+ On non-Windows platforms this reports that platform-specific tests are not
1594
+ required.
1594
1595
 
1595
1596
  Parameters:
1596
- args (argparse.Namespace): Parsed CLI arguments; passed through to the Windows
1597
- config-generation test when running on Windows.
1597
+ args (argparse.Namespace): CLI arguments passed through to the Windows
1598
+ configuration-generation test (only used when running on Windows).
1598
1599
 
1599
1600
  Returns:
1600
- bool: True if the platform is Windows (Windows checks were executed), False otherwise.
1601
+ bool: True if Windows checks were executed (running on Windows), False otherwise.
1601
1602
  """
1602
1603
  print("3. Platform-specific diagnostics...")
1603
1604
  import sys
@@ -1704,7 +1705,7 @@ logging:
1704
1705
  def _diagnose_minimal_config_template():
1705
1706
  """
1706
1707
  Validate the built-in minimal YAML configuration template and print a concise pass/fail status.
1707
-
1708
+
1708
1709
  Attempts to parse the string returned by _get_minimal_config_template() using yaml.safe_load. Prints a single-line result showing a ✅ with the template character length when the template is valid YAML, or a ❌ with the YAML parsing error when invalid. This is a non-destructive diagnostic helper that prints output and does not return a value.
1709
1710
  """
1710
1711
  print("4. Testing minimal config template fallback...")
@@ -1722,13 +1723,13 @@ def handle_config_diagnose(args):
1722
1723
  """
1723
1724
  Run non-destructive diagnostics for the MMRelay configuration subsystem and print a human-readable report.
1724
1725
 
1725
- Performs four checks: resolves candidate config paths and reports directory accessibility; verifies packaged sample config availability (filesystem and importlib.resources fallback); runs platform-specific diagnostics (Windows-focused where applicable); and validates the built-in minimal YAML template. Prints findings and suggested next steps to stdout/stderr.
1726
+ Performs four checks without modifying user files: (1) resolves and reports candidate configuration file paths and their directory accessibility, (2) verifies availability of the packaged sample configuration, (3) runs platform-specific diagnostics (Windows checks when applicable), and (4) validates the built-in minimal YAML configuration template. Results and actionable guidance are written to stdout/stderr.
1726
1727
 
1727
1728
  Parameters:
1728
- args (argparse.Namespace): Parsed CLI arguments used to resolve config search paths and to control platform-specific checks.
1729
+ args (argparse.Namespace): Parsed CLI arguments used to resolve configuration search paths and to control platform-specific checks.
1729
1730
 
1730
1731
  Returns:
1731
- int: Exit code (0 on success, 1 on failure).
1732
+ int: Exit code (0 on success, 1 on failure). On failure an error summary is printed to stderr.
1732
1733
  """
1733
1734
  print("MMRelay Configuration System Diagnostics")
1734
1735
  print("=" * 40)
@@ -196,14 +196,14 @@ def get_log_dir():
196
196
 
197
197
  def get_e2ee_store_dir():
198
198
  """
199
- Return the absolute path to the E2EE data store directory, creating it if missing.
199
+ Return the absolute path to the application's end-to-end-encryption (E2EE) data store directory, creating it if missing.
200
200
 
201
201
  On Linux and macOS this is "<base_dir>/store" where base_dir is returned by get_base_dir().
202
- On Windows this is "<custom_data_dir>/store" when module-level custom_data_dir is set; otherwise it uses
202
+ On Windows this is "<custom_data_dir>/store" when module-level custom_data_dir is set; otherwise it is
203
203
  platformdirs.user_data_dir(APP_NAME, APP_AUTHOR)/store.
204
204
 
205
205
  Returns:
206
- str: Absolute path to the ensured store directory. The directory is created if it does not exist.
206
+ str: Absolute path to the ensured store directory.
207
207
  """
208
208
  if sys.platform in ["linux", "darwin"]:
209
209
  # Use ~/.mmrelay/store/ for Linux and Mac
@@ -388,12 +388,12 @@ def is_e2ee_enabled(config):
388
388
  def check_e2ee_enabled_silently(args=None):
389
389
  """
390
390
  Check silently whether End-to-End Encryption (E2EE) is enabled in the first readable configuration file.
391
-
391
+
392
392
  Searches candidate configuration paths returned by get_config_paths(args) in priority order, loads the first readable YAML file, and returns True if that configuration enables E2EE (via is_e2ee_enabled). I/O and YAML parsing errors are ignored and the function continues to the next candidate. On Windows this always returns False.
393
-
393
+
394
394
  Parameters:
395
395
  args (optional): Parsed command-line arguments that can influence config search order.
396
-
396
+
397
397
  Returns:
398
398
  bool: True if E2EE is enabled in the first valid configuration file found; otherwise False.
399
399
  """
@@ -493,16 +493,16 @@ def load_credentials():
493
493
  def save_credentials(credentials):
494
494
  """
495
495
  Persist a JSON-serializable credentials mapping to <base_dir>/credentials.json.
496
-
496
+
497
497
  Writes the provided credentials (a JSON-serializable mapping) to the application's
498
498
  base configuration directory as credentials.json, creating the base directory if
499
499
  necessary. On Unix-like systems the file permissions are adjusted to be
500
500
  restrictive (0o600) when possible. I/O and permission errors are caught and
501
501
  logged; the function does not raise them.
502
-
502
+
503
503
  Parameters:
504
504
  credentials (dict): JSON-serializable mapping of credentials to persist.
505
-
505
+
506
506
  Returns:
507
507
  None
508
508
  """
@@ -822,18 +822,18 @@ def load_config(config_file=None, args=None):
822
822
  def validate_yaml_syntax(config_content, config_path):
823
823
  """
824
824
  Validate YAML text for syntax and common style issues, parse it with PyYAML, and return results.
825
-
825
+
826
826
  Performs lightweight line-based checks for frequent mistakes (using '=' instead of ':'
827
827
  for mappings and non-standard boolean words like 'yes'/'no' or 'on'/'off') and then
828
828
  attempts to parse the content with yaml.safe_load. If only style warnings are found,
829
829
  parsing is considered successful and warnings are returned; if parsing fails or true
830
830
  syntax errors are detected, a detailed error message is returned that references
831
831
  config_path to identify the source.
832
-
832
+
833
833
  Parameters:
834
834
  config_content (str): Raw YAML text to validate.
835
835
  config_path (str): Path or label used in error messages to identify the source of the content.
836
-
836
+
837
837
  Returns:
838
838
  tuple:
839
839
  is_valid (bool): True if YAML parsed successfully (style warnings allowed), False on syntax/parsing error.
@@ -80,8 +80,13 @@ def get_e2ee_status(
80
80
  importlib.import_module("olm")
81
81
 
82
82
  if os.getenv("MMRELAY_TESTING") != "1":
83
- importlib.import_module("nio.crypto").OlmDevice
84
- importlib.import_module("nio.store").SqliteStore
83
+ nio_crypto = importlib.import_module("nio.crypto")
84
+ if not hasattr(nio_crypto, "OlmDevice"):
85
+ raise ImportError("nio.crypto.OlmDevice is unavailable")
86
+
87
+ nio_store = importlib.import_module("nio.store")
88
+ if not hasattr(nio_store, "SqliteStore"):
89
+ raise ImportError("nio.store.SqliteStore is unavailable")
85
90
 
86
91
  status["dependencies_installed"] = True
87
92
  except ImportError:
@@ -154,9 +154,9 @@ async def main(config):
154
154
 
155
155
  async def shutdown():
156
156
  """
157
- Signal the application to shut down.
157
+ Signal the application to begin shutdown.
158
158
 
159
- Sets the Meshtastic shutdown flag and triggers the local shutdown event so tasks waiting on that event can begin shutdown/cleanup. This coroutine only signals shutdown; it does not close clients or perform cleanup itself.
159
+ Sets the Meshtastic shutdown flag and triggers the local shutdown event so any coroutines waiting on that event can start their cleanup. This coroutine only signals shutdown; it does not perform client shutdown or resource cleanup itself.
160
160
  """
161
161
  matrix_logger.info("Shutdown signal received. Closing down...")
162
162
  meshtastic_utils.shutting_down = True # Set the shutting_down flag
@@ -113,15 +113,15 @@ def _is_room_alias(value: Any) -> bool:
113
113
  def _iter_room_alias_entries(mapping):
114
114
  """
115
115
  Yield (alias_or_id, setter) pairs for entries in a Matrix room mapping.
116
-
116
+
117
117
  Each yielded tuple contains:
118
118
  - alias_or_id (str): the room alias or room ID found in the entry (may be an alias starting with '#' or a canonical room ID). If a dict entry has no `"id"` key, an empty string is yielded.
119
119
  - setter (callable): a function accepting a single argument `new_id` which updates the underlying mapping in-place to replace the alias with the resolved room ID.
120
-
120
+
121
121
  Supports two mapping shapes:
122
122
  - list: items may be strings (alias/ID) or dicts with an `"id"` key.
123
123
  - dict: values may be strings (alias/ID) or dicts with an `"id"` key.
124
-
124
+
125
125
  The setter updates the original collection (list element or dict value) so callers can resolve aliases and persist resolved IDs back into the provided mapping.
126
126
  """
127
127
 
@@ -150,13 +150,13 @@ def _iter_room_alias_entries(mapping):
150
150
  async def _resolve_aliases_in_mapping(mapping, resolver):
151
151
  """
152
152
  Resolve Matrix room alias entries found in a mapping (list or dict) by calling an async resolver and replacing aliases with resolved room IDs in-place.
153
-
153
+
154
154
  This function iterates entries produced by _iter_room_alias_entries(mapping). For each entry whose key/value looks like a Matrix room alias (a string starting with '#'), it awaits the provided resolver coroutine with the alias; if the resolver returns a truthy room ID, the corresponding entry in the original mapping is updated via the entry's setter. If mapping is not a list or dict, the function logs a warning and returns without modifying anything.
155
-
155
+
156
156
  Parameters:
157
157
  mapping (list|dict): A mapping of Matrix rooms where some entries may be aliases (e.g., "#room:example.org").
158
158
  resolver (Callable[[str], Awaitable[Optional[str]]]): Async callable that accepts an alias and returns a resolved room ID (or falsy on failure).
159
-
159
+
160
160
  Returns:
161
161
  None
162
162
  """
@@ -178,12 +178,12 @@ async def _resolve_aliases_in_mapping(mapping, resolver):
178
178
  def _update_room_id_in_mapping(mapping, alias, resolved_id) -> bool:
179
179
  """
180
180
  Replace a room alias with its resolved room ID in a mapping.
181
-
181
+
182
182
  Parameters:
183
183
  mapping (list|dict): A matrix_rooms mapping represented as a list of aliases or a dict of entries; only list and dict types are supported.
184
184
  alias (str): The room alias to replace (e.g., "#room:server").
185
185
  resolved_id (str): The canonical room ID to substitute for the alias (e.g., "!abcdef:server").
186
-
186
+
187
187
  Returns:
188
188
  bool: True if the alias was found and replaced with resolved_id; False if the mapping type is unsupported or the alias was not present.
189
189
  """
@@ -789,29 +789,18 @@ def bot_command(command, event):
789
789
 
790
790
  async def connect_matrix(passed_config=None):
791
791
  """
792
- Create and initialize a matrix-nio AsyncClient connected to the configured Matrix homeserver, optionally enabling End-to-End Encryption (E2EE).
793
-
794
- This routine selects credentials in the following order:
795
- 1. credentials.json (preferred; restores full session including device_id and E2EE store).
796
- 2. Automatic login using username/password from the provided config (saved to credentials.json on success).
797
- 3. Direct token-based values from the config matrix section.
798
-
799
- Behavior summary:
800
- - Validates presence of a top-level "matrix_rooms" configuration and raises ValueError if missing.
801
- - Builds an AsyncClient with a certifi-backed SSL context when available.
802
- - When E2EE is enabled in configuration and dependencies are present, prepares the encryption store, restores session state, and uploads device keys if needed.
803
- - Performs an initial full_state sync and resolves room aliases configured as aliases to room IDs.
804
- - Populates module-level globals used elsewhere (e.g., matrix_client, bot_user_name, matrix_homeserver, matrix_rooms, matrix_access_token, bot_user_id).
805
- - Returns the initialized AsyncClient instance ready for use.
806
-
792
+ Create and initialize a matrix-nio AsyncClient connected to the configured Matrix homeserver.
793
+
794
+ Attempts credential selection in this order: credentials.json (preferred), automatic login using username/password from config (saved to credentials.json), then direct token values from config. Optionally enables End-to-End Encryption (E2EE) when configured and dependencies are available. Performs an initial full-state sync, resolves configured room aliases to room IDs, sets module-level connection state (matrix_client, bot_user_name, matrix_homeserver, matrix_rooms, matrix_access_token, bot_user_id) and returns the ready AsyncClient.
795
+
807
796
  Parameters:
808
- passed_config (dict | None): Optional configuration to use for this connection attempt. If provided, it replaces the module-level config for this call.
809
-
797
+ passed_config (dict | None): Optional configuration to use for this connection attempt; if provided, it overrides the module-level config for this call.
798
+
810
799
  Returns:
811
- AsyncClient | None: An initialized matrix-nio AsyncClient on success, or None if connection cannot be established due to missing configuration/credentials.
812
-
800
+ AsyncClient | None: An initialized matrix-nio AsyncClient on success, or None if connection/credentials are unavailable.
801
+
813
802
  Raises:
814
- ValueError: If the top-level "matrix_rooms" configuration is missing from the (effective) config.
803
+ ValueError: If the required top-level "matrix_rooms" configuration is missing.
815
804
  ConnectionError: If the initial Matrix sync fails or times out.
816
805
  """
817
806
  global matrix_client, bot_user_name, matrix_homeserver, matrix_rooms, matrix_access_token, bot_user_id, config
@@ -1010,8 +999,15 @@ async def connect_matrix(passed_config=None):
1010
999
  # Also check for other required E2EE dependencies unless tests skip them
1011
1000
  if os.getenv("MMRELAY_TESTING") != "1":
1012
1001
  try:
1013
- from nio.crypto import OlmDevice as _OlmDevice
1014
- from nio.store import SqliteStore as _SqliteStore
1002
+ nio_crypto = importlib.import_module("nio.crypto")
1003
+ if not hasattr(nio_crypto, "OlmDevice"):
1004
+ raise ImportError("nio.crypto.OlmDevice is unavailable")
1005
+
1006
+ nio_store = importlib.import_module("nio.store")
1007
+ if not hasattr(nio_store, "SqliteStore"):
1008
+ raise ImportError(
1009
+ "nio.store.SqliteStore is unavailable"
1010
+ )
1015
1011
 
1016
1012
  logger.debug("All E2EE dependencies are available")
1017
1013
  except ImportError:
@@ -1034,6 +1030,10 @@ async def connect_matrix(passed_config=None):
1034
1030
  "Skipping additional E2EE dependency imports in test mode"
1035
1031
  )
1036
1032
 
1033
+ if e2ee_enabled:
1034
+ # Ensure nio still receives a store path even when dependency
1035
+ # checks are skipped (e.g. production runs without MMRELAY_TESTING);
1036
+ # without this the client will not load encryption state.
1037
1037
  # Get store path from config or use default
1038
1038
  if (
1039
1039
  "encryption" in config["matrix"]
@@ -1050,8 +1050,6 @@ async def connect_matrix(passed_config=None):
1050
1050
  config["matrix"]["e2ee"]["store_path"]
1051
1051
  )
1052
1052
  else:
1053
- from mmrelay.config import get_e2ee_store_dir
1054
-
1055
1053
  e2ee_store_path = get_e2ee_store_dir()
1056
1054
 
1057
1055
  # Create store directory if it doesn't exist
@@ -1215,7 +1213,7 @@ async def connect_matrix(passed_config=None):
1215
1213
  async def _resolve_alias(alias: str) -> Optional[str]:
1216
1214
  """
1217
1215
  Resolve a Matrix room alias to its canonical room ID.
1218
-
1216
+
1219
1217
  Attempts to resolve the provided room alias using the module's Matrix client. Returns the resolved room ID string on success; returns None if the alias cannot be resolved or if an error/timeout occurs (errors from the underlying nio client are caught and handled internally).
1220
1218
  """
1221
1219
  logger.debug(f"Resolving alias from config: {alias}")
@@ -1778,17 +1776,17 @@ async def login_matrix_bot(
1778
1776
  async def join_matrix_room(matrix_client, room_id_or_alias: str) -> None:
1779
1777
  """
1780
1778
  Join the bot to a Matrix room by ID or alias.
1781
-
1779
+
1782
1780
  Resolves a room alias (e.g. "#room:server") to its canonical room ID, updates the in-memory
1783
1781
  matrix_rooms mapping with the resolved ID (if available), and attempts to join the resolved
1784
1782
  room ID. No-op if the client is already joined to the room. Errors during alias resolution
1785
1783
  or join are caught and logged; the function does not raise exceptions.
1786
-
1784
+
1787
1785
  Parameters documented only where meaning is not obvious:
1788
1786
  room_id_or_alias (str): A Matrix room identifier, either a canonical room ID (e.g. "!abc:server")
1789
1787
  or a room alias (starts with '#'). When an alias is provided, it will be resolved and
1790
1788
  the resolved room ID will be used for joining and recorded in the module's matrix_rooms mapping.
1791
-
1789
+
1792
1790
  Returns:
1793
1791
  None
1794
1792
  """
@@ -2221,12 +2219,12 @@ def strip_quoted_lines(text: str) -> str:
2221
2219
  async def get_user_display_name(room, event):
2222
2220
  """
2223
2221
  Return the display name for the event sender, preferring a room-specific name.
2224
-
2222
+
2225
2223
  If the room provides a per-room display name for the sender, that name is returned.
2226
2224
  Otherwise the function performs an asynchronous lookup against the homeserver for the
2227
2225
  user's global display name and returns it if present. If no display name is available,
2228
2226
  the sender's Matrix ID (MXID) is returned.
2229
-
2227
+
2230
2228
  Returns:
2231
2229
  str: A human-readable display name or the sender's MXID.
2232
2230
  """
@@ -2320,15 +2318,17 @@ async def send_reply_to_meshtastic(
2320
2318
  reply_id=None,
2321
2319
  ):
2322
2320
  """
2323
- Enqueue a Matrix reply for transmission over Meshtastic, either as a structured reply or a regular broadcast.
2321
+ Enqueue a Matrix reply to be sent over Meshtastic, either as a structured reply targeting an existing Meshtastic message or as a regular broadcast.
2324
2322
 
2325
- If Meshtastic broadcasting is disabled the function returns without action. When storage_enabled is True the function will create a mapping entry (using event.event_id and room.room_id) and attach it to the queued message when possible. If reply_id is provided the message is sent as a structured reply targeting that Meshtastic message ID; otherwise it is sent as a regular text broadcast. Failures are logged; the function does not raise on enqueue errors.
2326
- Parameters that add non-obvious context:
2327
- room_config (dict): Room-specific configuration — must include "meshtastic_channel" (an integer channel index).
2328
- room: Matrix room object (room.room_id is used for mapping metadata).
2329
- event: Matrix event object (event.event_id is used for mapping metadata).
2330
- storage_enabled (bool): When True, attempt to create and attach a message-mapping record to the queued item.
2331
- reply_id (int | None): If provided, send as a structured reply targeting this Meshtastic message ID.
2323
+ If Meshtastic broadcasting is disabled this is a no-op. When storage_enabled is True the function will create a mapping entry (using event.event_id and room.room_id) and attach it to the queued message for later reply/reaction correlation. Failures are logged; the function does not raise exceptions.
2324
+
2325
+ Parameters:
2326
+ room_config (dict): Room-specific configuration must include "meshtastic_channel" (integer channel index).
2327
+ room: Matrix room object; room.room_id is used to build mapping metadata.
2328
+ event: Matrix event object; event.event_id is used to build mapping metadata.
2329
+ storage_enabled (bool): If True, create and attach a message-mapping record to the queued Meshtastic message.
2330
+ reply_id (int | None): If provided, send as a structured Meshtastic reply targeting this message ID; otherwise send a regular text broadcast.
2331
+ local_meshnet_name (str | None): Name of the local meshnet used in mapping metadata.
2332
2332
  """
2333
2333
  loop = asyncio.get_running_loop()
2334
2334
  meshtastic_interface = await loop.run_in_executor(None, connect_meshtastic)
@@ -2433,19 +2433,19 @@ async def handle_matrix_reply(
2433
2433
  meshnet_name=None,
2434
2434
  ):
2435
2435
  """
2436
- Relay a Matrix reply back to Meshtastic when the replied-to Matrix event maps to a Meshtastic message.
2436
+ Handle a Matrix reply by forwarding it to Meshtastic when the replied-to Matrix event maps to a Meshtastic message.
2437
2437
 
2438
- If the replied-to Matrix event has a stored Meshtastic mapping, format a Meshtastic reply preserving sender attribution and queue it as a reply that references the original Meshtastic message ID. If no mapping exists, do nothing and return False so normal Matrix processing can continue.
2438
+ If the Matrix event identified by reply_to_event_id has an associated Meshtastic mapping, this function formats a Meshtastic reply that preserves sender attribution and enqueues it referencing the original Meshtastic message ID. If no mapping exists, it returns False so normal Matrix processing can continue.
2439
2439
 
2440
2440
  Parameters:
2441
- reply_to_event_id (str): Matrix event ID being replied to; used to locate the corresponding Meshtastic mapping.
2442
- storage_enabled (bool): If True, message mappings may be created/updated when sending the reply.
2443
- local_meshnet_name (str): Local meshnet name used to determine cross-meshnet reply formatting.
2441
+ reply_to_event_id (str): Matrix event ID being replied to; used to locate the Meshtastic mapping.
2442
+ storage_enabled (bool): Whether message mappings/storage are enabled (affects created mapping behavior when sending).
2443
+ local_meshnet_name (str): Local meshnet name used to determine formatting when replying across meshnets.
2444
2444
  config (dict): Relay configuration passed to formatting routines.
2445
- mesh_text_override (str | None): Optional override text to send to Meshtastic instead of derived text.
2446
- longname (str | None): Sender long display name used for prefixing in the Meshtastic message.
2447
- shortname (str | None): Sender short display name used for prefixing in the Meshtastic message.
2448
- meshnet_name (str | None): Remote meshnet name of the original Matrix/meshtastic mapping (if any).
2445
+ mesh_text_override (str | None): Optional text to send instead of derived text.
2446
+ longname (str | None): Sender long display name used for prefixing.
2447
+ shortname (str | None): Sender short display name used for prefixing.
2448
+ meshnet_name (str | None): Remote meshnet name associated with the original mapping, if any.
2449
2449
 
2450
2450
  Returns:
2451
2451
  bool: True if a mapping was found and the reply was queued to Meshtastic; False if no mapping existed and nothing was sent.
@@ -2546,7 +2546,7 @@ async def on_room_message(
2546
2546
  ) -> None:
2547
2547
  """
2548
2548
  Handle an incoming Matrix room event and, when applicable, relay it to Meshtastic.
2549
-
2549
+
2550
2550
  Processes text, notice, emote, and reaction events (including replies and messages relayed from other meshnets) for configured rooms. Behavior highlights:
2551
2551
  - Ignores events from before the bot started and events sent by the bot itself.
2552
2552
  - Uses per-room configuration and global interaction settings to decide whether to process or ignore the event.
@@ -2554,12 +2554,12 @@ async def on_room_message(
2554
2554
  - Bridges Matrix replies to Meshtastic replies when a corresponding Meshtastic mapping is found and replies are enabled.
2555
2555
  - Relays regular Matrix messages to Meshtastic using configured prefix/truncation rules; handles special detection-sensor port forwarding.
2556
2556
  - Integrates with the plugin system; plugins may consume or modify messages. Messages identified as bot commands are not relayed to Meshtastic.
2557
-
2557
+
2558
2558
  Side effects:
2559
2559
  - May enqueue Meshtastic send operations (text or data) via the internal queue.
2560
2560
  - May read/write persistent message mappings to support reaction/reply bridging.
2561
2561
  - May call Matrix APIs (e.g., to fetch display names).
2562
-
2562
+
2563
2563
  Returns:
2564
2564
  - None
2565
2565
  """
@@ -44,8 +44,17 @@ from mmrelay.constants.network import (
44
44
  CONNECTION_TYPE_TCP,
45
45
  DEFAULT_BACKOFF_TIME,
46
46
  ERRNO_BAD_FILE_DESCRIPTOR,
47
- INFINITE_RETRIES,
47
+ INFINITE_RETRIES,
48
48
  )
49
+ from mmrelay.db_utils import (
50
+ get_longname,
51
+ get_message_map_by_meshtastic_id,
52
+ get_shortname,
53
+ save_longname,
54
+ save_shortname,
55
+ )
56
+ from mmrelay.log_utils import get_logger
57
+ from mmrelay.runtime_utils import is_running_as_service
49
58
 
50
59
  # Maximum number of timeout retries when retries are configured as infinite.
51
60
  MAX_TIMEOUT_RETRIES_INFINITE = 5
@@ -62,16 +71,6 @@ except ImportError:
62
71
  pass
63
72
 
64
73
 
65
- from mmrelay.db_utils import (
66
- get_longname,
67
- get_message_map_by_meshtastic_id,
68
- get_shortname,
69
- save_longname,
70
- save_shortname,
71
- )
72
- from mmrelay.log_utils import get_logger
73
- from mmrelay.runtime_utils import is_running_as_service
74
-
75
74
  # Global config variable that will be set from config.py
76
75
  config = None
77
76
 
@@ -153,15 +152,15 @@ def _submit_coro(coro, loop=None):
153
152
  def _resolve_plugin_timeout(cfg: dict | None, default: float = 5.0) -> float:
154
153
  """
155
154
  Resolve and return a positive plugin timeout value from the given configuration.
156
-
155
+
157
156
  Attempts to read meshtastic.plugin_timeout from cfg and convert it to a positive float.
158
157
  If the value is missing, non-numeric, or not greater than 0, the provided default is returned.
159
158
  Invalid or non-positive values will cause a warning to be logged.
160
-
159
+
161
160
  Parameters:
162
161
  cfg (dict | None): Configuration mapping that may contain a "meshtastic" section.
163
162
  default (float): Fallback timeout (seconds) used when cfg does not provide a valid value.
164
-
163
+
165
164
  Returns:
166
165
  float: A positive timeout in seconds.
167
166
  """
@@ -270,13 +269,13 @@ def serial_port_exists(port_name):
270
269
  def connect_meshtastic(passed_config=None, force_connect=False):
271
270
  """
272
271
  Establish and return a Meshtastic client connection (serial, BLE, or TCP), with configurable retries, exponential backoff, and single-time event subscription.
273
-
272
+
274
273
  Attempts to (re)connect using the module configuration and updates module-level state on success (meshtastic_client, matrix_rooms, and event subscriptions). Supports the legacy "network" alias for TCP, verifies serial port presence before connecting, and honors a retry limit (or infinite retries when unspecified). On successful connection the client's node info and firmware metadata are probed and message/connection-lost handlers are subscribed once for the process lifetime.
275
-
274
+
276
275
  Parameters:
277
276
  passed_config (dict, optional): If provided, replaces the module-level configuration (and may update matrix_rooms).
278
277
  force_connect (bool, optional): When True, forces creating a new connection even if one already exists.
279
-
278
+
280
279
  Returns:
281
280
  The connected Meshtastic client instance on success, or None if connection cannot be established or shutdown is in progress.
282
281
  """
@@ -464,7 +463,7 @@ def connect_meshtastic(passed_config=None, force_connect=False):
464
463
  subscribed_to_connection_lost = True
465
464
  logger.debug("Subscribed to meshtastic.connection.lost")
466
465
 
467
- except (ConnectionRefusedError, MemoryError) as e:
466
+ except (ConnectionRefusedError, MemoryError):
468
467
  # Handle critical errors that should not be retried
469
468
  logger.exception("Critical connection error")
470
469
  return None
@@ -1142,17 +1141,17 @@ def sendTextReply(
1142
1141
  ):
1143
1142
  """
1144
1143
  Send a Meshtastic text reply that references a previous Meshtastic message.
1145
-
1144
+
1146
1145
  Builds a Data payload containing `text` and `reply_id`, wraps it in a MeshPacket on `channelIndex`,
1147
1146
  and sends it using the provided Meshtastic interface.
1148
-
1147
+
1149
1148
  Parameters:
1150
1149
  text (str): UTF-8 text to send.
1151
1150
  reply_id (int): ID of the Meshtastic message being replied to.
1152
1151
  destinationId (int | str, optional): Recipient address or node ID (defaults to broadcast).
1153
1152
  wantAck (bool, optional): If True, request an acknowledgement for the packet.
1154
1153
  channelIndex (int, optional): Channel index to send the packet on.
1155
-
1154
+
1156
1155
  Returns:
1157
1156
  The result returned by the interface's _sendPacket call (typically the sent MeshPacket), or
1158
1157
  None if the interface is not available or sending fails.
@@ -473,11 +473,11 @@ class MessageQueue:
473
473
 
474
474
  def _should_send_message(self) -> bool:
475
475
  """
476
- Return True if it is currently safe to send a message via Meshtastic.
476
+ Return True when it is safe to send a Meshtastic message; otherwise False.
477
477
 
478
- Performs runtime checks: ensures the global reconnecting flag is not set, a Meshtastic client object is available, and — if the client exposes `is_connected` (callable or boolean) — that it reports connected. Returns False if any check fails.
478
+ Performs runtime checks: verifies the module-level `reconnecting` flag is False, a Meshtastic client object exists, and — if the client exposes `is_connected` (callable or boolean) — that it reports connected. If any check fails the method returns False.
479
479
 
480
- If importing the Meshtastic utilities raises ImportError, the method will asynchronously stop this MessageQueue and return False.
480
+ If importing the Meshtastic utilities raises ImportError, the queue will be stopped asynchronously and the method returns False.
481
481
  """
482
482
  # Import here to avoid circular imports
483
483
  try:
@@ -517,18 +517,18 @@ class MessageQueue:
517
517
 
518
518
  def _handle_message_mapping(self, result, mapping_info):
519
519
  """
520
- Persist a sent message mapping (mesh message id → Matrix event) and optionally prune old mappings.
520
+ Persist a sent mesh-to-Matrix message mapping and optionally prune old mappings.
521
521
 
522
- If mapping_info contains 'matrix_event_id', 'room_id', and 'text', stores a mapping using result.id as the mesh message id. If 'msgs_to_keep' is present and > 0 it prunes older mappings to retain that many entries; otherwise DEFAULT_MSGS_TO_KEEP is used.
522
+ If mapping_info contains 'matrix_event_id', 'room_id', and 'text', this stores a mapping using result.id as the mesh message id. If 'msgs_to_keep' is present and > 0 it prunes older mappings to retain that many entries; otherwise DEFAULT_MSGS_TO_KEEP is used.
523
523
 
524
524
  Parameters:
525
- result: Send function result object with an `id` attribute (the mesh message id).
525
+ result: An object returned by the send function with an `id` attribute (the mesh message id).
526
526
  mapping_info (dict): Mapping details. Relevant keys:
527
- - matrix_event_id (str)
528
- - room_id (str)
529
- - text (str)
530
- - meshnet (optional): passed to the store operation
531
- - msgs_to_keep (optional, int): number of mappings to retain for pruning
527
+ - matrix_event_id (str): Matrix event ID to map to.
528
+ - room_id (str): Matrix room ID where the event was sent.
529
+ - text (str): Message text to associate with the mapping.
530
+ - meshnet (optional): Mesh network identifier passed through to storage.
531
+ - msgs_to_keep (optional, int): Number of mappings to retain when pruning.
532
532
  """
533
533
  try:
534
534
  # Import here to avoid circular imports