From acc6061c623de695e8172369015760fde5b0b84c Mon Sep 17 00:00:00 2001 From: John Burwell Date: Thu, 27 Apr 2023 15:06:55 -0500 Subject: [PATCH] move all commands to plugins --- rsbbs/__init__.py | 10 +- rsbbs/commands.py | 111 ------------------- rsbbs/console.py | 191 +++++++------------------------- rsbbs/controller.py | 11 -- rsbbs/parser.py | 25 +---- rsbbs/pluginloader.py | 66 +++++++++++ rsbbs/plugins/bye/plugin.py | 41 +++++++ rsbbs/plugins/delete/plugin.py | 47 ++++++++ rsbbs/plugins/deletem/plugin.py | 54 +++++++++ rsbbs/plugins/heard/plugin.py | 54 +++++++++ rsbbs/plugins/help/plugin.py | 42 +++++++ rsbbs/plugins/list/plugin.py | 41 +++++++ rsbbs/plugins/listm/plugin.py | 43 +++++++ rsbbs/plugins/read/plugin.py | 54 +++++++++ rsbbs/plugins/readm/plugin.py | 52 +++++++++ rsbbs/plugins/send/plugin.py | 63 +++++++++++ rsbbs/plugins/sendp/plugin.py | 63 +++++++++++ rsbbs/rsbbs.py | 3 +- 18 files changed, 679 insertions(+), 292 deletions(-) delete mode 100644 rsbbs/commands.py create mode 100644 rsbbs/pluginloader.py create mode 100644 rsbbs/plugins/bye/plugin.py create mode 100644 rsbbs/plugins/delete/plugin.py create mode 100644 rsbbs/plugins/deletem/plugin.py create mode 100644 rsbbs/plugins/heard/plugin.py create mode 100644 rsbbs/plugins/help/plugin.py create mode 100644 rsbbs/plugins/list/plugin.py create mode 100644 rsbbs/plugins/listm/plugin.py create mode 100644 rsbbs/plugins/read/plugin.py create mode 100644 rsbbs/plugins/readm/plugin.py create mode 100644 rsbbs/plugins/send/plugin.py create mode 100644 rsbbs/plugins/sendp/plugin.py 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