From fbfb4067e8ea5360bdfe96284bd0a67b9d9291be Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Mon, 29 Apr 2024 19:05:51 +1000 Subject: [PATCH 01/13] Add message board contrib --- .../game_systems/messageboard/__init__.py | 1 + .../game_systems/messageboard/messageboard.py | 454 ++++++++++++++++++ 2 files changed, 455 insertions(+) create mode 100644 evennia/contrib/game_systems/messageboard/__init__.py create mode 100644 evennia/contrib/game_systems/messageboard/messageboard.py diff --git a/evennia/contrib/game_systems/messageboard/__init__.py b/evennia/contrib/game_systems/messageboard/__init__.py new file mode 100644 index 0000000000..812ae521bd --- /dev/null +++ b/evennia/contrib/game_systems/messageboard/__init__.py @@ -0,0 +1 @@ +from .messageboard import EvMessageBoard, CmdEvMessageBoard diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py new file mode 100644 index 0000000000..ce75a4f008 --- /dev/null +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -0,0 +1,454 @@ +import pytz +from django.conf import settings + +from evennia import AttributeProperty +from evennia.objects.objects import DefaultObject +from evennia.utils.eveditor import EvEditor +from evennia.utils import datetime_format, interactive +from evennia.utils.utils import class_from_module +from evennia.utils.ansi import strip_ansi +from evennia.utils.create import create_message +from evennia.comms.models import Msg + +COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) + + +class EvMessageBoard(DefaultObject): + messages = AttributeProperty(dict, autocreate=False) + message_id = AttributeProperty(0, autocreate=False) + + def at_object_creation(self): + super().at_object_creation() + + self.locks.add("get:false();brdpost:all();brdchange:all();brdmanage:perm(Builder)") + self.tags.add("message_board", "contrib") + self.db.desc = ( + "A board on which messages can be posted. Use the |hboard|n command to " + "read and post to it (if you have the required permissions)." + ) + + def delete(self): + Msg.objects.filter(db_receivers_objects=self).delete() + return super().delete() + + +class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): + """ + Read and post messages on a message board. + + Usage: + board[/switches] [message #] [subject = message] + + Switches: + unread - List only unread messages. + read - Read the oldest unread message or a specific message. + post - Post a new message. + reply - Reply to a message. + change - Change an existing message. This always uses the line editor. + delete - Delete a message. Can be abbreviated to del. + clear - Clear all messages from the board. + edit - Use the line editor to compose the message. + + Examples: + board/unread + board/read + board/read 1 + board/post Welcome = Welcome to the message board! + board/post/edit + board/reply 1 = Hello! + board/reply/edit 1 + board/change 1 + board/delete 1 + board/clear + + With no arguments, all messages on the board will be listed. + """ + + key = "board" + aliases = ["brd"] + switch_options = ("read", "unread", "post", "reply", "change", "delete", "del", "clear", "edit") + + _MAX_SUBJECT_DISPLAY_LENGTH = 40 + + def parse(self): + super().parse() + + def func(self): + boards = [ + obj + for obj in self.caller.location.contents + if obj.tags.has("message_board", category="contrib") + ] + + if len(boards) == 0: + self.caller.msg("There isn't a message board here.") + return + if len(boards) > 1: + self.caller.msg("There is more than one message board here (and there shouldn't be.)") + return + + board = boards[0] + messages = _board_get_messages(board) + use_editor = "edit" in self.switches + + if "read" in self.switches: + if self.args: + message_id = self.args.strip() + if not ( + message := self._get_message(board, message_id, "Usage: board/read [message #]") + ): + return + else: + unread = { + id: message + for id, message in messages.items() + if self.caller not in message["read_by"] + } + if not unread: + self.caller.msg("You have read all the messages on this board.") + return + + message_id = next(iter(unread)) + message = messages[message_id] + + width = 78 + border_col = self.caller.account.options.get("border_color") + separator_char = self.caller.account.options.get("separator_fill") + separator = f"|{border_col}{separator_char * width}|n" + + msg = message["message"] + time_zone = self.caller.account.options.get("timezone") + date_time = self._utc_to_local(msg.date_created, time_zone).strftime( + "%Y-%m-%d %H:%M:%S" + ) + info = ( + f"|{border_col}From:|n " + f"{message['author_name']} |{border_col}@|n " + f"{date_time} |{border_col}[#{message_id}]|n" + ) + + lines = msg.message.split("\n") + subject = f"|{border_col}Subject:|n {lines[0]}" + body = "\n".join(lines[1:]) + + self.caller.msg( + f"{separator}\n" + f"{info}\n" + f"{separator}\n" + f"|h{subject}|n\n\n" + f"{body}\n" + f"{separator}" + ) + message["read_by"].add(self.caller) + + return + + if "post" in self.switches: + if not board.access(self.caller, "brdpost"): + self.caller.msg("You are not allowed to post on this message board.") + return + if use_editor: + self._send_editor_intructions() + self.caller.db.message_board = board + EvEditor( + self.caller, + loadfunc=_board_editor_load, + savefunc=_board_editor_save, + quitfunc=_board_editor_quit, + key="board message", + persistent=True + ) + return + else: + if not (self.lhs and self.rhs): + self.caller.msg("Usage: board/post topic = message") + return + _board_post_message(self.caller, board, self.lhs, self.rhs) + + return + + if "reply" in self.switches: + usage = ( + "Usage: board/reply = \n" + " board/reply/edit " + ) + + if "edit" in self.switches: + if len(self.arglist) != 1 or self.rhs: + self.caller.msg(usage) + return + use_editor = True + else: + if len(self.arglist) < 1 or not self.rhs: + self.caller.msg(usage) + return + use_editor = False + + message_id = self.arglist[0].strip() + if not (message := self._get_message(board, message_id, usage)): + return + + subject = message["message"].message.split("\n")[0] + subject = f"Re: {subject}" + if use_editor: + self._send_editor_intructions() + self.caller.db.message_board = board + self.caller.db.message_board_buf = subject + EvEditor( + self.caller, + loadfunc=_board_editor_load, + savefunc=_board_editor_save, + quitfunc=_board_editor_quit, + key="board message", + persistent=True + ) + return + + _board_post_message(self.caller, board, subject, self.rhs) + return + + if "change" in self.switches: + can_post = board.access(self.caller, "brdpost") + can_change = board.access(self.caller, "brdchange") + can_manage = board.access(self.caller, "brdmanage") + + if not ((can_post and can_change) or can_manage): + self.caller.msg("You are not allowed to change posts on this message board.") + return + + usage = "Usage: board/delete " + message_id = self.args.strip() + if not message_id.isdigit(): + self.caller.msg(usage) + return + + if not (message := self._get_message(board, message_id, usage)): + return + + if not (can_manage or self.caller in message["message"].senders): + self.caller.msg("You may only change your own messages.") + return + + self._send_editor_intructions() + self.caller.db.message_board = board + self.caller.db.message_board_buf = message["message"].message + self.caller.db.message_board_message_id = int(message_id) + EvEditor( + self.caller, + loadfunc=_board_editor_load, + savefunc=_board_editor_save, + quitfunc=_board_editor_quit, + key="board message", + persistent=True + ) + + return + + if "delete" in self.switches or "del" in self.switches: + can_post = board.access(self.caller, "brdpost") + can_change = board.access(self.caller, "brdchange") + can_manage = board.access(self.caller, "brdmanage") + + if not ((can_post and can_change) or can_manage): + self.caller.msg("You are not allowed to delete messages from this board.") + return + + message_id = self.args.strip() + if not message_id.isdigit(): + self.caller.msg("Usage: board/delete ") + return + + if not ( + message := self._get_message(board, message_id, "Usage: board/delete ") + ): + return + + if not (can_manage or self.caller in message["message"].senders): + self.caller.msg("You may only delete your own messages.") + return + + self._delete_message(self.caller, board, int(message_id)) + return + + if "clear" in self.switches: + if not board.access(self.caller, "brdmanage"): + self.caller.msg("You are not allowed to clear this message board.") + return + + self._clear_board(self.caller, board) + return + + # List messages + can_post = ( + "You |gmay post|n on this message board" + if board.access(self.caller, "brdpost") + else "You |rmay not post|n on this message board" + ) + + if messages: + if "unread" in self.switches: + # messages = [message for message in messages if self.caller not in message["read_by"]] + messages = { + message_id: message + for message_id, message in messages.items() + if not self.caller in message["read_by"] + } + if not messages: + self.caller.msg("You have read all the messages on this board.") + return + + table = self.styled_table("#", "From", "Subject", "Posted") + time_zone = self.caller.account.options.get("timezone") + for message_id, message in messages.items(): + unread_mark = "" if self.caller in message["read_by"] else "*" + subject = message["subject"] + time = datetime_format(self._utc_to_local(message["post_date"], time_zone)) + if len(subject) > self._MAX_SUBJECT_DISPLAY_LENGTH: + subject = subject[:self._MAX_SUBJECT_DISPLAY_LENGTH] + "..." + table.add_row(f"{unread_mark}{message_id}", message["author_name"], subject, time) + table.reformat_column(0, align="r") + + string = str(table) + f"\n * Indicates an unread message. {can_post}." + else: + string = f"There are no messages on this board yet. {can_post}." + self.msg(string) + + def _utc_to_local(self, utc_time, time_zone): + if not time_zone: + return utc_time + # don't convert a time that's not UTC + if utc_time.utcoffset().total_seconds() != 0: + return utc_time + + return utc_time.replace(tzinfo=pytz.utc).astimezone(time_zone) + + def _get_message(self, board, message_id, usage): + if not message_id.isdigit(): + self.caller.msg(usage) + return None + + message_id = int(message_id) + if not (message := board.messages.get(message_id)): + self.caller.msg(f"There is no message #{message_id}.") + return None + + return message + + def _send_editor_intructions(self): + string = ( + "|yUse the line editor to enter your message. The first line containing\n" + "text will be used as the subject.\n" + "\n" + "To post the message, save it (:w) then quit (:q). If you have saved a\n" + "message you can cancel posting it by clearing the message (:DD) then\n" + "saving and quitting.|n\n" + ) + self.caller.msg(string) + + @interactive + def _delete_message(self, caller, board, message_id): + message = board.messages[message_id]["message"] + subject = message.message.split("\n")[0] + answer = yield ( + f"Are you sure you want to delete the message '{subject}|n' (#{message_id}) yes/[no]?" + ) + if not answer.lower() in ("yes", "y"): + caller.msg("Cancelled.") + return + + message.delete() + del board.messages[message_id] + caller.msg(f"Message #{message_id} deleted.") + + @interactive + def _clear_board(self, caller, board): + answer = yield ("Are you sure you want to clear all messages from the board yes/[no]?") + if not answer.lower() in ("yes", "y"): + caller.msg("Cancelled.") + return + + Msg.objects.filter(db_receivers_objects=board).delete() + board.messages.clear() + board.message_id = 0 + caller.msg("Message board cleared.") + + +def _board_get_messages(board): + if not (messages := board.db.messages): + board.db.messages = {} + messages = board.db.messages + + return messages + + +def _board_editor_load(caller): + buf = caller.db.message_board_buf or "" + caller.attributes.remove("message_board_buf") + + return buf + + +def _board_editor_save(caller, buf): + if buf: + if len([line for line in buf.split("\n") if line.strip()]) < 2: + caller.msg("|rYour message must contain at least a subject line and a body line.|n") + return False + caller.msg("Message saved for posting.") + else: + caller.msg("|yMessage will not be posted.|n") + + caller.db.message_board_buf = buf + return True + + +def _board_editor_quit(caller): + message = caller.db.message_board_buf + if not message: + caller.msg("Posting cancelled.") + return + + lines = message.strip().split("\n") + subject = lines[0] + body = "\n".join(lines[1:]).strip() + + message_id = caller.db.message_board_message_id + if message_id: + _board_post_message(caller, caller.db.message_board, subject, body, message_id=message_id) + else: + _board_post_message(caller, caller.db.message_board, subject, body) + + caller.attributes.remove("message_board") + caller.attributes.remove("message_board_buf") + caller.attributes.remove("message_board_message_id") + + +def _board_post_message(caller, board, subject, body, message_id=None): + if not caller.permissions.check("Builder"): + subject = strip_ansi(subject) + + if message_id is None: + msg = create_message( + caller, subject + "\n" + body, receivers=board, tags=[("board_message", "comms")] + ) + message = { + "post_date": msg.date_created, + "author_name": caller.key, + "subject": subject, + "message": msg, + "read_by": {caller} + } + + message_id = board.message_id + 1 + board.message_id = message_id + board.messages[message_id] = message + + caller.msg(f"Your message '{subject}|n' has been posted.") + else: + messages = _board_get_messages(board) + if not (message := messages.get(message_id)): + caller.msg(f"Message #{message_id} has been deleted. Change cancelled.") + return + + message["subject"] = subject + message["message"].message = subject + "\n" + body + caller.msg(f"Message #{message_id} has been changed.") From c9a0f32c9daeb715df5872887a11aa4b2d2329f7 Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 00:28:19 +1000 Subject: [PATCH 02/13] Correct board command usage syntax in docstring --- evennia/contrib/game_systems/messageboard/messageboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index ce75a4f008..141d702461 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -37,7 +37,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): Read and post messages on a message board. Usage: - board[/switches] [message #] [subject = message] + board[/switches] [message #] [[subject] = message] Switches: unread - List only unread messages. From f7c9b63346dab03fa0079e8326350eb2d93125e1 Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 11:39:53 +1000 Subject: [PATCH 03/13] Change message board command from 'board' to '@board' --- evennia/contrib/game_systems/messageboard/messageboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 141d702461..efa0da56da 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -64,8 +64,8 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): With no arguments, all messages on the board will be listed. """ - key = "board" - aliases = ["brd"] + key = "@board" + aliases = ["@brd"] switch_options = ("read", "unread", "post", "reply", "change", "delete", "del", "clear", "edit") _MAX_SUBJECT_DISPLAY_LENGTH = 40 From f6544884951ef1b4ecc64fd9bde351bae370d155 Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 11:44:10 +1000 Subject: [PATCH 04/13] Rename message board locks --- .../game_systems/messageboard/messageboard.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index efa0da56da..92d72a0e68 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -20,7 +20,7 @@ class EvMessageBoard(DefaultObject): def at_object_creation(self): super().at_object_creation() - self.locks.add("get:false();brdpost:all();brdchange:all();brdmanage:perm(Builder)") + self.locks.add("get:false();post:all();change:all();manage:perm(Builder)") self.tags.add("message_board", "contrib") self.db.desc = ( "A board on which messages can be posted. Use the |hboard|n command to " @@ -144,7 +144,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): return if "post" in self.switches: - if not board.access(self.caller, "brdpost"): + if not board.access(self.caller, "post"): self.caller.msg("You are not allowed to post on this message board.") return if use_editor: @@ -208,9 +208,9 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): return if "change" in self.switches: - can_post = board.access(self.caller, "brdpost") - can_change = board.access(self.caller, "brdchange") - can_manage = board.access(self.caller, "brdmanage") + can_post = board.access(self.caller, "post") + can_change = board.access(self.caller, "change") + can_manage = board.access(self.caller, "manage") if not ((can_post and can_change) or can_manage): self.caller.msg("You are not allowed to change posts on this message board.") @@ -245,9 +245,9 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): return if "delete" in self.switches or "del" in self.switches: - can_post = board.access(self.caller, "brdpost") - can_change = board.access(self.caller, "brdchange") - can_manage = board.access(self.caller, "brdmanage") + can_post = board.access(self.caller, "post") + can_change = board.access(self.caller, "change") + can_manage = board.access(self.caller, "manage") if not ((can_post and can_change) or can_manage): self.caller.msg("You are not allowed to delete messages from this board.") @@ -271,7 +271,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): return if "clear" in self.switches: - if not board.access(self.caller, "brdmanage"): + if not board.access(self.caller, "manage"): self.caller.msg("You are not allowed to clear this message board.") return @@ -281,7 +281,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): # List messages can_post = ( "You |gmay post|n on this message board" - if board.access(self.caller, "brdpost") + if board.access(self.caller, "post") else "You |rmay not post|n on this message board" ) From 0bf8e0e5d10f25ae36e39a4506e316cf4c6cf2f5 Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 11:45:53 +1000 Subject: [PATCH 05/13] Add line break to message board default description --- evennia/contrib/game_systems/messageboard/messageboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 92d72a0e68..10d707e32b 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -23,7 +23,7 @@ class EvMessageBoard(DefaultObject): self.locks.add("get:false();post:all();change:all();manage:perm(Builder)") self.tags.add("message_board", "contrib") self.db.desc = ( - "A board on which messages can be posted. Use the |hboard|n command to " + "A board on which messages can be posted. Use the |hboard|n command to\n" "read and post to it (if you have the required permissions)." ) From 47eb2317ec1b81e0ce42a86b56b8ac86eb4423fb Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 13:00:49 +1000 Subject: [PATCH 06/13] Add styling support to message board list and post display via overrides --- .../game_systems/messageboard/messageboard.py | 88 +++++++++++++------ 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 10d707e32b..340b4eb51b 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -68,6 +68,16 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): aliases = ["@brd"] switch_options = ("read", "unread", "post", "reply", "change", "delete", "del", "clear", "edit") + post_template = """ +{separator} +|hFrom:|n {author} |h@|n {date_time} [|h#{message_id}|n] +{separator} +|hSubject:|n {subject} + +{body} +{separator} + """ + _MAX_SUBJECT_DISPLAY_LENGTH = 40 def parse(self): @@ -117,27 +127,14 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): separator = f"|{border_col}{separator_char * width}|n" msg = message["message"] - time_zone = self.caller.account.options.get("timezone") - date_time = self._utc_to_local(msg.date_created, time_zone).strftime( - "%Y-%m-%d %H:%M:%S" - ) - info = ( - f"|{border_col}From:|n " - f"{message['author_name']} |{border_col}@|n " - f"{date_time} |{border_col}[#{message_id}]|n" - ) - lines = msg.message.split("\n") subject = f"|{border_col}Subject:|n {lines[0]}" body = "\n".join(lines[1:]) + author = message['author_name'] + date_time = msg.date_created self.caller.msg( - f"{separator}\n" - f"{info}\n" - f"{separator}\n" - f"|h{subject}|n\n\n" - f"{body}\n" - f"{separator}" + self.format_post(message_id, separator, date_time, author, subject, body) ) message["read_by"].add(self.caller) @@ -279,12 +276,6 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): return # List messages - can_post = ( - "You |gmay post|n on this message board" - if board.access(self.caller, "post") - else "You |rmay not post|n on this message board" - ) - if messages: if "unread" in self.switches: # messages = [message for message in messages if self.caller not in message["read_by"]] @@ -297,7 +288,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): self.caller.msg("You have read all the messages on this board.") return - table = self.styled_table("#", "From", "Subject", "Posted") + table = self.create_table() time_zone = self.caller.account.options.get("timezone") for message_id, message in messages.items(): unread_mark = "" if self.caller in message["read_by"] else "*" @@ -305,14 +296,57 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): time = datetime_format(self._utc_to_local(message["post_date"], time_zone)) if len(subject) > self._MAX_SUBJECT_DISPLAY_LENGTH: subject = subject[:self._MAX_SUBJECT_DISPLAY_LENGTH] + "..." - table.add_row(f"{unread_mark}{message_id}", message["author_name"], subject, time) - table.reformat_column(0, align="r") + self.add_table_row( + table, f"{unread_mark}{message_id}", message["author_name"], subject, time + ) - string = str(table) + f"\n * Indicates an unread message. {can_post}." + self.format_table(table) + string = self.get_table_header(board) + str(table) + self.get_table_footer(board) else: - string = f"There are no messages on this board yet. {can_post}." + string = ( + "There are no messages on this board yet." + f" {self.get_can_can_post_info(board)}." + ) self.msg(string) + def format_post(self, message_id, separator, date_time, author, subject, body): + time_zone = self.caller.account.options.get("timezone") + date_time = self._utc_to_local(date_time, time_zone).strftime("%Y-%m-%d %H:%M:%S") + + return self.post_template.format( + separator=separator, + message_id=message_id, + date_time=date_time, + author=author, + subject=subject, + body=body + ).strip() + + def create_table(self): + """ + table must override __str__() + """ + return self.styled_table("#", "From", "Subject", "Posted") + + def add_table_row(self, table, message_id, author, subject, date_time): + table.add_row(message_id, author, subject, date_time) + + def format_table(self, table): + table.reformat_column(0, align="r") + + def get_table_header(self, board): + return "" + + def get_table_footer(self, board): + return f"\n * Indicates an unread message. {self.get_can_can_post_info(board)}." + + def get_can_can_post_info(self, board): + return ( + "You |gmay post|n on this message board" + if board.access(self.caller, "post") + else "You |rmay not post|n on this message board" + ) + def _utc_to_local(self, utc_time, time_zone): if not time_zone: return utc_time From f2eadea319f5e5387a03fa1aae8508ee95b262ad Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 13:29:49 +1000 Subject: [PATCH 07/13] Add support to message board for using a different editor via overrides --- .../game_systems/messageboard/messageboard.py | 94 +++++++++---------- 1 file changed, 43 insertions(+), 51 deletions(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 340b4eb51b..30ebb7a47e 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -12,7 +12,6 @@ from evennia.comms.models import Msg COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) - class EvMessageBoard(DefaultObject): messages = AttributeProperty(dict, autocreate=False) message_id = AttributeProperty(0, autocreate=False) @@ -144,24 +143,16 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): if not board.access(self.caller, "post"): self.caller.msg("You are not allowed to post on this message board.") return - if use_editor: - self._send_editor_intructions() - self.caller.db.message_board = board - EvEditor( - self.caller, - loadfunc=_board_editor_load, - savefunc=_board_editor_save, - quitfunc=_board_editor_quit, - key="board message", - persistent=True - ) - return - else: - if not (self.lhs and self.rhs): - self.caller.msg("Usage: board/post topic = message") - return - _board_post_message(self.caller, board, self.lhs, self.rhs) + if use_editor: + self.start_editor(board) + return + + if not (self.lhs and self.rhs): + self.caller.msg("Usage: board/post topic = message") + return + + board_post_message(self.caller, board, self.lhs, self.rhs) return if "reply" in self.switches: @@ -188,20 +179,10 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): subject = message["message"].message.split("\n")[0] subject = f"Re: {subject}" if use_editor: - self._send_editor_intructions() - self.caller.db.message_board = board - self.caller.db.message_board_buf = subject - EvEditor( - self.caller, - loadfunc=_board_editor_load, - savefunc=_board_editor_save, - quitfunc=_board_editor_quit, - key="board message", - persistent=True - ) + self.start_editor(board, subject=subject) return - _board_post_message(self.caller, board, subject, self.rhs) + board_post_message(self.caller, board, subject, self.rhs) return if "change" in self.switches: @@ -226,19 +207,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): self.caller.msg("You may only change your own messages.") return - self._send_editor_intructions() - self.caller.db.message_board = board - self.caller.db.message_board_buf = message["message"].message - self.caller.db.message_board_message_id = int(message_id) - EvEditor( - self.caller, - loadfunc=_board_editor_load, - savefunc=_board_editor_save, - quitfunc=_board_editor_quit, - key="board message", - persistent=True - ) - + self.start_editor(board, buf=message["message"].message, message_id=int(message_id)) return if "delete" in self.switches or "del" in self.switches: @@ -309,6 +278,33 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): ) self.msg(string) + def start_editor(self, board, subject=None, buf=None, message_id=None): + """ + (WIP) + subject required if replying + buf and message_id (but not subject) required for editing (this will be changed) + + Edit should call the module's + board_post_message(caller, board, subject, body, message_id=None) + """ + self._send_editor_intructions() + self.caller.db.message_board = board + if subject: + self.caller.db.message_board_buf = subject + if buf: + self.caller.db.message_board_buf = buf + if message_id: + self.caller.db.message_board_message_id = message_id + + EvEditor( + self.caller, + loadfunc=_board_editor_load, + savefunc=_board_editor_save, + quitfunc=_board_editor_quit, + key="board message", + persistent=True + ) + def format_post(self, message_id, separator, date_time, author, subject, body): time_zone = self.caller.account.options.get("timezone") date_time = self._utc_to_local(date_time, time_zone).strftime("%Y-%m-%d %H:%M:%S") @@ -324,6 +320,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): def create_table(self): """ + (WIP) table must override __str__() """ return self.styled_table("#", "From", "Subject", "Posted") @@ -406,7 +403,6 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): board.message_id = 0 caller.msg("Message board cleared.") - def _board_get_messages(board): if not (messages := board.db.messages): board.db.messages = {} @@ -414,14 +410,12 @@ def _board_get_messages(board): return messages - def _board_editor_load(caller): buf = caller.db.message_board_buf or "" caller.attributes.remove("message_board_buf") return buf - def _board_editor_save(caller, buf): if buf: if len([line for line in buf.split("\n") if line.strip()]) < 2: @@ -434,7 +428,6 @@ def _board_editor_save(caller, buf): caller.db.message_board_buf = buf return True - def _board_editor_quit(caller): message = caller.db.message_board_buf if not message: @@ -447,16 +440,15 @@ def _board_editor_quit(caller): message_id = caller.db.message_board_message_id if message_id: - _board_post_message(caller, caller.db.message_board, subject, body, message_id=message_id) + board_post_message(caller, caller.db.message_board, subject, body, message_id=message_id) else: - _board_post_message(caller, caller.db.message_board, subject, body) + board_post_message(caller, caller.db.message_board, subject, body) caller.attributes.remove("message_board") caller.attributes.remove("message_board_buf") caller.attributes.remove("message_board_message_id") - -def _board_post_message(caller, board, subject, body, message_id=None): +def board_post_message(caller, board, subject, body, message_id=None): if not caller.permissions.check("Builder"): subject = strip_ansi(subject) From c1d76f9baaacc2040f45add5b9bacd691ec90cfb Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 13:39:21 +1000 Subject: [PATCH 08/13] Run black on message board source --- .../game_systems/messageboard/messageboard.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 30ebb7a47e..b62cf47417 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -12,6 +12,7 @@ from evennia.comms.models import Msg COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) + class EvMessageBoard(DefaultObject): messages = AttributeProperty(dict, autocreate=False) message_id = AttributeProperty(0, autocreate=False) @@ -129,7 +130,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): lines = msg.message.split("\n") subject = f"|{border_col}Subject:|n {lines[0]}" body = "\n".join(lines[1:]) - author = message['author_name'] + author = message["author_name"] date_time = msg.date_created self.caller.msg( @@ -157,8 +158,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): if "reply" in self.switches: usage = ( - "Usage: board/reply = \n" - " board/reply/edit " + f"Usage: board/reply = \n board/reply/edit " ) if "edit" in self.switches: @@ -264,7 +264,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): subject = message["subject"] time = datetime_format(self._utc_to_local(message["post_date"], time_zone)) if len(subject) > self._MAX_SUBJECT_DISPLAY_LENGTH: - subject = subject[:self._MAX_SUBJECT_DISPLAY_LENGTH] + "..." + subject = subject[: self._MAX_SUBJECT_DISPLAY_LENGTH] + "..." self.add_table_row( table, f"{unread_mark}{message_id}", message["author_name"], subject, time ) @@ -273,8 +273,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): string = self.get_table_header(board) + str(table) + self.get_table_footer(board) else: string = ( - "There are no messages on this board yet." - f" {self.get_can_can_post_info(board)}." + f"There are no messages on this board yet. {self.get_can_can_post_info(board)}." ) self.msg(string) @@ -302,7 +301,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): savefunc=_board_editor_save, quitfunc=_board_editor_quit, key="board message", - persistent=True + persistent=True, ) def format_post(self, message_id, separator, date_time, author, subject, body): @@ -315,7 +314,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): date_time=date_time, author=author, subject=subject, - body=body + body=body, ).strip() def create_table(self): @@ -403,6 +402,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): board.message_id = 0 caller.msg("Message board cleared.") + def _board_get_messages(board): if not (messages := board.db.messages): board.db.messages = {} @@ -410,12 +410,14 @@ def _board_get_messages(board): return messages + def _board_editor_load(caller): buf = caller.db.message_board_buf or "" caller.attributes.remove("message_board_buf") return buf + def _board_editor_save(caller, buf): if buf: if len([line for line in buf.split("\n") if line.strip()]) < 2: @@ -428,6 +430,7 @@ def _board_editor_save(caller, buf): caller.db.message_board_buf = buf return True + def _board_editor_quit(caller): message = caller.db.message_board_buf if not message: @@ -448,6 +451,7 @@ def _board_editor_quit(caller): caller.attributes.remove("message_board_buf") caller.attributes.remove("message_board_message_id") + def board_post_message(caller, board, subject, body, message_id=None): if not caller.permissions.check("Builder"): subject = strip_ansi(subject) @@ -461,7 +465,7 @@ def board_post_message(caller, board, subject, body, message_id=None): "author_name": caller.key, "subject": subject, "message": msg, - "read_by": {caller} + "read_by": {caller}, } message_id = board.message_id + 1 From fdd1fafcfd948b998717c11f1cf49c9007a66a2a Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 17:41:13 +1000 Subject: [PATCH 09/13] Remove superfluous formatting from message board subject --- evennia/contrib/game_systems/messageboard/messageboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index b62cf47417..4010bba301 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -128,7 +128,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): msg = message["message"] lines = msg.message.split("\n") - subject = f"|{border_col}Subject:|n {lines[0]}" + subject = lines[0] body = "\n".join(lines[1:]) author = message["author_name"] date_time = msg.date_created From cf2047852c947be542f8e9e4f9d8a559041aef09 Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Tue, 30 Apr 2024 19:23:11 +1000 Subject: [PATCH 10/13] Add blank line between subject and body when changing message board post --- evennia/contrib/game_systems/messageboard/messageboard.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 4010bba301..834355a6a9 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -207,7 +207,9 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): self.caller.msg("You may only change your own messages.") return - self.start_editor(board, buf=message["message"].message, message_id=int(message_id)) + lines = message["message"].message.split("\n") + buf = lines[0] + "\n\n" + "\n".join(lines[1:]) + self.start_editor(board, buf=buf, message_id=int(message_id)) return if "delete" in self.switches or "del" in self.switches: From 6d81de2619dc07605a65ff8d4058fe0fb4f7be53 Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Wed, 1 May 2024 19:39:17 +1000 Subject: [PATCH 11/13] Move some message board functions into func() --- .../game_systems/messageboard/messageboard.py | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 834355a6a9..7ebf05484a 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -235,7 +235,20 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): self.caller.msg("You may only delete your own messages.") return - self._delete_message(self.caller, board, int(message_id)) + message_id = int(message_id) + message = board.messages[message_id]["message"] + subject = message.message.split("\n")[0] + answer = yield ( + f"Are you sure you want to delete the message '{subject}|n' (#{message_id}) yes/[no]?" + ) + if not answer.lower() in ("yes", "y"): + self.caller.msg("Cancelled. Message not deleted.") + return + + message.delete() + del board.messages[message_id] + self.caller.msg(f"Message #{message_id} deleted.") + return if "clear" in self.switches: @@ -243,7 +256,16 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): self.caller.msg("You are not allowed to clear this message board.") return - self._clear_board(self.caller, board) + answer = yield ("Are you sure you want to clear all messages from the board yes/[no]?") + if not answer.lower() in ("yes", "y"): + self.caller.msg("Cancelled. The message board was not cleared.") + return + + Msg.objects.filter(db_receivers_objects=board).delete() + board.messages.clear() + board.message_id = 0 + self.caller.msg("The message board has been cleared.") + return # List messages @@ -377,33 +399,6 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): ) self.caller.msg(string) - @interactive - def _delete_message(self, caller, board, message_id): - message = board.messages[message_id]["message"] - subject = message.message.split("\n")[0] - answer = yield ( - f"Are you sure you want to delete the message '{subject}|n' (#{message_id}) yes/[no]?" - ) - if not answer.lower() in ("yes", "y"): - caller.msg("Cancelled.") - return - - message.delete() - del board.messages[message_id] - caller.msg(f"Message #{message_id} deleted.") - - @interactive - def _clear_board(self, caller, board): - answer = yield ("Are you sure you want to clear all messages from the board yes/[no]?") - if not answer.lower() in ("yes", "y"): - caller.msg("Cancelled.") - return - - Msg.objects.filter(db_receivers_objects=board).delete() - board.messages.clear() - board.message_id = 0 - caller.msg("Message board cleared.") - def _board_get_messages(board): if not (messages := board.db.messages): From 08548d4acd2ec61d309a0b2a8cf5931047f0290a Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Thu, 2 May 2024 17:32:15 +1000 Subject: [PATCH 12/13] Move message board message meta-data onto Msg --- .../game_systems/messageboard/messageboard.py | 80 +++++++++---------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 7ebf05484a..8fa2b6508b 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -126,17 +126,14 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): separator_char = self.caller.account.options.get("separator_fill") separator = f"|{border_col}{separator_char * width}|n" - msg = message["message"] - lines = msg.message.split("\n") - subject = lines[0] - body = "\n".join(lines[1:]) - author = message["author_name"] - date_time = msg.date_created + author, subject = message.header.split("\n") + body = message.message + date_time = message.date_created self.caller.msg( self.format_post(message_id, separator, date_time, author, subject, body) ) - message["read_by"].add(self.caller) + message.tags.add(self.caller.dbid, category="read_by") return @@ -165,18 +162,19 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): if len(self.arglist) != 1 or self.rhs: self.caller.msg(usage) return + message_id = self.arglist[0].strip() use_editor = True else: - if len(self.arglist) < 1 or not self.rhs: + if not (self.lhs and self.rhs): self.caller.msg(usage) return + message_id = self.lhs use_editor = False - message_id = self.arglist[0].strip() if not (message := self._get_message(board, message_id, usage)): return - subject = message["message"].message.split("\n")[0] + subject = message.header.split("\n")[1] subject = f"Re: {subject}" if use_editor: self.start_editor(board, subject=subject) @@ -207,9 +205,9 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): self.caller.msg("You may only change your own messages.") return - lines = message["message"].message.split("\n") - buf = lines[0] + "\n\n" + "\n".join(lines[1:]) - self.start_editor(board, buf=buf, message_id=int(message_id)) + subject = message.header.split("\n")[1] + body = message.message + self.start_editor(board, subject=subject, body=body, message_id=int(message_id)) return if "delete" in self.switches or "del" in self.switches: @@ -222,10 +220,6 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): return message_id = self.args.strip() - if not message_id.isdigit(): - self.caller.msg("Usage: board/delete ") - return - if not ( message := self._get_message(board, message_id, "Usage: board/delete ") ): @@ -235,18 +229,16 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): self.caller.msg("You may only delete your own messages.") return - message_id = int(message_id) - message = board.messages[message_id]["message"] - subject = message.message.split("\n")[0] + subject = message.header.split("\n")[1] answer = yield ( f"Are you sure you want to delete the message '{subject}|n' (#{message_id}) yes/[no]?" ) if not answer.lower() in ("yes", "y"): - self.caller.msg("Cancelled. Message not deleted.") + self.caller.msg("Cancelled. The message was not deleted.") return message.delete() - del board.messages[message_id] + del board.messages[int(message_id)] self.caller.msg(f"Message #{message_id} deleted.") return @@ -271,11 +263,10 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): # List messages if messages: if "unread" in self.switches: - # messages = [message for message in messages if self.caller not in message["read_by"]] messages = { message_id: message for message_id, message in messages.items() - if not self.caller in message["read_by"] + if not message.tags.get(self.caller.dbid, category="read_by") } if not messages: self.caller.msg("You have read all the messages on this board.") @@ -284,13 +275,13 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): table = self.create_table() time_zone = self.caller.account.options.get("timezone") for message_id, message in messages.items(): - unread_mark = "" if self.caller in message["read_by"] else "*" - subject = message["subject"] - time = datetime_format(self._utc_to_local(message["post_date"], time_zone)) + unread_mark = "" if message.tags.get(self.caller.dbid, category="read_by") else "*" + author, subject = message.header.split("\n") if len(subject) > self._MAX_SUBJECT_DISPLAY_LENGTH: subject = subject[: self._MAX_SUBJECT_DISPLAY_LENGTH] + "..." + time = datetime_format(self._utc_to_local(message.date_created, time_zone)) self.add_table_row( - table, f"{unread_mark}{message_id}", message["author_name"], subject, time + table, f"{unread_mark}{message_id}", author, subject, time ) self.format_table(table) @@ -301,7 +292,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): ) self.msg(string) - def start_editor(self, board, subject=None, buf=None, message_id=None): + def start_editor(self, board, subject=None, body=None, message_id=None): """ (WIP) subject required if replying @@ -310,12 +301,14 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): Edit should call the module's board_post_message(caller, board, subject, body, message_id=None) """ + self._send_editor_intructions() self.caller.db.message_board = board + self.caller.db.message_board_buf = "" if subject: - self.caller.db.message_board_buf = subject - if buf: - self.caller.db.message_board_buf = buf + self.caller.db.message_board_buf = f"{subject}\n" + if body: + self.caller.db.message_board_buf += f"\n{body}" if message_id: self.caller.db.message_board_message_id = message_id @@ -454,16 +447,16 @@ def board_post_message(caller, board, subject, body, message_id=None): subject = strip_ansi(subject) if message_id is None: - msg = create_message( - caller, subject + "\n" + body, receivers=board, tags=[("board_message", "comms")] + message = create_message( + caller, + body, + header=f"{caller.key}\n{subject}", + receivers=board, + tags=[ + ("board_message", "comms"), + (caller.dbid, "read_by") + ] ) - message = { - "post_date": msg.date_created, - "author_name": caller.key, - "subject": subject, - "message": msg, - "read_by": {caller}, - } message_id = board.message_id + 1 board.message_id = message_id @@ -476,6 +469,7 @@ def board_post_message(caller, board, subject, body, message_id=None): caller.msg(f"Message #{message_id} has been deleted. Change cancelled.") return - message["subject"] = subject - message["message"].message = subject + "\n" + body + author = message.header.split('\n')[0] + message.header = f"{author}\n{subject}" + message.message = body caller.msg(f"Message #{message_id} has been changed.") From 38d7a0d6f4d322a8aefda27cf05b07c9cf5f751c Mon Sep 17 00:00:00 2001 From: Chiizujin Date: Wed, 8 May 2024 12:21:06 +1000 Subject: [PATCH 13/13] Fix traceback when using `board/read` with no message number --- evennia/contrib/game_systems/messageboard/messageboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/game_systems/messageboard/messageboard.py b/evennia/contrib/game_systems/messageboard/messageboard.py index 8fa2b6508b..1c1364b63e 100644 --- a/evennia/contrib/game_systems/messageboard/messageboard.py +++ b/evennia/contrib/game_systems/messageboard/messageboard.py @@ -112,7 +112,7 @@ class CmdEvMessageBoard(COMMAND_DEFAULT_CLASS): unread = { id: message for id, message in messages.items() - if self.caller not in message["read_by"] + if not message.tags.has(self.caller.dbid, category="read_by") } if not unread: self.caller.msg("You have read all the messages on this board.")