mmrelay 1.1.3__py3-none-any.whl → 1.1.4__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/plugin_loader.py CHANGED
@@ -519,26 +519,17 @@ def clone_or_update_repo(repo_url, ref, plugins_dir):
519
519
 
520
520
 
521
521
  def load_plugins_from_directory(directory, recursive=False):
522
- """Load plugin classes from Python files in a directory.
523
-
524
- Args:
525
- directory (str): Directory path to search for plugin files
526
- recursive (bool): Whether to search subdirectories recursively
527
-
528
- Returns:
529
- list: List of instantiated plugin objects found in the directory
522
+ """
523
+ Dynamically loads and instantiates plugin classes from Python files in a specified directory.
530
524
 
531
- Scans for .py files and attempts to import each as a module. Looks for
532
- a 'Plugin' class in each module and instantiates it if found.
525
+ Scans the given directory (and subdirectories if `recursive` is True) for `.py` files, importing each as a module and instantiating its `Plugin` class if present. Automatically attempts to install missing dependencies when a `ModuleNotFoundError` occurs, supporting both pip and pipx environments. Provides compatibility for plugins importing from either `plugins` or `mmrelay.plugins`. Skips files without a `Plugin` class or with unresolved import errors.
533
526
 
534
- Features:
535
- - Automatic dependency installation for missing imports (via pip/pipx)
536
- - Compatibility layer for import paths (plugins vs mmrelay.plugins)
537
- - Proper sys.path management for plugin directory imports
538
- - Comprehensive error handling and logging
527
+ Parameters:
528
+ directory (str): Path to the directory containing plugin files.
529
+ recursive (bool): If True, searches subdirectories recursively.
539
530
 
540
- Skips files that don't define a Plugin class or have import errors
541
- that can't be automatically resolved.
531
+ Returns:
532
+ list: Instantiated plugin objects found in the directory.
542
533
  """
543
534
  plugins = []
544
535
  if os.path.isdir(directory):
@@ -626,13 +617,23 @@ def load_plugins_from_directory(directory, recursive=False):
626
617
  )
627
618
 
628
619
  # Try to load the module again
629
- spec.loader.exec_module(plugin_module)
630
-
631
- if hasattr(plugin_module, "Plugin"):
632
- plugins.append(plugin_module.Plugin())
633
- else:
634
- logger.warning(
635
- f"{plugin_path} does not define a Plugin class."
620
+ try:
621
+ spec.loader.exec_module(plugin_module)
622
+
623
+ if hasattr(plugin_module, "Plugin"):
624
+ plugins.append(plugin_module.Plugin())
625
+ else:
626
+ logger.warning(
627
+ f"{plugin_path} does not define a Plugin class."
628
+ )
629
+ except ModuleNotFoundError:
630
+ logger.error(
631
+ f"Module {missing_module} still not available after installation. "
632
+ f"The package name might be different from the import name."
633
+ )
634
+ except Exception as retry_error:
635
+ logger.error(
636
+ f"Error loading plugin {plugin_path} after dependency installation: {retry_error}"
636
637
  )
637
638
 
638
639
  except subprocess.CalledProcessError:
@@ -660,25 +661,16 @@ def load_plugins_from_directory(directory, recursive=False):
660
661
 
661
662
 
662
663
  def load_plugins(passed_config=None):
663
- """Load and initialize all active plugins based on configuration.
664
+ """
665
+ Discovers, loads, and initializes all active plugins according to the configuration.
664
666
 
665
- Args:
666
- passed_config (dict, optional): Configuration dictionary to use.
667
- If None, uses global config variable.
667
+ This function manages the full plugin lifecycle: it loads core, custom, and community plugins as specified in the configuration, handles cloning and updating of community plugin repositories, installs dependencies as needed, and starts each active plugin. Plugins are filtered and sorted by priority before being returned. If plugins have already been loaded, returns the cached list.
668
+
669
+ Parameters:
670
+ passed_config (dict, optional): Configuration dictionary to use instead of the global config.
668
671
 
669
672
  Returns:
670
- list: List of active plugin instances sorted by priority
671
-
672
- This is the main plugin loading function that:
673
- - Loads core plugins from mmrelay.plugins package
674
- - Processes custom plugins from ~/.mmrelay/plugins/custom and plugins/custom
675
- - Downloads and loads community plugins from configured Git repositories
676
- - Filters plugins based on active status in configuration
677
- - Sorts active plugins by priority and calls their start() method
678
- - Sets up proper plugin configuration and channel mapping
679
-
680
- Only plugins explicitly marked as active=true in config are loaded.
681
- Custom and community plugins are cloned/updated automatically.
673
+ list: Active plugin instances sorted by priority.
682
674
  """
683
675
  global sorted_active_plugins
684
676
  global plugins_loaded
@@ -750,11 +742,15 @@ def load_plugins(passed_config=None):
750
742
  plugin_path = os.path.join(custom_dir, plugin_name)
