mmrelay 1.0.3__py3-none-any.whl → 1.0.5__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/config.py CHANGED
@@ -103,6 +103,32 @@ def get_data_dir():
103
103
  return data_dir
104
104
 
105
105
 
106
+ def get_plugin_data_dir(plugin_name=None):
107
+ """
108
+ Returns the directory for storing plugin-specific data files.
109
+ If plugin_name is provided, returns a plugin-specific subdirectory.
110
+ Creates the directory if it doesn't exist.
111
+
112
+ Example:
113
+ - get_plugin_data_dir() returns ~/.mmrelay/data/plugins/
114
+ - get_plugin_data_dir("my_plugin") returns ~/.mmrelay/data/plugins/my_plugin/
115
+ """
116
+ # Get the base data directory
117
+ base_data_dir = get_data_dir()
118
+
119
+ # Create the plugins directory
120
+ plugins_data_dir = os.path.join(base_data_dir, "plugins")
121
+ os.makedirs(plugins_data_dir, exist_ok=True)
122
+
123
+ # If a plugin name is provided, create and return a plugin-specific directory
124
+ if plugin_name:
125
+ plugin_data_dir = os.path.join(plugins_data_dir, plugin_name)
126
+ os.makedirs(plugin_data_dir, exist_ok=True)
127
+ return plugin_data_dir
128
+
129
+ return plugins_data_dir
130
+
131
+
106
132
  def get_log_dir():
