mmrelay 1.1.3__py3-none-any.whl → 1.2.0__py3-none-any.whl

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.

mmrelay/main.py CHANGED
@@ -5,16 +5,23 @@ It uses Meshtastic-python and Matrix nio client library to interface with the ra
5
5
 
6
6
  import asyncio
7
7
  import concurrent.futures
8
- import logging
9
8
  import signal
10
9
  import sys
11
10
 
12
- from nio import ReactionEvent, RoomMessageEmote, RoomMessageNotice, RoomMessageText
11
+ from nio import (
12
+ MegolmEvent,
13
+ ReactionEvent,
14
+ RoomMessageEmote,
15
+ RoomMessageNotice,
16
+ RoomMessageText,
17
+ )
13
18
  from nio.events.room_events import RoomMemberEvent
14
19
 
15
20
  # Import version from package
16
21
  # Import meshtastic_utils as a module to set event_loop
17
22
  from mmrelay import __version__, meshtastic_utils
23
+ from mmrelay.cli_utils import msg_suggest_check_config, msg_suggest_generate_config
24
+ from mmrelay.constants.app import APP_DISPLAY_NAME, WINDOWS_PLATFORM
18
25
  from mmrelay.db_utils import (
19
26
  initialize_database,
20
27
  update_longnames,
@@ -22,9 +29,16 @@ from mmrelay.db_utils import (
22
29
  wipe_message_map,
23
30
  )
24
31
  from mmrelay.log_utils import get_logger
25
- from mmrelay.matrix_utils import connect_matrix, join_matrix_room
32
+ from mmrelay.matrix_utils import (
33
+ connect_matrix,
34
+ join_matrix_room,
35
+ )
26
36
  from mmrelay.matrix_utils import logger as matrix_logger
27
- from mmrelay.matrix_utils import on_room_member, on_room_message
37
+ from mmrelay.matrix_utils import (
38
+ on_decryption_failure,
39
+ on_room_member,
40
+ on_room_message,
41
+ )
28
42
  from mmrelay.meshtastic_utils import connect_meshtastic
29
43
  from mmrelay.meshtastic_utils import logger as meshtastic_logger
30
44
  from mmrelay.message_queue import (
@@ -36,10 +50,7 @@ from mmrelay.message_queue import (
36
50
  from mmrelay.plugin_loader import load_plugins
37
51
 
38
52
  # Initialize logger
39
- logger = get_logger(name="M<>M Relay")
40
-
41
- # Set the logging level for 'nio' to ERROR to suppress warnings
42
- logging.getLogger("nio").setLevel(logging.ERROR)
53
+ logger = get_logger(name=APP_DISPLAY_NAME)
43
54
 
44
55
 
45
56
  # Flag to track if banner has been printed
@@ -47,19 +58,24 @@ _banner_printed = False
47
58
 
48
59
 
49
60
  def print_banner():
50
- """Print a simple startup message with version information."""
61
+ """
62
+ Log the MMRelay startup banner with version information once.
63
+
64
+ This records an informational message "Starting MMRelay version <version>" via the module logger
65
+ the first time it is called and sets a module-level flag to prevent subsequent prints.
66
+ """
51
67
  global _banner_printed
52
68
  # Only print the banner once
53
69
  if not _banner_printed:
54
- logger.info(f"Starting MMRelay v{__version__}")
70
+ logger.info(f"Starting MMRelay version {__version__}")
55
71
  _banner_printed = True
56
72
 
57
73
 
58
74
  async def main(config):
59
75
  """
60
- Runs the main asynchronous relay loop, managing the lifecycle and coordination between Meshtastic and Matrix clients.
76
+ Coordinates the main asynchronous relay loop between Meshtastic and Matrix clients.
61
77
 
62
- Initializes the database, loads plugins, starts the message queue, and establishes connections to both Meshtastic and Matrix. Joins configured Matrix rooms, registers event callbacks for message and membership events, and periodically updates node names from the Meshtastic network. Monitors connection health, manages the Matrix sync loop with reconnection and shutdown handling, and ensures graceful shutdown of all components, including optional message map wiping on startup and shutdown if configured.
78
+ Initializes the database, loads plugins, starts the message queue, and establishes connections to both Meshtastic and Matrix. Joins configured Matrix rooms, registers event callbacks for message and membership events, and periodically updates node names from the Meshtastic network. Monitors connection health, manages the Matrix sync loop with reconnection and shutdown handling, and ensures graceful shutdown of all components. Optionally wipes the message map on startup and shutdown if configured.
63
79
  """
64
80
  # Extract Matrix configuration
65
81
  from typing import List
@@ -108,6 +124,13 @@ async def main(config):
108
124
  # Connect to Matrix
109
125
  matrix_client = await connect_matrix(passed_config=config)
110
126
 
127
+ # Check if Matrix connection was successful
128
+ if matrix_client is None:
129
+ # The error is logged by connect_matrix, so we can just raise here.
130
+ raise ConnectionError(
131
+ "Failed to connect to Matrix. Cannot continue without Matrix client."
132
+ )
133
+
111
134
  # Join the rooms specified in the config.yaml
112
135
  for room in matrix_rooms:
113
136
  await join_matrix_room(matrix_client, room["id"])
@@ -115,12 +138,14 @@ async def main(config):
115
138
  # Register the message callback for Matrix
116
139
  matrix_logger.info("Listening for inbound Matrix messages...")
117
140
  matrix_client.add_event_callback(
118
- on_room_message, (RoomMessageText, RoomMessageNotice, RoomMessageEmote)
141
+ on_room_message,
142
+ (RoomMessageText, RoomMessageNotice, RoomMessageEmote, ReactionEvent),
119
143
  )
120
- # Add ReactionEvent callback so we can handle matrix reactions
121
- matrix_client.add_event_callback(on_room_message, ReactionEvent)
144
+ # Add E2EE callbacks - MegolmEvent only goes to decryption failure handler
145
+ # Successfully decrypted messages will be converted to RoomMessageText etc. by matrix-nio
146
+ matrix_client.add_event_callback(on_decryption_failure, (MegolmEvent,))
122
147
  # Add RoomMemberEvent callback to track room-specific display name changes
123
- matrix_client.add_event_callback(on_room_member, RoomMemberEvent)
148
+ matrix_client.add_event_callback(on_room_member, (RoomMemberEvent,))
124
149
 
125
150
  # Set up shutdown event
126
151
  shutdown_event = asyncio.Event()
@@ -133,7 +158,7 @@ async def main(config):
133
158
  loop = asyncio.get_running_loop()
134
159
 
135
160
  # Handle signals differently based on the platform
136
- if sys.platform != "win32":
161
+ if sys.platform != WINDOWS_PLATFORM:
137
162
  for sig in (signal.SIGINT, signal.SIGTERM):
138
163
  loop.add_signal_handler(sig, lambda: asyncio.create_task(shutdown()))
139
164
  else:
@@ -170,15 +195,33 @@ async def main(config):
170
195
  [sync_task, shutdown_task],
171
196
  return_when=asyncio.FIRST_COMPLETED,
172
197
  )
173
- if shutdown_event.is_set():
174
- matrix_logger.info("Shutdown event detected. Stopping sync loop...")
175
- sync_task.cancel()
198
+
199
+ # Cancel any pending tasks
200
+ for task in pending:
201
+ task.cancel()
176
202
  try:
177
- await sync_task
203
+ await task
178
204
  except asyncio.CancelledError:
179
205
  pass
206
+
207
+ if shutdown_event.is_set():
208
+ matrix_logger.info("Shutdown event detected. Stopping sync loop...")
180
209
  break
181
210
 
211
+ # Check if sync_task completed with an exception
212
+ if sync_task in done:
213
+ try:
214
+ # This will raise the exception if the task failed
215
+ sync_task.result()
216
+ # If we get here, sync completed normally (shouldn't happen with sync_forever)
217
+ matrix_logger.warning(
218
+ "Matrix sync_forever completed unexpectedly"
219
+ )
220
+ except Exception as e:
221
+ # Log the exception and continue to retry
222
+ matrix_logger.error(f"Matrix sync failed: {e}")
223
+ # The outer try/catch will handle the retry logic
224
+
182
225
  except Exception as e:
183
226
  if shutdown_event.is_set():
184
227
  break
@@ -245,13 +288,16 @@ async def main(config):
245
288
 
246
289
 
247
290
  def run_main(args):
248
- """Run the main functionality of the application.
291
+ """
292
+ Run the application's top-level startup sequence and invoke the main async runner.
293
+
294
+ Performs initial setup (prints banner, optionally sets a custom data directory, loads and applies configuration and logging overrides), validates that required configuration sections are present (required keys differ if credentials.json is present), then runs the main coroutine. Returns an exit code: 0 for successful run or user interrupt, 1 for configuration errors or unhandled exceptions.
249
295
 
250
- Args:
251
- args: The parsed command-line arguments
296
+ Parameters:
297
+ args: Parsed command-line arguments (may be None). Recognized options used here include `data_dir` and `log_level`.
252
298
 
253
299
  Returns:
254
- int: Exit code (0 for success, non-zero for failure)
300
+ int: Exit code (0 on success or user-initiated interrupt, 1 on failure such as invalid config or runtime error).
255
301
  """
256
302
  # Print the banner at startup
257
303
  print_banner()
@@ -316,7 +362,17 @@ def run_main(args):
316
362
  config_rich_logger.info(f"Log file location: {log_file_path}")
317
363
 
318
364
  # Check if config exists and has the required keys
319
- required_keys = ["matrix", "meshtastic", "matrix_rooms"]
365
+ # Note: matrix section is optional if credentials.json exists
366
+ from mmrelay.config import load_credentials
367
+
368
+ credentials = load_credentials()
369
+
370
+ if credentials:
371
+ # With credentials.json, only meshtastic and matrix_rooms are required
372
+ required_keys = ["meshtastic", "matrix_rooms"]
373
+ else:
374
+ # Without credentials.json, all sections are required
375
+ required_keys = ["matrix", "meshtastic", "matrix_rooms"]
320
376
 
321
377
  # Check each key individually for better debugging
322
378
  for key in required_keys:
@@ -326,10 +382,21 @@ def run_main(args):
326
382
  if not config or not all(key in config for key in required_keys):
327
383
  # Exit with error if no config exists
328
384
  missing_keys = [key for key in required_keys if key not in config]
329
- logger.error(
330
- f"Configuration is missing required keys: {missing_keys}. "
331
- "Please create a valid config.yaml file or use --generate-config to create one."
332
- )
385
+ if credentials:
386
+ logger.error(f"Configuration is missing required keys: {missing_keys}")
387
+ logger.error("Matrix authentication will use credentials.json")
388
+ logger.error("Next steps:")
389
+ logger.error(
390
+ f" • Create a valid config.yaml file or {msg_suggest_generate_config()}"
391
+ )
392
+ logger.error(f" • {msg_suggest_check_config()}")
393
+ else:
394
+ logger.error(f"Configuration is missing required keys: {missing_keys}")
395
+ logger.error("Next steps:")
396
+ logger.error(
397
+ f" • Create a valid config.yaml file or {msg_suggest_generate_config()}"
398
+ )
399
+ logger.error(f" • {msg_suggest_check_config()}")
333
400
  return 1
334
401
 
335
402
  try:
@@ -339,7 +406,11 @@ def run_main(args):
339
406
  logger.info("Interrupted by user. Exiting.")
340
407
  return 0
341
408
  except Exception as e:
409
+ import traceback
410
+
342
411
  logger.error(f"Error running main functionality: {e}")
412
+ logger.error("Full traceback:")
413
+ logger.error(traceback.format_exc())
343
414
  return 1
344
415
 
345
416