diff --git a/rsbbs/__init__.py b/rsbbs/__init__.py
index a7afe4c..b43b2ca 100644
--- a/rsbbs/__init__.py
+++ b/rsbbs/__init__.py
@@ -16,5 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-__all__ = ["rsbbs", "bbs", "message", "parser"]
+__all__ = ['config', 'console',
+ 'controller', 'parser', 'pluginloader']
+
__version__ = "0.2.0"
+
+# from .config import Config
+# from .console import Console
+# from .controller import Controller
+# from .parser import Parser
+# from .pluginloader import PluginLoader
diff --git a/rsbbs/commands.py b/rsbbs/commands.py
deleted file mode 100644
index 5f43d93..0000000
--- a/rsbbs/commands.py
+++ /dev/null
@@ -1,111 +0,0 @@
-#!/usr/bin/env python
-#
-# Really Simple BBS - a really simple BBS for ax.25 packet radio.
-# Copyright (C) 2023 John Burwell
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-
-class Commands():
-
- def __init__(self, responder):
- self.responder = responder
-
- @property
- def commands(self):
- commands = [
- # (name,
- # aliases,
- # helpmsg,
- # function,
- # arg:
- # arg attributes...,
- # )
- ('bye',
- ['b', 'q'],
- 'Sign off and disconnect',
- self.responder.bye,
- {}),
-
- ('delete',
- ['d', 'k'],
- 'Delete a message',
- self.responder.delete,
- {'number':
- {'help':
- 'The numeric index of the message to delete'}},),
-
- ('deletem',
- ['dm', 'km'],
- 'Delete all your messages',
- self.responder.delete_mine,
- {}),
-
- ('help',
- ['h', '?'],
- 'Show help',
- self.responder.help,
- {}),
-
- ('heard',
- ['j'],
- 'Show heard stations log',
- self.responder.heard,
- {}),
-
- ('list',
- ['l'],
- 'List all messages',
- self.responder.list,
- {}),
-
- ('listm',
- ['lm'],
- 'List only messages addressed to you',
- self.responder.list_mine,
- {}),
-
- ('read',
- ['r'],
- 'Read messages',
- self.responder.read,
- {'number': {'help': 'Message number to read'}}),
-
- ('readm',
- ['rm'],
- 'Read only messages addressed to you',
- self.responder.read_mine,
- {}),
-
- ('send',
- ['s'],
- 'Send a new message to a user',
- self.responder.send,
- {
- '--callsign': {'help': 'Message recipient callsign'},
- '--subject': {'help': 'Message subject'},
- '--message': {'help': 'Message'},
- },),
-
- ('sendp',
- ['sp'],
- 'Send a private message to a user',
- self.responder.send_private,
- {
- '--callsign': {'help': 'Message recipient callsign'},
- '--subject': {'help': 'Message subject'},
- '--message': {'help': 'Message'},
- },),]
-
- return commands
diff --git a/rsbbs/console.py b/rsbbs/console.py
index f2dde08..9e4644d 100644
--- a/rsbbs/console.py
+++ b/rsbbs/console.py
@@ -16,15 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import os
import sys
import sqlalchemy.exc
import rsbbs
-from rsbbs.commands import Commands
from rsbbs.config import Config
from rsbbs.controller import Controller
from rsbbs.parser import Parser
+from rsbbs.pluginloader import PluginLoader
# Main UI console class
@@ -35,33 +36,42 @@ class Console():
self.config = config
self.controller = controller
- self.commands = Commands(self)
- self.parser = Parser(self.commands)
+ self.parser = Parser()
+
+ self.pluginloader = PluginLoader(self)
+ self.pluginloader.load_plugins()
#
# Input and output
#
- def _read_line(self, prompt):
+ def read_enter(self, prompt) -> None:
+ """Wait for the user to press enter.
+ """
+ if prompt:
+ self.write_output(prompt)
+ sys.stdin.readline().strip()
+
+ def read_line(self, prompt) -> str:
"""Read a single line of input, with an optional prompt,
until we get something.
"""
output = None
while not output:
if prompt:
- self._write_output(prompt)
+ self.write_output(prompt)
input = sys.stdin.readline().strip()
if input != "":
output = input
return output
- def _read_multiline(self, prompt):
+ def read_multiline(self, prompt) -> str:
"""Read multiple lines of input, with an optional prompt,
until the user enters '/ex' by itself on a line.
"""
output = []
if prompt:
- self._write_output(prompt)
+ self.write_output(prompt)
while True:
line = sys.stdin.readline()
if line.lower().strip() == "/ex":
@@ -70,14 +80,14 @@ class Console():
output.append(line)
return ''.join(output)
- def _write_output(self, output):
+ def write_output(self, output) -> None:
"""Write something to stdout."""
sys.stdout.write(output + '\r\n')
- def print_configuration(self):
- self._write_output(repr(self.config))
+ def print_configuration(self) -> None:
+ self.write_output(repr(self.config))
- def print_greeting(self):
+ def print_greeting(self) -> None:
# Show greeting
greeting = []
greeting.append(f"[RSBBS-{rsbbs.__version__}] listening on "
@@ -90,7 +100,7 @@ class Console():
greeting.append("For help, enter 'h'")
- self._write_output('\r\n'.join(greeting))
+ self.write_output('\r\n'.join(greeting))
def print_message(self, message):
"""Print an individual message."""
@@ -98,151 +108,36 @@ class Console():
datetime = message.Message.datetime.strftime(
'%A, %B %-d, %Y at %-H:%M %p UTC')
# Print the message
- self._write_output(f"")
- self._write_output(f"Message: {message.Message.id}")
- self._write_output(f"Date: {datetime}")
- self._write_output(f"From: {message.Message.sender}")
- self._write_output(f"To: {message.Message.recipient}")
- self._write_output(f"Subject: {message.Message.subject}")
- self._write_output(f"")
- self._write_output(f"{message.Message.message}")
+ self.write_output(f"")
+ self.write_output(f"Message: {message.Message.id}")
+ self.write_output(f"Date: {datetime}")
+ self.write_output(f"From: {message.Message.sender}")
+ self.write_output(f"To: {message.Message.recipient}")
+ self.write_output(f"Subject: {message.Message.subject}")
+ self.write_output(f"")
+ self.write_output(f"{message.Message.message}")
- def print_message_list(self, messages):
+ def print_message_list(self, messages) -> None:
"""Print a list of messages."""
# Print the column headers
- self._write_output(f"{'MSG#': <{5}} "
- f"{'TO': <{9}} "
- f"{'FROM': <{9}} "
- f"{'DATE': <{11}} "
- f"SUBJECT")
+ self.write_output(f"{'MSG#': <{5}} "
+ f"{'TO': <{9}} "
+ f"{'FROM': <{9}} "
+ f"{'DATE': <{11}} "
+ f"SUBJECT")
# Print the messages
for message in messages:
datetime_ = message.Message.datetime.strftime('%Y-%m-%d')
- self._write_output(f"{message.Message.id: <{5}} "
- f"{message.Message.recipient: <{9}} "
- f"{message.Message.sender: <{9}} "
- f"{datetime_: <{11}} "
- f"{message.Message.subject}")
+ self.write_output(f"{message.Message.id: <{5}} "
+ f"{message.Message.recipient: <{9}} "
+ f"{message.Message.sender: <{9}} "
+ f"{datetime_: <{11}} "
+ f"{message.Message.subject}")
#
# Command functions
#
- def bye(self, args):
- """Disconnect and exit."""
- self._write_output("Bye!")
- exit(0)
-
- def delete(self, args):
- """Delete a message specified by ID number."""
- if args.number:
- try:
- self.controller.delete(args)
- self._write_output(f"Deleted message #{args.number}")
- except Exception as e:
- self._write_output(f"Message not found.")
-
- def delete_mine(self, args):
- """Delete all messages addressed to the calling station's callsign."""
- self._write_output("Delete all messages addressed to you? Y/N:")
- response = sys.stdin.readline().strip()
- if response.lower() != "y":
- return
- else:
- try:
- result = self.controller.delete_mine(args)
- messages = result.all()
- count = len(messages)
- if count > 0:
- self._write_output(f"Deleted {count} messages")
- else:
- self._write_output(f"No messages to delete.")
- except Exception as e:
- self._write_output(f"Unable to delete messages: {e}")
-
- def heard(self, args):
- """Show a log of stations that have been heard by this station,
- also known as the 'mheard' (linux) or 'jheard' (KPC, etc.) log.
- """
- self._write_output(f"Heard stations:")
- try:
- result = self.controller.heard(args)
- self._write_output(result.stdout)
- except FileNotFoundError:
- self._write_output(f"mheard utility not found.")
- except Exception as e:
- if self.config.debug:
- raise
- else:
- self._write_output(f"Heard stations not available.")
-
- def help(self, args):
- self.parser.print_help()
-
- def list(self, args):
- """List all public messages and private messages to the caller."""
- result = self.controller.list(args)
- self.print_message_list(result)
-
- def list_mine(self, args):
- """List only messages addressed to the calling station's callsign,
- including public and private messages.
- """
- result = self.controller.list_mine(args)
- self.print_message_list(result)
-
- def read(self, args):
- """Read a message.
-
- Arguments:
- number -- the message number to read
- """
- if args.number:
- try:
- result = self.controller.read(args)
- self.print_message(result)
- except sqlalchemy.exc.NoResultFound:
- self._write_output(f"Message not found.")
- except Exception as e:
- print(e)
-
- def read_mine(self, args):
- """Read all messages addressed to the calling station's callsign,
- in sequence."""
- result = self.controller.list_mine(args)
- messages = result.all()
- count = len(messages)
- if count > 0:
- self._write_output(f"Reading {count} messages:")
- for message in messages:
- self.print_message(message)
- self._write_output("Enter to continue...")
- sys.stdin.readline()
- else:
- self._write_output(f"No messages to read.")
-
- def send(self, args, is_private=False):
- """Create a new message addressed to another callsign.
-
- Required arguments:
- callsign -- the recipient's callsign
-
- Optional arguments:
- subject -- message subject
- message -- the message itself
- """
- if not args.callsign:
- args.callsign = self._read_line("Callsign:")
- if not args.subject:
- args.subject = self._read_line("Subject:")
- if not args.message:
- args.message = self._read_multiline(
- "Message - end with /ex on a single line:")
- try:
- self.controller.send(args, is_private=is_private)
- except Exception as e:
- print(e)
-
def send_private(self, args):
self.send(args, is_private=True)
"""Send a message visible only to the recipient callsign.
@@ -270,7 +165,7 @@ class Console():
self.print_greeting()
# Show initial prompt to the calling user
- self._write_output(self.config.command_prompt)
+ self.write_output(self.config.command_prompt)
# Parse the BBS interactive commands for the rest of time
for line in sys.stdin:
@@ -284,4 +179,4 @@ class Console():
pass
# Show our prompt to the calling user again
- self._write_output(self.config.command_prompt)
+ self.write_output(self.config.command_prompt)
diff --git a/rsbbs/controller.py b/rsbbs/controller.py
index dbcc9d6..be6e663 100644
--- a/rsbbs/controller.py
+++ b/rsbbs/controller.py
@@ -93,17 +93,6 @@ class Controller():
except Exception:
raise
- def heard(self, args):
- """Show a log of stations that have been heard by this station,
- also known as the 'mheard' (linux) or 'jheard' (KPC, etc.) log.
- """
- try:
- return subprocess.run(['mheard'], capture_output=True, text=True)
- except FileNotFoundError:
- raise
- except Exception:
- raise
-
def list(self, args):
"""List all messages."""
with Session(self.engine) as session:
diff --git a/rsbbs/parser.py b/rsbbs/parser.py
index 318bee3..b87199d 100644
--- a/rsbbs/parser.py
+++ b/rsbbs/parser.py
@@ -18,8 +18,6 @@
import argparse
-from rsbbs.commands import Commands
-
class BBSArgumentParser(argparse.ArgumentParser):
# Override the error handler to prevent spewing error cruft over the air
@@ -33,8 +31,8 @@ class BBSArgumentParser(argparse.ArgumentParser):
class Parser(BBSArgumentParser):
- def __init__(self, commands: Commands):
- self._init_parser(commands)
+ def __init__(self):
+ self._init_parser()
# The only thing anyone should ever access from Parser is its parser
# attribute, so let's save everyone a step.
@@ -44,7 +42,7 @@ class Parser(BBSArgumentParser):
except AttributeError:
return getattr(self.parser, attr)
- def _init_parser(self, commands):
+ def _init_parser(self):
# Root parser for BBS commands
self.parser = BBSArgumentParser(
description='BBS Main Menu',
@@ -54,20 +52,9 @@ class Parser(BBSArgumentParser):
)
# We will create a subparser for each individual command
- subparsers = self.parser.add_subparsers(
+ self.subparsers = self.parser.add_subparsers(
title='Commands',
dest='command')
- # Loop through the commands and add each as a subparser
- for name, aliases, help_msg, func, arguments in commands.commands:
- # Add the command attributes
- subparser = subparsers.add_parser(
- name,
- aliases=aliases,
- help=help_msg,
- )
- # Add the command parameters
- for arg_name, options in arguments.items():
- subparser.add_argument(arg_name, **options)
- # Trick to pass a function to call when the command is entered
- subparser.set_defaults(func=func)
+ # Plugins will then add a subparser for each command, so we're done
+ # here
diff --git a/rsbbs/pluginloader.py b/rsbbs/pluginloader.py
new file mode 100644
index 0000000..102089b
--- /dev/null
+++ b/rsbbs/pluginloader.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import importlib
+import os
+
+
+class PluginLoader():
+
+ def __init__(self, api) -> None:
+ self.api = api
+ self.plugins = []
+
+ def load_plugins(self) -> None:
+ # Path to command plugin directory
+ plugins_dir = os.path.join(os.path.dirname(__file__), 'plugins')
+
+ # Discover all subdirectories in the plugins directory
+ plugin_dirs = [d
+ for d in os.listdir(plugins_dir)
+ if os.path.isdir(os.path.join(plugins_dir, d))
+ and not d.startswith('__')]
+
+ if self.api.config.debug:
+ print(f"Plugin dirs: {plugin_dirs}")
+
+ # Loop over each plugin directory
+ for plugin_dir in plugin_dirs:
+ try:
+ # Import the module containing the plugin class
+ if self.api.config.debug:
+ print(f"Import rsbbs.plugins.{plugin_dir}.plugin")
+
+ plugin_module = importlib.import_module(
+ f"rsbbs.plugins.{plugin_dir}.plugin")
+
+ # Get a reference to the plugin class
+ plugin_class = plugin_module.Plugin
+
+ # Initialize an instance of the plugin class, passing api as an
+ # argument
+ plugin = plugin_class(self.api)
+
+ # Add the loaded plugin to the list of plugins
+ self.plugins.append(plugin)
+ except Exception as e:
+ if self.api.config.debug:
+ print(f"{e}")
+ raise
+ else:
+ continue
diff --git a/rsbbs/plugins/bye/plugin.py b/rsbbs/plugins/bye/plugin.py
new file mode 100644
index 0000000..9bc5a41
--- /dev/null
+++ b/rsbbs/plugins/bye/plugin.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='bye',
+ aliases=['b', 'q'],
+ help='Sign off and disconnect')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Disconnect and exit."""
+ self.api.write_output("Bye!")
+ exit(0)
diff --git a/rsbbs/plugins/delete/plugin.py b/rsbbs/plugins/delete/plugin.py
new file mode 100644
index 0000000..1b1e417
--- /dev/null
+++ b/rsbbs/plugins/delete/plugin.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='delete',
+ aliases=['d', 'k'],
+ help='Delete a message')
+ subparser.add_argument('number',
+ help='The number of the message to delete')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Delete a message specified by ID number."""
+ if args.number:
+ try:
+ self.api.controller.delete(args)
+ self.api.write_output(f"Deleted message #{args.number}")
+ except Exception as e:
+ self.api.write_output(f"Message not found.")
diff --git a/rsbbs/plugins/deletem/plugin.py b/rsbbs/plugins/deletem/plugin.py
new file mode 100644
index 0000000..0020a6f
--- /dev/null
+++ b/rsbbs/plugins/deletem/plugin.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='deletem',
+ aliases=['dm', 'km'],
+ help='Delete all messages addressed to you')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Delete all messages addressed to the calling station's callsign."""
+ response = self.api.read_line(
+ "Delete all messages addressed to you? Y/N:")
+ if response.lower() != "y":
+ return
+ else:
+ try:
+ result = self.api.controller.delete_mine(args)
+ messages = result.all()
+ count = len(messages)
+ if count > 0:
+ self.api.write_output(f"Deleted {count} messages")
+ else:
+ self.api.write_output(f"No messages to delete.")
+ except Exception as e:
+ self.api.write_output(f"Unable to delete messages: {e}")
diff --git a/rsbbs/plugins/heard/plugin.py b/rsbbs/plugins/heard/plugin.py
new file mode 100644
index 0000000..f7bf783
--- /dev/null
+++ b/rsbbs/plugins/heard/plugin.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import subprocess
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='heard',
+ aliases=['j'],
+ help='Show heard stations log')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Show a log of stations that have been heard by this station,
+ also known as the 'mheard' (linux) or 'jheard' (KPC, etc.) log.
+ """
+ self.api.write_output(f"Heard stations:")
+ try:
+ result = subprocess.run(['mheard'], capture_output=True, text=True)
+ self.api.write_output(result.stdout)
+ except FileNotFoundError:
+ self.api.write_output(f"mheard utility not found.")
+ except Exception as e:
+ if self.api.config.debug:
+ raise
+ else:
+ self.api.write_output(f"Heard stations not available.")
diff --git a/rsbbs/plugins/help/plugin.py b/rsbbs/plugins/help/plugin.py
new file mode 100644
index 0000000..6974f0d
--- /dev/null
+++ b/rsbbs/plugins/help/plugin.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='help',
+ aliases=['h', '?'],
+ help='Show help')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Show a log of stations that have been heard by this station,
+ also known as the 'mheard' (linux) or 'jheard' (KPC, etc.) log.
+ """
+ self.api.parser.print_help()
diff --git a/rsbbs/plugins/list/plugin.py b/rsbbs/plugins/list/plugin.py
new file mode 100644
index 0000000..44b9901
--- /dev/null
+++ b/rsbbs/plugins/list/plugin.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='list',
+ aliases=['l'],
+ help='List all available messages')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """List all public messages and messages private to the caller."""
+ result = self.api.controller.list(args)
+ self.api.print_message_list(result)
diff --git a/rsbbs/plugins/listm/plugin.py b/rsbbs/plugins/listm/plugin.py
new file mode 100644
index 0000000..321b354
--- /dev/null
+++ b/rsbbs/plugins/listm/plugin.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='listm',
+ aliases=['lm'],
+ help='List only messages addressed to you')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """List only messages addressed to the calling station's callsign,
+ including public and private messages.
+ """
+ result = self.api.controller.list_mine(args)
+ self.api.print_message_list(result)
diff --git a/rsbbs/plugins/read/plugin.py b/rsbbs/plugins/read/plugin.py
new file mode 100644
index 0000000..9d88aaf
--- /dev/null
+++ b/rsbbs/plugins/read/plugin.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import sqlalchemy
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='read',
+ aliases=['r'],
+ help='Read a message')
+ subparser.add_argument('number', help='Message number to read')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Read a message.
+
+ Arguments:
+ number -- the message number to read
+ """
+ if args.number:
+ try:
+ result = self.api.controller.read(args)
+ self.api.print_message(result)
+ except sqlalchemy.exc.NoResultFound:
+ self.api.write_output(f"Message not found.")
+ except Exception as e:
+ print(e)
diff --git a/rsbbs/plugins/readm/plugin.py b/rsbbs/plugins/readm/plugin.py
new file mode 100644
index 0000000..292c9a9
--- /dev/null
+++ b/rsbbs/plugins/readm/plugin.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import sqlalchemy
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='readm',
+ aliases=['rm'],
+ help='Read all messages addressed to you')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Read all messages addressed to the calling station's callsign,
+ in sequence."""
+ result = self.api.controller.list_mine(args)
+ messages = result.all()
+ count = len(messages)
+ if count > 0:
+ self.api.write_output(f"Reading {count} messages:")
+ for message in messages:
+ self.api.print_message(message)
+ self.api.read_enter("Enter to continue...")
+ else:
+ self.api.write_output(f"No messages to read.")
diff --git a/rsbbs/plugins/send/plugin.py b/rsbbs/plugins/send/plugin.py
new file mode 100644
index 0000000..2acd1e3
--- /dev/null
+++ b/rsbbs/plugins/send/plugin.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import sqlalchemy
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='send',
+ aliases=['s'],
+ help='Send a new message to a user')
+ subparser.add_argument('--callsign', help='Message recipient callsign')
+ subparser.add_argument('--subject', help='Message subject')
+ subparser.add_argument('--message', help='Message')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Create a new message addressed to another callsign.
+
+ Required arguments:
+ callsign -- the recipient's callsign
+
+ Optional arguments:
+ subject -- message subject
+ message -- the message itself
+ """
+ if not args.callsign:
+ args.callsign = self.api.read_line("Callsign:")
+ if not args.subject:
+ args.subject = self.api.read_line("Subject:")
+ if not args.message:
+ args.message = self.api.read_multiline(
+ "Message - end with /ex on a single line:")
+ try:
+ self.api.controller.send(args, is_private=False)
+ except Exception as e:
+ print(e)
diff --git a/rsbbs/plugins/sendp/plugin.py b/rsbbs/plugins/sendp/plugin.py
new file mode 100644
index 0000000..f036e61
--- /dev/null
+++ b/rsbbs/plugins/sendp/plugin.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+#
+# Really Simple BBS - a really simple BBS for ax.25 packet radio.
+# Copyright (C) 2023 John Burwell
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import sqlalchemy
+
+from rsbbs.console import Console
+from rsbbs.parser import Parser
+
+
+class Plugin():
+
+ def __init__(self, api: Console):
+ self.api = api
+ self.init_parser(api.parser)
+ if api.config.debug:
+ print(f"Plugin {__name__} loaded")
+
+ def init_parser(self, parser: Parser):
+ subparser = parser.subparsers.add_parser(
+ name='sendp',
+ aliases=['sp'],
+ help='Send a private message to a user')
+ subparser.add_argument('--callsign', help='Message recipient callsign')
+ subparser.add_argument('--subject', help='Message subject')
+ subparser.add_argument('--message', help='Message')
+ subparser.set_defaults(func=self.run)
+
+ def run(self, args):
+ """Create a new message addressed to another callsign.
+
+ Required arguments:
+ callsign -- the recipient's callsign
+
+ Optional arguments:
+ subject -- message subject
+ message -- the message itself
+ """
+ if not args.callsign:
+ args.callsign = self.api.read_line("Callsign:")
+ if not args.subject:
+ args.subject = self.api.read_line("Subject:")
+ if not args.message:
+ args.message = self.api.read_multiline(
+ "Message - end with /ex on a single line:")
+ try:
+ self.api.controller.send(args, is_private=True)
+ except Exception as e:
+ print(e)
diff --git a/rsbbs/rsbbs.py b/rsbbs/rsbbs.py
index 7287e52..3f07baa 100755
--- a/rsbbs/rsbbs.py
+++ b/rsbbs/rsbbs.py
@@ -20,7 +20,6 @@ import argparse
import sys
from rsbbs import __version__
-from rsbbs.commands import Commands
from rsbbs.config import Config
from rsbbs.console import Console
from rsbbs.controller import Controller
@@ -82,7 +81,7 @@ def main():
app_name='rsbbs',
args=argv_args)
- # Init the contoller
+ # Init the controller
controller = Controller(config)
# Init the UI console