mmrelay 1.1.4__py3-none-any.whl → 1.2.1__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,22 @@ 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
18
24
  from mmrelay.constants.app import APP_DISPLAY_NAME, WINDOWS_PLATFORM
19
25
  from mmrelay.db_utils import (
20
26
  initialize_database,
@@ -23,9 +29,16 @@ from mmrelay.db_utils import (
23
29
  wipe_message_map,
24
30
  )
25
31
  from mmrelay.log_utils import get_logger
26
- from mmrelay.matrix_utils import connect_matrix, join_matrix_room
32
+ from mmrelay.matrix_utils import (
33
+ connect_matrix,
34
+ join_matrix_room,
35
+ )
27
36
  from mmrelay.matrix_utils import logger as matrix_logger
28
- 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
+ )
29
42
  from mmrelay.meshtastic_utils import connect_meshtastic
30
43
  from mmrelay.meshtastic_utils import logger as meshtastic_logger
31
44
  from mmrelay.message_queue import (
@@ -39,20 +52,22 @@ from mmrelay.plugin_loader import load_plugins
39
52
  # Initialize logger
40
53
  logger = get_logger(name=APP_DISPLAY_NAME)
41
54
 
42
- # Set the logging level for 'nio' to ERROR to suppress warnings
43
- logging.getLogger("nio").setLevel(logging.ERROR)
44
-
45
55
 
46
56
  # Flag to track if banner has been printed
47
57
  _banner_printed = False
48
58
 
49
59
 
50
60
  def print_banner():
51
- """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
+ """
52
67
  global _banner_printed
53
68
  # Only print the banner once
54
69
  if not _banner_printed:
55
- logger.info(f"Starting MMRelay v{__version__}")
70
+ logger.info(f"Starting MMRelay version {__version__}")
56
71
  _banner_printed = True
57
72
 
58
73
 
@@ -109,6 +124,13 @@ async def main(config):
109
124
  # Connect to Matrix
110
125
  matrix_client = await connect_matrix(passed_config=config)
111
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
+
112
134
  # Join the rooms specified in the config.yaml
113
135
  for room in matrix_rooms:
114
136
  await join_matrix_room(matrix_client, room["id"])
@@ -116,12 +138,14 @@ async def main(config):
116
138
  # Register the message callback for Matrix
117
139
  matrix_logger.info("Listening for inbound Matrix messages...")
118
140
  matrix_client.add_event_callback(
119
- on_room_message, (RoomMessageText, RoomMessageNotice, RoomMessageEmote)
141
+ on_room_message,
142
+ (RoomMessageText, RoomMessageNotice, RoomMessageEmote, ReactionEvent),
120
143
  )
121
- # Add ReactionEvent callback so we can handle matrix reactions
122
- 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,))
123
147
  # Add RoomMemberEvent callback to track room-specific display name changes
124
- matrix_client.add_event_callback(on_room_member, RoomMemberEvent)
148
+ matrix_client.add_event_callback(on_room_member, (RoomMemberEvent,))
125
149
 
126
150
  # Set up shutdown event
127
151
  shutdown_event = asyncio.Event()
@@ -171,15 +195,33 @@ async def main(config):
171
195
  [sync_task, shutdown_task],
172
196
  return_when=asyncio.FIRST_COMPLETED,
173
197
  )
174
- if shutdown_event.is_set():
175
- matrix_logger.info("Shutdown event detected. Stopping sync loop...")
176
- sync_task.cancel()
198
+
199
+ # Cancel any pending tasks
200
+ for task in pending:
201
+ task.cancel()
177
202
  try:
178
- await sync_task
203
+ await task
179
204
  except asyncio.CancelledError:
180
205
  pass
206
+
207
+ if shutdown_event.is_set():
208
+ matrix_logger.info("Shutdown event detected. Stopping sync loop...")
181
209
  break
182
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
+
183
225
  except Exception as e:
184
226
  if shutdown_event.is_set():
185
227
  break
@@ -246,13 +288,16 @@ async def main(config):
246
288
 
247
289
 
248
290
  def run_main(args):
249
- """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.
250
295
 
251
- Args:
252
- 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`.
253
298
 
254
299
  Returns:
255
- 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).
256
301
  """
257
302
  # Print the banner at startup
258
303
  print_banner()
@@ -317,7 +362,17 @@ def run_main(args):
317
362
  config_rich_logger.info(f"Log file location: {log_file_path}")
318
363
 
319
364
  # Check if config exists and has the required keys
320
- 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"]
321
376
 
322
377
  # Check each key individually for better debugging
323
378
  for key in required_keys:
@@ -327,10 +382,21 @@ def run_main(args):
327
382
  if not config or not all(key in config for key in required_keys):
328
383
  # Exit with error if no config exists
329
384
  missing_keys = [key for key in required_keys if key not in config]
330
- logger.error(
331
- f"Configuration is missing required keys: {missing_keys}. "
332
- "Please create a valid config.yaml file or use --generate-config to create one."
333
- )
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()}")
334
400
  return 1
335
401
 
336
402
  try:
@@ -340,7 +406,11 @@ def run_main(args):
340
406
  logger.info("Interrupted by user. Exiting.")
341
407
  return 0
342
408
  except Exception as e:
409
+ import traceback
410
+
343
411
  logger.error(f"Error running main functionality: {e}")
412
+ logger.error("Full traceback:")
413
+ logger.error(traceback.format_exc())
344
414
  return 1
345
415
 
346
416