751
743
  if os.path.exists(plugin_path):
752
744
  logger.debug(f"Loading custom plugin from: {plugin_path}")
753
- plugins.extend(
754
- load_plugins_from_directory(plugin_path, recursive=False)
755
- )
756
- plugin_found = True
757
- break
745
+ try:
746
+ plugins.extend(
747
+ load_plugins_from_directory(plugin_path, recursive=False)
748
+ )
749
+ plugin_found = True
750
+ break
751
+ except Exception as e:
752
+ logger.error(f"Failed to load custom plugin {plugin_name}: {e}")
753
+ continue
758
754
 
759
755
  if not plugin_found:
760
756
  logger.warning(
@@ -842,11 +838,17 @@ def load_plugins(passed_config=None):
842
838
  plugin_path = os.path.join(dir_path, repo_name)
843
839
  if os.path.exists(plugin_path):
844
840
  logger.info(f"Loading community plugin from: {plugin_path}")
845
- plugins.extend(
846
- load_plugins_from_directory(plugin_path, recursive=True)
847
- )
848
- plugin_found = True
849
- break
841
+ try:
842
+ plugins.extend(
843
+ load_plugins_from_directory(plugin_path, recursive=True)
844
+ )
845
+ plugin_found = True
846
+ break
847
+ except Exception as e:
848
+ logger.error(
849
+ f"Failed to load community plugin {repo_name}: {e}"
850
+ )
851
+ continue
850
852
 
851
853
  if not plugin_found:
852
854
  logger.warning(
@@ -900,4 +902,5 @@ def load_plugins(passed_config=None):
900
902
  else:
901
903
  logger.info("Loaded: none")
902
904
 
903
- plugins_loaded = True # Set the flag to indicate that plugins have been load
905
+ plugins_loaded = True # Set the flag to indicate that plugins have been loaded
906
+ return sorted_active_plugins
@@ -7,6 +7,11 @@ import markdown
7
7
  import schedule
8
8
 
9
9
  from mmrelay.config import get_plugin_data_dir
10
+ from mmrelay.constants.database import (
11
+ DEFAULT_MAX_DATA_ROWS_PER_NODE_BASE,
12
+ DEFAULT_TEXT_TRUNCATION_LENGTH,
13
+ )
14
+ from mmrelay.constants.queue import DEFAULT_MESSAGE_DELAY
10
15
  from mmrelay.db_utils import (
11
16
  delete_plugin_data,
12
17
  get_plugin_data,
@@ -14,7 +19,7 @@ from mmrelay.db_utils import (
14
19
  store_plugin_data,
15
20
  )
16
21
  from mmrelay.log_utils import get_logger
17
- from mmrelay.message_queue import DEFAULT_MESSAGE_DELAY, queue_message
22
+ from mmrelay.message_queue import queue_message
18
23
 
19
24
  # Global config variable that will be set from main.py
20
25
  config = None
@@ -47,7 +52,7 @@ class BasePlugin(ABC):
47
52
 
48
53
  # Class-level default attributes
49
54
  plugin_name = None # Must be overridden in subclasses
50
- max_data_rows_per_node = 100
55
+ max_data_rows_per_node = DEFAULT_MAX_DATA_ROWS_PER_NODE_BASE
51
56
  priority = 10
52
57
 
53
58
  @property
@@ -251,19 +256,63 @@ class BasePlugin(ABC):
251
256
  """
252
257
  return self.response_delay
253
258
 
259
+ def get_my_node_id(self):
260
+ """Get the relay's Meshtastic node ID.
261
+
262
+ Returns:
263
+ int: The relay's node ID, or None if unavailable
264
+
265
+ This method provides access to the relay's own node ID without requiring
266
+ plugins to call connect_meshtastic() directly. Useful for determining
267
+ if messages are direct messages or for other node identification needs.
268
+
269
+ The node ID is cached after first successful retrieval to avoid repeated
270
+ connection calls, as the relay's node ID is static during runtime.
271
+ """
272
+ if hasattr(self, "_my_node_id"):
273
+ return self._my_node_id
274
+
275
+ from mmrelay.meshtastic_utils import connect_meshtastic
276
+
277
+ meshtastic_client = connect_meshtastic()
278
+ if meshtastic_client and meshtastic_client.myInfo:
279
+ self._my_node_id = meshtastic_client.myInfo.my_node_num
280
+ return self._my_node_id
281
+ return None
282
+
283
+ def is_direct_message(self, packet):
284
+ """Check if a Meshtastic packet is a direct message to this relay.
285
+
286
+ Args:
287
+ packet (dict): Meshtastic packet data
288
+
289
+ Returns:
290
+ bool: True if the packet is a direct message to this relay, False otherwise
291
+
292
+ This method encapsulates the common pattern of checking if a message
293
+ is addressed directly to the relay node, eliminating the need for plugins
294
+ to call connect_meshtastic() directly for DM detection.
295
+ """
296
+ toId = packet.get("to")
297
+ if toId is None:
298
+ return False
299
+
300
+ myId = self.get_my_node_id()
301
+ return toId == myId
302
+
254
303
  def send_message(self, text: str, channel: int = 0, destination_id=None) -> bool:
255
304
  """
256
- Send a message to the Meshtastic network via the message queue.
305
+ Send a message to the Meshtastic network using the message queue.
257
306
 
258
- Automatically queues the message for broadcast or direct delivery, applying rate limiting as configured. Returns True if the message was successfully queued, or False if the Meshtastic client is unavailable.
307
+ Queues the specified text for broadcast or direct delivery on the given channel. Returns True if the message was successfully queued, or False if the Meshtastic client is unavailable.
259
308
 
260
309
  Parameters:
261
- text (str): The message text to send.
262
- channel (int, optional): Channel index to send the message on. Defaults to 0.
263
- destination_id (optional): Destination node ID for direct messages; if None, the message is broadcast.
310
+ text (str): The message content to send.
311
+ channel (int, optional): The channel index for sending the message. Defaults to 0.
312
+ destination_id (optional): The destination node ID for direct messages. If None, the message is broadcast.
264
313
 
265
314
  Returns:
266
- bool: True if the message was queued successfully, False otherwise.
315
+ bool: True if the message was queued successfully; False otherwise.
267
316
  """
268
317
  from mmrelay.meshtastic_utils import connect_meshtastic
269
318
 
@@ -272,9 +321,7 @@ class BasePlugin(ABC):
272
321
  self.logger.error("No Meshtastic client available")
273
322
  return False
274
323
 
275
- description = (
276
- f"Plugin {self.plugin_name}: {text[:50]}{'...' if len(text) > 50 else ''}"
277
- )
324
+ description = f"Plugin {self.plugin_name}: {text[:DEFAULT_TEXT_TRUNCATION_LENGTH]}{'...' if len(text) > DEFAULT_TEXT_TRUNCATION_LENGTH else ''}"
278
325
 
279
326
  send_kwargs = {
280
327
  "text": text,
@@ -2,6 +2,8 @@ import re
2
2
 
3
3
  from haversine import haversine
4
4
 
5
+ from mmrelay.constants.database import DEFAULT_DISTANCE_KM_FALLBACK, DEFAULT_RADIUS_KM
6
+ from mmrelay.constants.formats import TEXT_MESSAGE_APP
5
7
  from mmrelay.meshtastic_utils import connect_meshtastic
6
8
  from mmrelay.plugins.base_plugin import BasePlugin
7
9
 
@@ -25,6 +27,14 @@ class Plugin(BasePlugin):
25
27
  async def handle_meshtastic_message(
26
28
  self, packet, formatted_message, longname, meshnet_name
27
29
  ):
30
+ """
31
+ Handles incoming Meshtastic packets for the drop message plugin, delivering or storing dropped messages based on packet content and node location.
32
+
33
+ When a packet is received, attempts to deliver any stored dropped messages to the sender if they are within a configured radius of the message's location and are not the original dropper. If the packet contains a properly formatted drop command, extracts the message and stores it with the sender's current location for future delivery.
34
+
35
+ Returns:
36
+ True if a drop command was processed and stored, False otherwise.
37
+ """
28
38
  meshtastic_client = connect_meshtastic()
29
39
  nodeInfo = meshtastic_client.getMyNodeInfo()
30
40
 
@@ -55,10 +65,8 @@ class Plugin(BasePlugin):
55
65
  message["location"],
56
66
  )
57
67
  except (ValueError, TypeError):
58
- distance_km = 1000
59
- radius_km = (
60
- self.config["radius_km"] if "radius_km" in self.config else 5
61
- )
68
+ distance_km = DEFAULT_DISTANCE_KM_FALLBACK
69
+ radius_km = self.config.get("radius_km", DEFAULT_RADIUS_KM)
62
70
  if distance_km <= radius_km:
63
71
  target_node = packet["fromId"]
64
72
  self.logger.debug(f"Sending dropped message to {target_node}")
@@ -76,7 +84,7 @@ class Plugin(BasePlugin):
76
84
  if (
77
85
  "decoded" in packet
78
86
  and "portnum" in packet["decoded"]
79
- and packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP"
87
+ and packet["decoded"]["portnum"] == TEXT_MESSAGE_APP
80
88
  ):
81
89
  text = packet["decoded"]["text"] if "text" in packet["decoded"] else None
82
90
  if f"!{self.plugin_name}" not in text:
@@ -6,6 +6,7 @@ import re
6
6
 
7
7
  from meshtastic import mesh_pb2
8
8
 
9
+ from mmrelay.constants.database import DEFAULT_MAX_DATA_ROWS_PER_NODE_MESH_RELAY
9
10
  from mmrelay.plugins.base_plugin import BasePlugin, config
10
11
 
11
12
 
@@ -25,21 +26,17 @@ class Plugin(BasePlugin):
25
26
  """
26
27
 
27
28
  plugin_name = "mesh_relay"
28
- max_data_rows_per_node = 50
29
+ max_data_rows_per_node = DEFAULT_MAX_DATA_ROWS_PER_NODE_MESH_RELAY
29
30
 
30
31
  def normalize(self, dict_obj):
31
- """Normalize packet data to consistent dictionary format.
32
+ """
33
+ Converts packet data in various formats (dict, JSON string, or plain string) into a normalized dictionary with raw data fields removed.
32
34
 
33
- Args:
34
- dict_obj: Packet data (dict, JSON string, or plain string)
35
+ Parameters:
36
+ dict_obj: Packet data as a dictionary, JSON string, or plain string.
35
37
 
36
38
  Returns:
37
- dict: Normalized packet dictionary with raw data stripped
38
-
39
- Handles various packet formats:
40
- - Dict objects (passed through)
41
- - JSON strings (parsed)
42
- - Plain strings (wrapped in TEXT_MESSAGE_APP format)
39
+ A dictionary representing the normalized packet with raw fields stripped.
43
40
  """
44
41
  if not isinstance(dict_obj, dict):
45
42
  try:
@@ -3,6 +3,7 @@ import asyncio
3
3
  import requests
4
4
  from meshtastic.mesh_interface import BROADCAST_NUM
5
5
 
6
+ from mmrelay.constants.formats import TEXT_MESSAGE_APP
6
7
  from mmrelay.plugins.base_plugin import BasePlugin
7
8
 
8
9
 
@@ -122,10 +123,18 @@ class Plugin(BasePlugin):
122
123
  async def handle_meshtastic_message(
123
124
  self, packet, formatted_message, longname, meshnet_name
124
125
  ):
126
+ """
127
+ Processes incoming Meshtastic text messages and responds with a weather forecast if the plugin command is detected.
128
+
129
+ Checks if the message is a valid text message on the expected port, verifies channel and command enablement, retrieves the sender's GPS location, generates a weather forecast, and sends the response either as a direct message or broadcast depending on the message type.
130
+
131
+ Returns:
132
+ bool: True if the message was handled and a response was sent; False otherwise.
133
+ """
125
134
  if (
126
135
  "decoded" in packet
127
136
  and "portnum" in packet["decoded"]
128
- and packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP"
137
+ and packet["decoded"]["portnum"] == TEXT_MESSAGE_APP
129
138
  and "text" in packet["decoded"]
130
139
  ):
131
140
  message = packet["decoded"]["text"].strip()
mmrelay/setup_utils.py CHANGED
@@ -14,6 +14,7 @@ import subprocess
14
14
  import sys
15
15
  from pathlib import Path
16
16
 
17
+ from mmrelay.constants.database import PROGRESS_COMPLETE, PROGRESS_TOTAL_STEPS
17
18
  from mmrelay.tools import get_service_template_path
18
19
 
19
20
 
@@ -54,7 +55,11 @@ def print_service_commands():
54
55
 
55
56
 
56
57
  def wait_for_service_start():
57
- """Wait for the service to start with a Rich progress indicator."""
58
+ """
59
+ Displays a progress spinner while waiting up to 10 seconds for the mmrelay service to become active.
60
+
61
+ The function checks the service status after 5 seconds and completes early if the service is detected as active.
62
+ """
58
63
  import time
59
64
 
60
65
  from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
@@ -67,7 +72,7 @@ def wait_for_service_start():
67
72
  transient=True,
68
73
  ) as progress:
69
74
  # Add a task that will run for approximately 10 seconds
70
- task = progress.add_task("Starting", total=100)
75
+ task = progress.add_task("Starting", total=PROGRESS_TOTAL_STEPS)
71
76
 
72
77
  # Update progress over 10 seconds
73
78
  for i in range(10):
@@ -76,7 +81,7 @@ def wait_for_service_start():
76
81
 
77
82
  # Check if service is active after 5 seconds to potentially finish early
78
83
  if i >= 5 and is_service_active():
79
- progress.update(task, completed=100)
84
+ progress.update(task, completed=PROGRESS_COMPLETE)
80
85
  break
81
86
 
82
87
 
@@ -372,10 +377,11 @@ def check_loginctl_available():
372
377
 
373
378
 
374
379
  def check_lingering_enabled():
375
- """Check if user lingering is enabled.
380
+ """
381
+ Determine whether user lingering is enabled for the current user.
376
382
 
377
383
  Returns:
378
- bool: True if lingering is enabled, False otherwise.
384
+ bool: True if user lingering is enabled, False otherwise.
379
385
  """
380
386
  try:
381
387
  username = os.environ.get("USER", os.environ.get("USERNAME"))
@@ -386,7 +392,8 @@ def check_lingering_enabled():
386
392
  text=True,
387
393
  )
388
394
  return result.returncode == 0 and "Linger=yes" in result.stdout
389
- except Exception:
395
+ except Exception as e:
396
+ print(f"Error checking lingering status: {e}")
390
397
  return False
391
398
 
392
399
 
@@ -417,7 +424,14 @@ def enable_lingering():
417
424
 
418
425
 
419
426
  def install_service():
420
- """Install or update the MMRelay user service."""
427
+ """
428
+ Install or update the MMRelay systemd user service, guiding the user through creation, updating, enabling, and starting the service as needed.
429
+
430
+ Prompts the user for confirmation before updating an existing service file, enabling user lingering, enabling the service to start at boot, and starting or restarting the service. Handles user interruptions gracefully and prints a summary of the service status and management commands upon completion.
431
+
432
+ Returns:
433
+ bool: True if the installation or update process completes successfully, False otherwise.
434
+ """
421
435
  # Check if service already exists
422
436
  existing_service = read_service_file()
423
437
  service_path = get_user_service_path()
@@ -431,11 +445,14 @@ def install_service():
431
445
 
432
446
  if update_needed:
433
447
  print(f"The service file needs to be updated: {reason}")
434
- if (
435
- not input("Do you want to update the service file? (y/n): ")
436
- .lower()
437
- .startswith("y")
438
- ):
448
+ try:
449
+ user_input = input("Do you want to update the service file? (y/n): ")
450
+ if not user_input.lower().startswith("y"):
451
+ print("Service update cancelled.")
452
+ print_service_commands()
453
+ return True
454
+ except (EOFError, KeyboardInterrupt):
455
+ print("\nInput cancelled. Proceeding with default behavior.")
439
456
  print("Service update cancelled.")
440
457
  print_service_commands()
441
458
  return True
@@ -450,9 +467,11 @@ def install_service():
450
467
  if not create_service_file():
451
468
  return False
452
469
 
453
- # Reload daemon
470
+ # Reload daemon (continue even if this fails)
454
471
  if not reload_daemon():
455
- return False
472
+ print(
473
+ "Warning: Failed to reload systemd daemon. You may need to run 'systemctl --user daemon-reload' manually."
474
+ )
456
475
 
457
476
  if existing_service:
458
477
  print("Service file updated successfully")
@@ -473,13 +492,16 @@ def install_service():
473
492
  print(
474
493
  "Lingering allows user services to run even when you're not logged in."
475
494
  )
476
- if (
477
- input(
495
+ try:
496
+ user_input = input(
478
497
  "Do you want to enable lingering for your user? (requires sudo) (y/n): "
479
498
  )
480
- .lower()
481
- .startswith("y")
482
- ):
499
+ should_enable_lingering = user_input.lower().startswith("y")
500
+ except (EOFError, KeyboardInterrupt):
501
+ print("\nInput cancelled. Skipping lingering setup.")
502
+ should_enable_lingering = False
503
+
504
+ if should_enable_lingering:
483
505
  enable_lingering()
484
506
 
485
507
  # Check if the service is already enabled
@@ -488,11 +510,16 @@ def install_service():
488
510
  print("The service is already enabled to start at boot.")
489
511
  else:
490
512
  print("The service is not currently enabled to start at boot.")
491
- if (
492
- input("Do you want to enable the service to start at boot? (y/n): ")
493
- .lower()
494
- .startswith("y")
495
- ):
513
+ try:
514
+ user_input = input(
515
+ "Do you want to enable the service to start at boot? (y/n): "
516
+ )
517
+ enable_service = user_input.lower().startswith("y")
518
+ except (EOFError, KeyboardInterrupt):
519
+ print("\nInput cancelled. Skipping service enable.")
520
+ enable_service = False
521
+
522
+ if enable_service:
496
523
  try:
497
524
  subprocess.run(
498
525
  ["/usr/bin/systemctl", "--user", "enable", "mmrelay.service"],
@@ -509,7 +536,14 @@ def install_service():
509
536
  service_active = is_service_active()
510
537
  if service_active:
511
538
  print("The service is already running.")
512
- if input("Do you want to restart the service? (y/n): ").lower().startswith("y"):
539
+ try:
540
+ user_input = input("Do you want to restart the service? (y/n): ")
541
+ restart_service = user_input.lower().startswith("y")
542
+ except (EOFError, KeyboardInterrupt):
543
+ print("\nInput cancelled. Skipping service restart.")
544
+ restart_service = False
545
+
546
+ if restart_service:
513
547
  try:
514
548
  subprocess.run(
515
549
  ["/usr/bin/systemctl", "--user", "restart", "mmrelay.service"],
@@ -526,11 +560,14 @@ def install_service():
526
560
  print(f"Error: {e}")
527
561
  else:
528
562
  print("The service is not currently running.")
529
- if (
530
- input("Do you want to start the service now? (y/n): ")
531
- .lower()
532
- .startswith("y")
533
- ):
563
+ try:
564
+ user_input = input("Do you want to start the service now? (y/n): ")
565
+ start_now = user_input.lower().startswith("y")
566
+ except (EOFError, KeyboardInterrupt):
567
+ print("\nInput cancelled. Skipping service start.")
568
+ start_now = False
569
+
570
+ if start_now:
534
571
  if start_service():
535
572
  # Wait for the service to start
536
573
  wait_for_service_start()
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmrelay
3
- Version: 1.1.3
3
+ Version: 1.1.4
4
4
  Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
5
- Home-page: https://github.com/geoffwhittington/meshtastic-matrix-relay
5
+ Home-page: https://github.com/jeremiah-k/meshtastic-matrix-relay
6
6
  Author: Geoff Whittington, Jeremiah K., and contributors
7
7
  Author-email: jeremiahk@gmx.com
8
- Project-URL: Bug Tracker, https://github.com/geoffwhittington/meshtastic-matrix-relay/issues
8
+ Project-URL: Bug Tracker, https://github.com/jeremiah-k/meshtastic-matrix-relay/issues
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
11
11
  Classifier: Operating System :: OS Independent
@@ -0,0 +1,43 @@
1
+ mmrelay/__init__.py,sha256=HEhksEbBrRIg-V44f45Ik9ZoN8cgAZPX5hVtq4c5a5o,120
2
+ mmrelay/cli.py,sha256=IduTq-Uyf99LEJD8jTSJBgLdrCOlYA_b69ClhsvqXMA,17116
3
+ mmrelay/config.py,sha256=cOLpVx51Rd2RhUFOc-rxPVsVVPZhWJ2A07y3Mf9e_rg,9672
4
+ mmrelay/config_checker.py,sha256=CW89TwNmGB03LYXUmKbygjAthJw_8KSq7csBbTTCRV0,6003
5
+ mmrelay/db_utils.py,sha256=b_cuw4zOwGlvk4CljFh3SguqOi-TRCHW_s9RCt9gUsU,19920
6
+ mmrelay/log_utils.py,sha256=DKkFKkpOLbSTvvbzYzvQXzGGxSQP5vVRtmbC6l-d2WY,7380
7
+ mmrelay/main.py,sha256=n4Qn4KdlXxWv6QcDrkX5rnJFEbNoC2s7c2kOf3dUOx4,13225
8
+ mmrelay/matrix_utils.py,sha256=iyQw3mU49fHP7n9yieQLxX8AEwIzhcktnrMAzlX7Wm0,55776
9
+ mmrelay/meshtastic_utils.py,sha256=I9jZOXWsKbrIVEKTD_ie9Qm_gY4twzg6iMOXlMVoX2w,35939
10
+ mmrelay/message_queue.py,sha256=tHKziIPwQ9Xm7qpITyR1r2e-ftdn4ZivdZhek9SmMcQ,18703
11
+ mmrelay/plugin_loader.py,sha256=Yyu8M399BSF8OVsCkETdhdn7xo673leiardl-lO9JXQ,40139
12
+ mmrelay/setup_utils.py,sha256=EAVPmKHQmlXnxMcrkhlU27fBPjJCiwTVSLZdbEFsWLQ,22095
13
+ mmrelay/constants/__init__.py,sha256=M8AXeIcS1JuS8OwmfTmhcCOkAz5XmWlNQ53GBxYOx94,1494
14
+ mmrelay/constants/app.py,sha256=T7qud8aJAMIV55KuT0LakIoS1y9AdteHMU-SUaF4Eqw,419
15
+ mmrelay/constants/config.py,sha256=ZhEQBP6TZS89XkHzZdmPCIZNGSjI2-tTzK5v0l7ilIQ,2350
16
+ mmrelay/constants/database.py,sha256=4cHfYfBePDUUtVSflrWyStcxKSQv7VE-jSrb1IzAjls,646
17
+ mmrelay/constants/formats.py,sha256=cjbrfNNFCKoGSFsFHR1QQDEQudiGquA9MUapfm0_ZNI,494
18
+ mmrelay/constants/messages.py,sha256=xr77IfurBJCrM4Qp53nbDr-Bo82Di7xZcICBOinjGq4,987
19
+ mmrelay/constants/network.py,sha256=8zpQemeVAflSjtZJgg7cQ4IMzeQzCah38PJbLNHmjvY,933
20
+ mmrelay/constants/queue.py,sha256=yyWSrtq06b5GWzZwdl6IFtrMvxEuF9PdKSNPh8DdL2M,565
21
+ mmrelay/plugins/__init__.py,sha256=KVMQIXRhe0wlGj4O3IZ0vOIQRKFkfPYejHXhJL17qrc,51
22
+ mmrelay/plugins/base_plugin.py,sha256=p0ojzdiXTm9VpZYbZ-GPysCEhlsNQ04oUWefIfvfsjU,20181
23
+ mmrelay/plugins/debug_plugin.py,sha256=adX0cRJHUEDLldajybPfiRDDlvytkZe5aN_dSgNKP2Y,870
24
+ mmrelay/plugins/drop_plugin.py,sha256=x4S-e0Muun2Dy1H2qwRMTBB1ptLmy7ZZJhgPu-KefGs,5394
25
+ mmrelay/plugins/health_plugin.py,sha256=svV_GfpAVL0QhiVzi3PVZ1mNpsOL1NHSmkRF-Mn_ExE,2250
26
+ mmrelay/plugins/help_plugin.py,sha256=S7nBhsANK46Zv9wPHOVegPGcuYGMErBsxAnrRlSSCwg,2149
27
+ mmrelay/plugins/map_plugin.py,sha256=eHV_t3TFcypBD4xT_OQx0hD6_iGkLJOADjwYVny0PvE,11292
28
+ mmrelay/plugins/mesh_relay_plugin.py,sha256=gpQkO8S-LqDNwqJpqq5ewGXVEST-JZgOsJmrE-qRzuw,7631
29
+ mmrelay/plugins/nodes_plugin.py,sha256=RDabzyG5hKG5aYWecsRUcLSjMCCv6Pngmq2Qpld1A1U,2903
30
+ mmrelay/plugins/ping_plugin.py,sha256=8uFnT3qfO3RBaTUOx348voIfKpzXB3zTfcT6Gtfc8kM,4070
31
+ mmrelay/plugins/telemetry_plugin.py,sha256=8SxWv4BLXMUTbiVaD3MjlMMdQyS7S_1OfLlVNAUMSO0,6306
32
+ mmrelay/plugins/weather_plugin.py,sha256=RkiDeZpTWAb0VjsDC3qkGUrYY4NmzJgNa-iwBCwg8OM,9186
33
+ mmrelay/tools/__init__.py,sha256=WFjDQjdevgg19_zT6iEoL29rvb1JPqYSd8708Jn5D7A,838
34
+ mmrelay/tools/mmrelay.service,sha256=3vqK1VbfXvVftkTrTEOan77aTHeOT36hIAL7HqJsmTg,567
35
+ mmrelay/tools/sample-docker-compose.yaml,sha256=vVgJrh-6l48hkj5F-52JA5tpDWPBjiPQ36CE9Pkqn44,1251
36
+ mmrelay/tools/sample.env,sha256=RP-o3rX3jnEIrVG2rqCZq31O1yRXou4HcGrXWLVbKKw,311
37
+ mmrelay/tools/sample_config.yaml,sha256=grS70MKHFd9e_lZ3GkmzPi4RRW-PdahMOMPCAg07MWs,4718
38
+ mmrelay-1.1.4.dist-info/licenses/LICENSE,sha256=aB_07MhnK-bL5WLI1ucXLUSdW_yBVoepPRYB0kaAOl8,35204
39
+ mmrelay-1.1.4.dist-info/METADATA,sha256=icpHKviPELzrsfvnNuXx0KIIEbXyAzAFLcte4pfQ6U8,6481
40
+ mmrelay-1.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
+ mmrelay-1.1.4.dist-info/entry_points.txt,sha256=SJZwGUOEpQ-qx4H8UL4xKFnKeInGUaZNW1I0ddjK7Ws,45
42
+ mmrelay-1.1.4.dist-info/top_level.txt,sha256=B_ZLCRm7NYAmI3PipRUyHGymP-C-q16LSeMGzmqJfo4,8
43
+ mmrelay-1.1.4.dist-info/RECORD,,
@@ -1,35 +0,0 @@
1
- mmrelay/__init__.py,sha256=4ciaRmcwzGfuaU_HuNDXVr4Qk0WVkPydhrdXIVo9VOs,120
2
- mmrelay/cli.py,sha256=hdPTlcGsXTJC9GEUiScG7b3IFp02B3lwhqgwFpU3NsM,13835
3
- mmrelay/config.py,sha256=5VZag8iSc5yLQgvwI76bbpizbtqag74cHnfXCrWHNyA,7910
4
- mmrelay/config_checker.py,sha256=UnoHVTXzfdTfFkbmXv9r_Si76v-sxXLb5FOaQSOM45E,4909
5
- mmrelay/db_utils.py,sha256=eTMMkYVWsmO_DkrBfnZMw4ohg_xa0S9TXJoBjRFTwzo,13590
6
- mmrelay/log_utils.py,sha256=1zVHBJ5qSOG1O4cq4Ebngq9dp81vaZOEq5khulAFuuM,6611
7
- mmrelay/main.py,sha256=46J-B6wDRawB392u71kP6pxNoayBdmwOiKcqsleikhU,13182
8
- mmrelay/matrix_utils.py,sha256=Ans7Wroh693un0LOp4x2p8NPfGFJ9z6TnG6ApXRXg7Q,55686
9
- mmrelay/meshtastic_utils.py,sha256=iy7BwMlqOwpkqSB0pTx2n8oqaKs2kWQ4PXFk4y16ys8,31440
10
- mmrelay/message_queue.py,sha256=spLK7pVugnl9djVLIh5-P7iHZ7kRtZEZIWTcQ4X1TfI,18529
11
- mmrelay/plugin_loader.py,sha256=UJ-i6cL2q_hwMTqjRkaOAQDTj6uUrmyj56-XbUVGcng,39231
12
- mmrelay/setup_utils.py,sha256=N6qdScHKHEMFKDmT1l7dcLDPNTusZXPkyxrLXjFLhRI,19910
13
- mmrelay/plugins/__init__.py,sha256=KVMQIXRhe0wlGj4O3IZ0vOIQRKFkfPYejHXhJL17qrc,51
14
- mmrelay/plugins/base_plugin.py,sha256=DJdM-l-69sMnx6Fgn0rweWF5CuWilnQQAT3nm6eq8f4,18359
15
- mmrelay/plugins/debug_plugin.py,sha256=adX0cRJHUEDLldajybPfiRDDlvytkZe5aN_dSgNKP2Y,870
16
- mmrelay/plugins/drop_plugin.py,sha256=0GOz0dgLnFST1HTgqrMayrjwmYlnu02QxfnTZtdPZ3U,4671
17
- mmrelay/plugins/health_plugin.py,sha256=svV_GfpAVL0QhiVzi3PVZ1mNpsOL1NHSmkRF-Mn_ExE,2250
18
- mmrelay/plugins/help_plugin.py,sha256=S7nBhsANK46Zv9wPHOVegPGcuYGMErBsxAnrRlSSCwg,2149
19
- mmrelay/plugins/map_plugin.py,sha256=eHV_t3TFcypBD4xT_OQx0hD6_iGkLJOADjwYVny0PvE,11292
20
- mmrelay/plugins/mesh_relay_plugin.py,sha256=PIQBACC7Yjq7hzKJJNycHTjkayEC9tsC8-JNG4q_AY8,7563
21
- mmrelay/plugins/nodes_plugin.py,sha256=RDabzyG5hKG5aYWecsRUcLSjMCCv6Pngmq2Qpld1A1U,2903
22
- mmrelay/plugins/ping_plugin.py,sha256=8uFnT3qfO3RBaTUOx348voIfKpzXB3zTfcT6Gtfc8kM,4070
23
- mmrelay/plugins/telemetry_plugin.py,sha256=8SxWv4BLXMUTbiVaD3MjlMMdQyS7S_1OfLlVNAUMSO0,6306
24
- mmrelay/plugins/weather_plugin.py,sha256=1bQhmiX-enNphzGoFVprU0LcZQX9BvGxWAJAG8Wekg0,8596
25
- mmrelay/tools/__init__.py,sha256=WFjDQjdevgg19_zT6iEoL29rvb1JPqYSd8708Jn5D7A,838
26
- mmrelay/tools/mmrelay.service,sha256=3vqK1VbfXvVftkTrTEOan77aTHeOT36hIAL7HqJsmTg,567
27
- mmrelay/tools/sample-docker-compose.yaml,sha256=vVgJrh-6l48hkj5F-52JA5tpDWPBjiPQ36CE9Pkqn44,1251
28
- mmrelay/tools/sample.env,sha256=RP-o3rX3jnEIrVG2rqCZq31O1yRXou4HcGrXWLVbKKw,311
29
- mmrelay/tools/sample_config.yaml,sha256=grS70MKHFd9e_lZ3GkmzPi4RRW-PdahMOMPCAg07MWs,4718
30
- mmrelay-1.1.3.dist-info/licenses/LICENSE,sha256=aB_07MhnK-bL5WLI1ucXLUSdW_yBVoepPRYB0kaAOl8,35204
31
- mmrelay-1.1.3.dist-info/METADATA,sha256=gNG_TVwJfpvt0s7sd3WDEceXl8Li2WCLGHTkYkXOWVQ,6493
32
- mmrelay-1.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
- mmrelay-1.1.3.dist-info/entry_points.txt,sha256=SJZwGUOEpQ-qx4H8UL4xKFnKeInGUaZNW1I0ddjK7Ws,45
34
- mmrelay-1.1.3.dist-info/top_level.txt,sha256=B_ZLCRm7NYAmI3PipRUyHGymP-C-q16LSeMGzmqJfo4,8
35
- mmrelay-1.1.3.dist-info/RECORD,,