107
133
  """
108
134
  Returns the directory for storing log files.
mmrelay/plugin_loader.py CHANGED
@@ -56,50 +56,444 @@ def get_community_plugin_dirs():
56
56
  return dirs
57
57
 
58
58
 
59
- def clone_or_update_repo(repo_url, tag, plugins_dir):
59
+ def clone_or_update_repo(repo_url, ref, plugins_dir):
60
60
  # Extract the repository name from the URL
61
61
  repo_name = os.path.splitext(os.path.basename(repo_url.rstrip("/")))[0]
62
62
  repo_path = os.path.join(plugins_dir, repo_name)
63
+
64
+ # Default branch names to try if ref is not specified
65
+ default_branches = ["main", "master"]
66
+
67
+ # Get the ref type and value
68
+ ref_type = ref["type"] # "tag" or "branch"
69
+ ref_value = ref["value"]
70
+
71
+ # Log what we're trying to do
72
+ logger.info(f"Using {ref_type} '{ref_value}' for repository {repo_name}")
73
+
74
+ # If it's a branch and one of the default branches, we'll handle it specially
75
+ is_default_branch = ref_type == "branch" and ref_value in default_branches
76
+
63
77
  if os.path.isdir(repo_path):
64
78
  try:
65
- subprocess.check_call(["git", "-C", repo_path, "fetch"])
66
- subprocess.check_call(["git", "-C", repo_path, "checkout", tag])
67
- subprocess.check_call(["git", "-C", repo_path, "pull", "origin", tag])
68
- logger.info(f"Updated repository {repo_name} to {tag}")
79
+ # Fetch all branches but don't fetch tags to avoid conflicts
80
+ try:
81
+ subprocess.check_call(["git", "-C", repo_path, "fetch", "origin"])
82
+ except subprocess.CalledProcessError as e:
83
+ logger.warning(f"Error fetching from remote: {e}")
84
+ # Continue anyway, we'll try to use what we have
85
+
86
+ # If it's a default branch, handle it differently
87
+ if is_default_branch:
88
+ try:
89
+ # Check if we're already on the right branch
90
+ current_branch = subprocess.check_output(
91
+ ["git", "-C", repo_path, "rev-parse", "--abbrev-ref", "HEAD"],
92
+ universal_newlines=True,
93
+ ).strip()
94
+
95
+ if current_branch == ref_value:
96
+ # We're on the right branch, just pull
97
+ try:
98
+ subprocess.check_call(
99
+ ["git", "-C", repo_path, "pull", "origin", ref_value]
100
+ )
101
+ logger.info(
102
+ f"Updated repository {repo_name} branch {ref_value}"
103
+ )
104
+ return True
105
+ except subprocess.CalledProcessError as e:
106
+ logger.warning(f"Error pulling branch {ref_value}: {e}")
107
+ # Continue anyway, we'll use what we have
108
+ return True
109
+ else:
110
+ # Switch to the right branch
111
+ subprocess.check_call(
112
+ ["git", "-C", repo_path, "checkout", ref_value]
113
+ )
114
+ subprocess.check_call(
115
+ ["git", "-C", repo_path, "pull", "origin", ref_value]
116
+ )
117
+ if ref_type == "branch":
118
+ logger.info(f"Switched to and updated branch {ref_value}")
119
+ else:
120
+ logger.info(f"Switched to and updated tag {ref_value}")
121
+ return True
122
+ except subprocess.CalledProcessError:
123
+ # If we can't checkout the specified branch, try the other default branch
124
+ other_default = "main" if ref_value == "master" else "master"
125
+ try:
126
+ logger.warning(
127
+ f"Branch {ref_value} not found, trying {other_default}"
128
+ )
129
+ subprocess.check_call(
130
+ ["git", "-C", repo_path, "checkout", other_default]
131
+ )
132
+ subprocess.check_call(
133
+ ["git", "-C", repo_path, "pull", "origin", other_default]
134
+ )
135
+ logger.info(
136
+ f"Using {other_default} branch instead of {ref_value}"
137
+ )
138
+ return True
139
+ except subprocess.CalledProcessError:
140
+ # If that fails too, just use whatever branch we're on
141
+ logger.warning(
142
+ "Could not checkout any default branch, using current branch"
143
+ )
144
+ return True
145
+ else:
146
+ # Handle tag checkout
147
+ # Check if we're already on the correct tag/commit
148
+ try:
149
+ # Get the current commit hash
150
+ current_commit = subprocess.check_output(
151
+ ["git", "-C", repo_path, "rev-parse", "HEAD"],
152
+ universal_newlines=True,
153
+ ).strip()
154
+
155
+ # Get the commit hash for the tag
156
+ tag_commit = None
157
+ try:
158
+ tag_commit = subprocess.check_output(
159
+ ["git", "-C", repo_path, "rev-parse", ref_value],
160
+ universal_newlines=True,
161
+ ).strip()
162
+ except subprocess.CalledProcessError:
163
+ # Tag doesn't exist locally, we'll need to fetch it
164
+ pass
165
+
166
+ # If we're already at the tag's commit, we're done
167
+ if tag_commit and current_commit == tag_commit:
168
+ logger.info(
169
+ f"Repository {repo_name} is already at tag {ref_value}"
170
+ )
171
+ return True
172
+
173
+ # Otherwise, try to checkout the tag
174
+ subprocess.check_call(
175
+ ["git", "-C", repo_path, "checkout", ref_value]
176
+ )
177
+ if ref_type == "branch":
178
+ logger.info(
179
+ f"Updated repository {repo_name} to branch {ref_value}"
180
+ )
181
+ else:
182
+ logger.info(
183
+ f"Updated repository {repo_name} to tag {ref_value}"
184
+ )
185
+ return True
186
+ except subprocess.CalledProcessError:
187
+ # If tag checkout fails, try to fetch it specifically
188
+ logger.warning(
189
+ f"Tag {ref_value} not found locally, trying to fetch it specifically"
190
+ )
191
+ try:
192
+ # Try to fetch the specific tag, but first remove any existing tag with the same name
193
+ try:
194
+ # Delete the local tag if it exists to avoid conflicts
195
+ subprocess.check_call(
196
+ ["git", "-C", repo_path, "tag", "-d", ref_value]
197
+ )
198
+ except subprocess.CalledProcessError:
199
+ # Tag doesn't exist locally, which is fine
200
+ pass
201
+
202
+ # Now fetch the tag from remote
203
+ try:
204
+ # Try to fetch the tag
205
+ subprocess.check_call(
206
+ [
207
+ "git",
208
+ "-C",
209
+ repo_path,
210
+ "fetch",
211
+ "origin",
212
+ f"refs/tags/{ref_value}",
213
+ ]
214
+ )
215
+ except subprocess.CalledProcessError:
216
+ # If that fails, try to fetch the tag without the refs/tags/ prefix
217
+ subprocess.check_call(
218
+ [
219
+ "git",
220
+ "-C",
221
+ repo_path,
222
+ "fetch",
223
+ "origin",
224
+ f"refs/tags/{ref_value}:refs/tags/{ref_value}",
225
+ ]
226
+ )
227
+
228
+ subprocess.check_call(
229
+ ["git", "-C", repo_path, "checkout", ref_value]
230
+ )
231
+ if ref_type == "branch":
232
+ logger.info(
233
+ f"Successfully fetched and checked out branch {ref_value}"
234
+ )
235
+ else:
236
+ logger.info(
237
+ f"Successfully fetched and checked out tag {ref_value}"
238
+ )
239
+ return True
240
+ except subprocess.CalledProcessError:
241
+ # If that fails too, try as a branch
242
+ logger.warning(
243
+ f"Could not fetch tag {ref_value}, trying as a branch"
244
+ )
245
+ try:
246
+ subprocess.check_call(
247
+ ["git", "-C", repo_path, "fetch", "origin", ref_value]
248
+ )
249
+ subprocess.check_call(
250
+ ["git", "-C", repo_path, "checkout", ref_value]
251
+ )
252
+ subprocess.check_call(
253
+ ["git", "-C", repo_path, "pull", "origin", ref_value]
254
+ )
255
+ logger.info(
256
+ f"Updated repository {repo_name} to branch {ref_value}"
257
+ )
258
+ return True
259
+ except subprocess.CalledProcessError:
260
+ # If all else fails, just use a default branch
261
+ logger.warning(
262
+ f"Could not checkout {ref_value} as tag or branch, trying default branches"
263
+ )
264
+ for default_branch in default_branches:
265
+ try:
266
+ subprocess.check_call(
267
+ [
268
+ "git",
269
+ "-C",
270
+ repo_path,
271
+ "checkout",
272
+ default_branch,
273
+ ]
274
+ )
275
+ subprocess.check_call(
276
+ [
277
+ "git",
278
+ "-C",
279
+ repo_path,
280
+ "pull",
281
+ "origin",
282
+ default_branch,
283
+ ]
284
+ )
285
+ logger.info(
286
+ f"Using {default_branch} instead of {ref_value}"
287
+ )
288
+ return True
289
+ except subprocess.CalledProcessError:
290
+ continue
291
+
292
+ # If we get here, we couldn't checkout any branch
293
+ logger.warning(
294
+ "Could not checkout any branch, using current state"
295
+ )
296
+ return True
69
297
  except subprocess.CalledProcessError as e:
70
298
  logger.error(f"Error updating repository {repo_name}: {e}")
71
299
  logger.error(
72
300
  f"Please manually git clone the repository {repo_url} into {repo_path}"
73
301
  )
74
- sys.exit(1)
302
+ return False
75
303
  else:
304
+ # Repository doesn't exist yet, clone it
76
305
  try:
77
306
  os.makedirs(plugins_dir, exist_ok=True)
78
- subprocess.check_call(
79
- ["git", "clone", "--branch", tag, repo_url], cwd=plugins_dir
80
- )
81
- logger.info(f"Cloned repository {repo_name} from {repo_url} at {tag}")
307
+
308
+ # If it's a default branch, just clone it directly
309
+ if is_default_branch:
310
+ try:
311
+ # Try to clone with the specified branch
312
+ subprocess.check_call(
313
+ ["git", "clone", "--branch", ref_value, repo_url],
314
+ cwd=plugins_dir,
315
+ )
316
+ if ref_type == "branch":
317
+ logger.info(
318
+ f"Cloned repository {repo_name} from {repo_url} at branch {ref_value}"
319
+ )
320
+ else:
321
+ logger.info(
322
+ f"Cloned repository {repo_name} from {repo_url} at tag {ref_value}"
323
+ )
324
+ return True
325
+ except subprocess.CalledProcessError:
326
+ # If that fails, try the other default branch
327
+ other_default = "main" if ref_value == "master" else "master"
328
+ try:
329
+ logger.warning(
330
+ f"Could not clone with branch {ref_value}, trying {other_default}"
331
+ )
332
+ subprocess.check_call(
333
+ ["git", "clone", "--branch", other_default, repo_url],
334
+ cwd=plugins_dir,
335
+ )
336
+ logger.info(
337
+ f"Cloned repository {repo_name} from {repo_url} at branch {other_default}"
338
+ )
339
+ return True
340
+ except subprocess.CalledProcessError:
341
+ # If that fails too, clone without specifying a branch
342
+ logger.warning(
343
+ f"Could not clone with branch {other_default}, cloning default branch"
344
+ )
345
+ subprocess.check_call(
346
+ ["git", "clone", repo_url], cwd=plugins_dir
347
+ )
348
+ logger.info(
349
+ f"Cloned repository {repo_name} from {repo_url} (default branch)"
350
+ )
351
+ return True
352
+ else:
353
+ # It's a tag, try to clone with the tag
354
+ try:
355
+ # Try to clone with the specified tag
356
+ subprocess.check_call(
357
+ ["git", "clone", "--branch", ref_value, repo_url],
358
+ cwd=plugins_dir,
359
+ )
360
+ if ref_type == "branch":
361
+ logger.info(
362
+ f"Cloned repository {repo_name} from {repo_url} at branch {ref_value}"
363
+ )
364
+ else:
365
+ logger.info(
366
+ f"Cloned repository {repo_name} from {repo_url} at tag {ref_value}"
367
+ )
368
+ return True
369
+ except subprocess.CalledProcessError:
370
+ # If that fails, clone without specifying a tag
371
+ logger.warning(
372
+ f"Could not clone with tag {ref_value}, cloning default branch"
373
+ )
374
+ subprocess.check_call(["git", "clone", repo_url], cwd=plugins_dir)
375
+
376
+ # Then try to fetch and checkout the tag
377
+ try:
378
+ # Try to fetch the tag
379
+ try:
380
+ subprocess.check_call(
381
+ [
382
+ "git",
383
+ "-C",
384
+ repo_path,
385
+ "fetch",
386
+ "origin",
387
+ f"refs/tags/{ref_value}",
388
+ ]
389
+ )
390
+ except subprocess.CalledProcessError:
391
+ # If that fails, try to fetch the tag without the refs/tags/ prefix
392
+ subprocess.check_call(
393
+ [
394
+ "git",
395
+ "-C",
396
+ repo_path,
397
+ "fetch",
398
+ "origin",
399
+ f"refs/tags/{ref_value}:refs/tags/{ref_value}",
400
+ ]
401
+ )
402
+
403
+ # Now checkout the tag
404
+ subprocess.check_call(
405
+ ["git", "-C", repo_path, "checkout", ref_value]
406
+ )
407
+ if ref_type == "branch":
408
+ logger.info(
409
+ f"Cloned repository {repo_name} and checked out branch {ref_value}"
410
+ )
411
+ else:
412
+ logger.info(
413
+ f"Cloned repository {repo_name} and checked out tag {ref_value}"
414
+ )
415
+ return True
416
+ except subprocess.CalledProcessError:
417
+ # If that fails, try as a branch
418
+ try:
419
+ logger.warning(
420
+ f"Could not checkout {ref_value} as a tag, trying as a branch"
421
+ )
422
+ subprocess.check_call(
423
+ ["git", "-C", repo_path, "fetch", "origin", ref_value]
424
+ )
425
+ subprocess.check_call(
426
+ ["git", "-C", repo_path, "checkout", ref_value]
427
+ )
428
+ logger.info(
429
+ f"Cloned repository {repo_name} and checked out branch {ref_value}"
430
+ )
431
+ return True
432
+ except subprocess.CalledProcessError:
433
+ logger.warning(
434
+ f"Could not checkout {ref_value}, using default branch"
435
+ )
436
+ logger.info(
437
+ f"Cloned repository {repo_name} from {repo_url} (default branch)"
438
+ )
439
+ return True
82
440
  except subprocess.CalledProcessError as e:
83
441
  logger.error(f"Error cloning repository {repo_name}: {e}")
84
442
  logger.error(
85
443
  f"Please manually git clone the repository {repo_url} into {repo_path}"
86
444
  )
87
- sys.exit(1)
445
+ return False
88
446
  # Install requirements if requirements.txt exists
89
447
  requirements_path = os.path.join(repo_path, "requirements.txt")
90
448
  if os.path.isfile(requirements_path):
91
449
  try:
92
- # Use pip to install the requirements.txt
93
- subprocess.check_call(
94
- [sys.executable, "-m", "pip", "install", "-r", requirements_path]
95
- )
96
- logger.info(f"Installed requirements for plugin {repo_name}")
450
+ # Check if we're running in a pipx environment
451
+ in_pipx = "PIPX_HOME" in os.environ or "PIPX_LOCAL_VENVS" in os.environ
452
+
453
+ # Read requirements from file
454
+ with open(requirements_path, "r") as f:
455
+ requirements = [
456
+ line.strip()
457
+ for line in f
458
+ if line.strip() and not line.startswith("#")
459
+ ]
460
+
461
+ if requirements:
462
+ if in_pipx:
463
+ # Use pipx inject for each requirement
464
+ logger.info(
465
+ f"Installing requirements for plugin {repo_name} with pipx inject"
466
+ )
467
+ for req in requirements:
468
+ logger.info(f"Installing {req}")
469
+ subprocess.check_call(["pipx", "inject", "mmrelay", req])
470
+ else:
471
+ # Use pip to install the requirements.txt
472
+ logger.info(
473
+ f"Installing requirements for plugin {repo_name} with pip"
474
+ )
475
+ subprocess.check_call(
476
+ [
477
+ sys.executable,
478
+ "-m",
479
+ "pip",
480
+ "install",
481
+ "-r",
482
+ requirements_path,
483
+ ]
484
+ )
485
+ logger.info(
486
+ f"Successfully installed requirements for plugin {repo_name}"
487
+ )
97
488
  except subprocess.CalledProcessError as e:
98
489
  logger.error(f"Error installing requirements for plugin {repo_name}: {e}")
99
490
  logger.error(
100
491
  f"Please manually install the requirements from {requirements_path}"
101
492
  )
102
- sys.exit(1)
493
+ # Don't exit, just continue with a warning
494
+ logger.warning(
495
+ f"Plugin {repo_name} may not work correctly without its dependencies"
496
+ )
103
497
 
104
498
 
105
499
  def load_plugins_from_directory(directory, recursive=False):
@@ -117,14 +511,101 @@ def load_plugins_from_directory(directory, recursive=False):
117
511
  module_name, plugin_path
118
512
  )
119
513
  plugin_module = importlib.util.module_from_spec(spec)
514
+
515
+ # Create a compatibility layer for plugins
516
+ # This allows plugins to import from 'plugins' or 'mmrelay.plugins'
517
+ if "mmrelay.plugins" not in sys.modules:
518
+ import mmrelay.plugins
519
+
520
+ sys.modules["mmrelay.plugins"] = mmrelay.plugins
521
+
522
+ # For backward compatibility with older plugins
523
+ if "plugins" not in sys.modules:
524
+ import mmrelay.plugins
525
+
526
+ sys.modules["plugins"] = mmrelay.plugins
527
+
120
528
  try:
529
+ # Add the plugin's directory to sys.path temporarily
530
+ plugin_dir = os.path.dirname(plugin_path)
531
+ sys.path.insert(0, plugin_dir)
532
+
533
+ # Execute the module
121
534
  spec.loader.exec_module(plugin_module)
535
+
536
+ # Remove the plugin directory from sys.path
537
+ if plugin_dir in sys.path:
538
+ sys.path.remove(plugin_dir)
539
+
122
540
  if hasattr(plugin_module, "Plugin"):
123
541
  plugins.append(plugin_module.Plugin())
124
542
  else:
125
543
  logger.warning(
126
544
  f"{plugin_path} does not define a Plugin class."
127
545
  )
546
+ except ModuleNotFoundError as e:
547
+ missing_module = str(e).split()[-1].strip("'")
548
+ logger.warning(
549
+ f"Missing dependency for plugin {plugin_path}: {missing_module}"
550
+ )
551
+
552
+ # Try to automatically install the missing dependency
553
+ try:
554
+ # Check if we're running in a pipx environment
555
+ in_pipx = (
556
+ "PIPX_HOME" in os.environ
557
+ or "PIPX_LOCAL_VENVS" in os.environ
558
+ )
559
+
560
+ if in_pipx:
561
+ logger.info(
562
+ f"Attempting to install missing dependency with pipx inject: {missing_module}"
563
+ )
564
+ subprocess.check_call(
565
+ ["pipx", "inject", "mmrelay", missing_module]
566
+ )
567
+ else:
568
+ logger.info(
569
+ f"Attempting to install missing dependency with pip: {missing_module}"
570
+ )
571
+ subprocess.check_call(
572
+ [
573
+ sys.executable,
574
+ "-m",
575
+ "pip",
576
+ "install",
577
+ missing_module,
578
+ ]
579
+ )
580
+
581
+ logger.info(
582
+ f"Successfully installed {missing_module}, retrying plugin load"
583
+ )
584
+
585
+ # Try to load the module again
586
+ spec.loader.exec_module(plugin_module)
587
+
588
+ if hasattr(plugin_module, "Plugin"):
589
+ plugins.append(plugin_module.Plugin())
590
+ else:
591
+ logger.warning(
592
+ f"{plugin_path} does not define a Plugin class."
593
+ )
594
+
595
+ except subprocess.CalledProcessError:
596
+ logger.error(
597
+ f"Failed to automatically install {missing_module}"
598
+ )
599
+ logger.error("Please install it manually:")
600
+ logger.error(
601
+ f"pipx inject mmrelay {missing_module} # if using pipx"
602
+ )
603
+ logger.error(
604
+ f"pip install {missing_module} # if using pip"
605
+ )
606
+ logger.error(
607
+ f"Plugin directory: {os.path.dirname(plugin_path)}"
608
+ )
128
609
  except Exception as e:
129
610
  logger.error(f"Error loading plugin {plugin_path}: {e}")
130
611
  if not recursive:
@@ -223,7 +704,7 @@ def load_plugins(passed_config=None):
223
704
 
224
705
  # Get the first directory for cloning (prefer user directory)
225
706
  community_plugins_dir = community_plugin_dirs[
226
- -1
707
+ 0
227
708
  ] # Use the user directory for new clones
228
709
 
229
710
  # Create community plugins directory if needed
@@ -252,14 +733,37 @@ def load_plugins(passed_config=None):
252
733
  continue
253
734
 
254
735
  repo_url = plugin_info.get("repository")
255
- tag = plugin_info.get("tag", "master")
736
+
737
+ # Support both tag and branch parameters
738
+ tag = plugin_info.get("tag")
739
+ branch = plugin_info.get("branch")
740
+
741
+ # Determine what to use (tag, branch, or default)
742
+ if tag and branch:
743
+ logger.warning(
744
+ f"Both tag and branch specified for plugin {plugin_name}, using tag"
745
+ )
746
+ ref = {"type": "tag", "value": tag}
747
+ elif tag:
748
+ ref = {"type": "tag", "value": tag}
749
+ elif branch:
750
+ ref = {"type": "branch", "value": branch}
751
+ else:
752
+ # Default to main branch if neither is specified
753
+ ref = {"type": "branch", "value": "main"}
754
+
256
755
  if repo_url:
257
756
  # Clone to the user directory by default
258
- clone_or_update_repo(repo_url, tag, community_plugins_dir)
757
+ success = clone_or_update_repo(repo_url, ref, community_plugins_dir)
758
+ if not success:
759
+ logger.warning(
760
+ f"Failed to clone/update plugin {plugin_name}, skipping"
761
+ )
762
+ continue
259
763
  else:
260
764
  logger.error("Repository URL not specified for a community plugin")
261
765
  logger.error("Please specify the repository URL in config.yaml")
262
- sys.exit(1)
766
+ continue
263
767
 
264
768
  # Only load community plugins that are explicitly enabled
265
769
  for plugin_name in active_community_plugins:
@@ -274,7 +778,7 @@ def load_plugins(passed_config=None):
274
778
  for dir_path in community_plugin_dirs:
275
779
  plugin_path = os.path.join(dir_path, repo_name)
276
780
  if os.path.exists(plugin_path):
277
- logger.debug(f"Loading community plugin from: {plugin_path}")
781
+ logger.info(f"Loading community plugin from: {plugin_path}")
278
782
  plugins.extend(
279
783
  load_plugins_from_directory(plugin_path, recursive=True)
280
784
  )
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import threading
2
3
  import time
3
4
  from abc import ABC, abstractmethod
@@ -5,6 +6,7 @@ from abc import ABC, abstractmethod
5
6
  import markdown
6
7
  import schedule
7
8
 
9
+ from mmrelay.config import get_plugin_data_dir
8
10
  from mmrelay.db_utils import (
9
11
  delete_plugin_data,
10
12
  get_plugin_data,
@@ -195,6 +197,33 @@ class BasePlugin(ABC):
195
197
  def get_data(self):
196
198
  return get_plugin_data(self.plugin_name)
197
199
 
200
+ def get_plugin_data_dir(self, subdir=None):
201
+ """
202
+ Returns the directory for storing plugin-specific data files.
203
+ Creates the directory if it doesn't exist.
204
+
205
+ Args:
206
+ subdir (str, optional): Optional subdirectory within the plugin's data directory.
207
+ If provided, this subdirectory will be created.
208
+
209
+ Returns:
210
+ str: Path to the plugin's data directory or subdirectory
211
+
212
+ Example:
213
+ self.get_plugin_data_dir() returns ~/.mmrelay/data/plugins/your_plugin_name/
214
+ self.get_plugin_data_dir("data_files") returns ~/.mmrelay/data/plugins/your_plugin_name/data_files/
215
+ """
216
+ # Get the plugin-specific data directory
217
+ plugin_dir = get_plugin_data_dir(self.plugin_name)
218
+
219
+ # If a subdirectory is specified, create and return it
220
+ if subdir:
221
+ subdir_path = os.path.join(plugin_dir, subdir)
222
+ os.makedirs(subdir_path, exist_ok=True)
223
+ return subdir_path
224
+
225
+ return plugin_dir
226
+
198
227
  def matches(self, event):
199
228
  from mmrelay.matrix_utils import bot_command
200
229
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmrelay
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
5
5
  Home-page: https://github.com/geoffwhittington/meshtastic-matrix-relay
6
6
  Author: Geoff Whittington, Jeremiah K., and contributors
@@ -1,16 +1,16 @@
1
1
  mmrelay/__init__.py,sha256=8fghAZAeSUb8uIaiit0FGNUWI0ZsBR__QbXNwqNbGtw,588
2
2
  mmrelay/cli.py,sha256=yBYOkCwGYDJ2gRKPoPltxB_PydX1CPNUerVJHcNuHTo,12355
3
- mmrelay/config.py,sha256=OIDxQStuVHyWxsDfnKqt0rr26ETEoZPqHVXReEH3IG8,7006
3
+ mmrelay/config.py,sha256=5VZag8iSc5yLQgvwI76bbpizbtqag74cHnfXCrWHNyA,7910
4
4
  mmrelay/config_checker.py,sha256=UnoHVTXzfdTfFkbmXv9r_Si76v-sxXLb5FOaQSOM45E,4909
5
5
  mmrelay/db_utils.py,sha256=DP2YuKBZtV771wo9X-Z7Ww5txfaIR0inWh1K_oVZ7cA,11430
6
6
  mmrelay/log_utils.py,sha256=FXhaq4WSDHwlqiG3k1BbSxiOl5be4P4Kyr1gsIThOBw,4572
7
7
  mmrelay/main.py,sha256=acgBF-DnL0Rs8MmYAfbNHZ9xjqM3Qc0_oKtATN4iOEE,11085
8
8
  mmrelay/matrix_utils.py,sha256=GkIVj2bbPHtx1emFMwhEhc1SWHcv4UvkuyZYdb-Wnwo,30511
9
9
  mmrelay/meshtastic_utils.py,sha256=Pd0j7mz008ncBDIjRGyHOIb0U3vKq06uXWoJP-csHcQ,23381
10
- mmrelay/plugin_loader.py,sha256=QfSXu4nxxBL_pGttLJGzGki8kRtWbFdtSoD_Qnx2iqU,12758
10
+ mmrelay/plugin_loader.py,sha256=Gg8E_TKqndMQHiVJwtmlIC-jCgcty-5EsX_5JJ6Onvo,36590
11
11
  mmrelay/setup_utils.py,sha256=GHttNLoqPwOpjSly0osaCnotPxCq0hfyopsZ2qq313A,14091
12
12
  mmrelay/plugins/__init__.py,sha256=KVMQIXRhe0wlGj4O3IZ0vOIQRKFkfPYejHXhJL17qrc,51
13
- mmrelay/plugins/base_plugin.py,sha256=7tBIARWp9Qc4iAEMcsKiUJh1cbqXHcXAk4dlu1b8SBg,7524
13
+ mmrelay/plugins/base_plugin.py,sha256=hv21tSEYG-AB36aLAFdW9DDKm0NOTRNPpGIO5F3i1ts,8633
14
14
  mmrelay/plugins/debug_plugin.py,sha256=Jziht9Nj_bRO6Rmy7TjfBXaYo5eM3XsenbWFxPpyUs4,443
15
15
  mmrelay/plugins/drop_plugin.py,sha256=ACchX6GfEldqTyvsZVg-5jwbe2-CjENQVVZFnw8SaEM,4618
16
16
  mmrelay/plugins/health_plugin.py,sha256=svV_GfpAVL0QhiVzi3PVZ1mNpsOL1NHSmkRF-Mn_ExE,2250
@@ -21,9 +21,9 @@ mmrelay/plugins/nodes_plugin.py,sha256=RDabzyG5hKG5aYWecsRUcLSjMCCv6Pngmq2Qpld1A
21
21
  mmrelay/plugins/ping_plugin.py,sha256=RTRdgDQUSO33lreDTmWsTlI0L1C3FJrXE0KYqfEWYO0,4017
22
22
  mmrelay/plugins/telemetry_plugin.py,sha256=8SxWv4BLXMUTbiVaD3MjlMMdQyS7S_1OfLlVNAUMSO0,6306
23
23
  mmrelay/plugins/weather_plugin.py,sha256=yoKA_HdFqFEhgYdXqLhvXatLphCyLJFuGUKCR7fILv0,8546
24
- mmrelay-1.0.3.dist-info/licenses/LICENSE,sha256=yceWauM1c0-FHxVplsD7W1-AbSeRaUNlmqT4UO1msBU,1073
25
- mmrelay-1.0.3.dist-info/METADATA,sha256=u2oPLnidTXciajKZyZuk28FaNTYL-Z_JygobFBJwm3I,6953
26
- mmrelay-1.0.3.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
27
- mmrelay-1.0.3.dist-info/entry_points.txt,sha256=SJZwGUOEpQ-qx4H8UL4xKFnKeInGUaZNW1I0ddjK7Ws,45
28
- mmrelay-1.0.3.dist-info/top_level.txt,sha256=B_ZLCRm7NYAmI3PipRUyHGymP-C-q16LSeMGzmqJfo4,8
29
- mmrelay-1.0.3.dist-info/RECORD,,
24
+ mmrelay-1.0.5.dist-info/licenses/LICENSE,sha256=yceWauM1c0-FHxVplsD7W1-AbSeRaUNlmqT4UO1msBU,1073
25
+ mmrelay-1.0.5.dist-info/METADATA,sha256=IrC7LB07B_2BqV3oTs-Epo3veb7XYlXE8H1aeD1WMQw,6953
26
+ mmrelay-1.0.5.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
27
+ mmrelay-1.0.5.dist-info/entry_points.txt,sha256=SJZwGUOEpQ-qx4H8UL4xKFnKeInGUaZNW1I0ddjK7Ws,45
28
+ mmrelay-1.0.5.dist-info/top_level.txt,sha256=B_ZLCRm7NYAmI3PipRUyHGymP-C-q16LSeMGzmqJfo4,8
29
+ mmrelay-1.0.5.dist-info/RECORD,,