From 11152ae8de210b69bec6fd2e50b9d1c48b6016c0 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 12 Jun 2025 00:33:34 -0700 Subject: [PATCH 1/4] Minor bug fixes --- consumerntfy.py | 14 ++++++++++---- readme.md | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/consumerntfy.py b/consumerntfy.py index 964fbcc..e54a1e5 100644 --- a/consumerntfy.py +++ b/consumerntfy.py @@ -42,6 +42,7 @@ class ConsumerNTFY(Plugin): async def report(self, evt: MessageEvent, message: str) -> None: # Split command (minus !command_name) into tokens tokens = message.split() + st_desig = tokens[0].lower() # Each command must have a state/territory designation and a message if len(tokens) < 2: return @@ -52,15 +53,20 @@ class ConsumerNTFY(Plugin): return # Check allowed senders - allowed_senders = self.config["allowed_senders." + tokens[0]] - if allowed_senders and not evt.sender in allowed_senders: - return + allowed_senders = self.config["allowed_senders"] + + print(evt.sender) + + if st_desig in self.config["allowed_senders"]: + print(f"Allowed senders for {st_desig}: {self.config['allowed_senders'].get(st_desig, [])}") + if not evt.sender in self.config['allowed_senders'].get(st_desig, []): + return # Sending notification to NTFY url = self.config["server_url"] + "/" + self.config["server_topic"] body = ' '.join(tokens[1:]) - if len(self.config["allowed_locations"]) > 1: + if len(self.config["allowed_locations"]) != 1: body = tokens[0] + ": " + body # Consider authentication diff --git a/readme.md b/readme.md index 48b4eed..4b621b7 100644 --- a/readme.md +++ b/readme.md @@ -2,4 +2,4 @@ Please see https://git.fiftyfiftyonearizona.org/webmaster/matrix-report-documentation -This plugin supports whitelisting state/territory designators, allowed senders, and authenticated NTFY posting. \ No newline at end of file +This plugin supports whitelisting state/territory designators, allowed senders, reaction receipt, and authenticated NTFY posting. \ No newline at end of file From 24f6f25506cd99f6cde5ad7f5dfa3a9f4581a59e Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 24 Jun 2025 15:29:32 -0700 Subject: [PATCH 2/4] Updating code to support endpoints per reigon and __global__ --- base-config.yaml | 37 +++++++-------- consumerntfy.py | 119 ++++++++++++++++++++++++++++------------------- maubot.yaml | 2 +- 3 files changed, 88 insertions(+), 70 deletions(-) diff --git a/base-config.yaml b/base-config.yaml index b7631ed..07fedc5 100644 --- a/base-config.yaml +++ b/base-config.yaml @@ -2,32 +2,29 @@ # DO NOT CHANGE THIS UNLESS YOU KNOW WHAT YOU ARE DOING command_prefix: report -# Whether or not the bot should pay attention to only specific state/territory designators -# This bot will include the designator in the pushed notificaion if this is disabled, -# or if the allowed_locations contains multiple strings -allowed_locations_enabled: false -allowed_locations: - - arizona - - california - -# Which accounts should be paid attention to on a state/territory-by-state/territory basis. -# If the state/territory is not in allowed_senders, then the bot will allow all senders -# Otherwise only listed senders -allowed_senders: +# Which regions the bot should pay attention to +# Additionally, optionally whitelist specific accounts for each region +allowed_regions: foo: - "@reports:fiftyfiftyonerarizona.org" bar: - "@reports:example.org" - "@reports:example.com" + buzz: + aaa: -# Where and how the plugin will send the data to -server_url: "https://ntfy.sh" -server_topic: "changeMe" +# Bot-specific configurations per region +region_configs: + arizona: + # Where and how the plugin will send the data to + server_url: "https://ntfy.sh" + server_topic: "changeme" -# If the plugin should use a username and password to send notifications -server_use_authentication: false -server_username: "foo" -server_password: "bar" + # If the plugin should use a username and password to send notifications + server_use_authentication: true + server_username: "no" + server_password: "way" -# If this bot should send a reaction to the message if the notification was successful +# If this bot should send a reaction to the message +# if the notification was successful send_reaction: true \ No newline at end of file diff --git a/consumerntfy.py b/consumerntfy.py index e54a1e5..822d18d 100644 --- a/consumerntfy.py +++ b/consumerntfy.py @@ -11,15 +11,8 @@ import aiohttp class Config(BaseProxyConfig): def do_update(self, helper: ConfigUpdateHelper) -> None: helper.copy("command_prefix") - helper.copy("allowed_locations_enabled") - helper.copy("allowed_locations") - helper.copy("allowed_senders") - helper.copy("server_url") - helper.copy("server_topic") - helper.copy("command_prefix") - helper.copy("server_use_authentication") - helper.copy("server_username") - helper.copy("server_password") + helper.copy("allowed_regions") + helper.copy("region_configs") helper.copy("send_reaction") class ConsumerNTFY(Plugin): @@ -36,51 +29,79 @@ class ConsumerNTFY(Plugin): def get_command_name(self) -> str: return self.config["command_prefix"] + # Checks if a sender if allowed to send for a particular region + def validateSender(self, region: str, sender: str): + # Check that config value exists + if not self.config["allowed_regions"]: return False + # Check that region is allowed + if not self.config["allowed_regions"][region]: return False + # All senders allowed for this region + if len(self.config["allowed_regions"][region]) == 0: return True + # Check that sender is allowed for region + if not sender in self.config["allowed_regions"][region]: return False + return True + + # Does the necesary config checks for the given event + # Returns list of regions to process (strings) + # Currently just the specified region and "__global__" + def validateReport(self, evt: MessageEvent, message: str): + # Split command (minus !command_name) into tokens + tokens = message.split() + region = tokens[0].lower() + + # Each command must have a state/territory designation and a message + if len(tokens) < 2: return None + + self.log.debug(region) + + # This is a list of regions to process for this specific message + # This is only used to consider __global__ + regions_to_process = [] + if (self.validateSender("__global__", evt.sender)): + regions_to_process.append("__global__") + if (self.validateSender(region, evt.sender)): + regions_to_process.append(region) + + return regions_to_process + # What gets called when !command_name message is sent @command.new(name=get_command_name, help="Report Something") @command.argument("message", pass_raw=True) async def report(self, evt: MessageEvent, message: str) -> None: - # Split command (minus !command_name) into tokens - tokens = message.split() - st_desig = tokens[0].lower() - - # Each command must have a state/territory designation and a message - if len(tokens) < 2: return - - # Check locations whitelist - if self.config["allowed_locations_enabled"]: - if not tokens[0].lower() in self.config["allowed_locations"]: - return - - # Check allowed senders - allowed_senders = self.config["allowed_senders"] - - print(evt.sender) - - if st_desig in self.config["allowed_senders"]: - print(f"Allowed senders for {st_desig}: {self.config['allowed_senders'].get(st_desig, [])}") - if not evt.sender in self.config['allowed_senders'].get(st_desig, []): - return - - # Sending notification to NTFY - - url = self.config["server_url"] + "/" + self.config["server_topic"] - body = ' '.join(tokens[1:]) - if len(self.config["allowed_locations"]) != 1: - body = tokens[0] + ": " + body - - # Consider authentication - authentication = None - if self.config["server_use_authentication"]: - authentication = aiohttp.BasicAuth(self.config["server_username"], self.config["server_password"]) - - # Send notification - for trash in range(3): # Try to send 3 times - async with self.http.post(url, data=body, auth=authentication) as response: - if response.status == 200: - await evt.react("👍") - return + # If all have passed + ntfy_posts_passed = None + # Iterate through each endpoint that the message should be pushed to + # (if any) + for region in self.validateReport(evt, message): + # Detect no regions in reaction + if ntfy_posts_passed is None: ntfy_posts_passed = True + + # Grab region-specific conrfig + region_config = self.config["region_configs"][region] + + # Create notification text + split_message = message.split() + text = "[" + split_message[0] + "] " + ' '.join(message.split()[1:]) + + # Build URL + url = region_config["server_url"] + "/" + region_config["server_topic"] + + # Consider authentication + authentication = None + if region_config["server_use_authentication"]: + authentication = aiohttp.BasicAuth(region_config["server_username"], region_config["server_password"]) + + # Send notification + async with self.http.post(url, data=text, auth=authentication) as response: + if not response.status == 200: + ntfy_posts_passed = False + # Send reaction based on successful or failedPOSTs + if self.config["send_reaction"]: + if ntfy_posts_passed is True: + await evt.react("👍") + elif ntfy_posts_passed is False: + await evt.react("👎") # That's all, folks \ No newline at end of file diff --git a/maubot.yaml b/maubot.yaml index daf2dfb..899e4fe 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: org.fiftyfiftyonearizona.reports.consumerntfy -version: 1.0.0 +version: 1.1.0 license: MIT modules: - consumerntfy From 2193a9ce62a223af784b0848c560d45599e1555e Mon Sep 17 00:00:00 2001 From: 50501AZ Webmaster Date: Mon, 30 Jun 2025 09:38:59 -0700 Subject: [PATCH 3/4] Heavy improvements. Version 1.2 --- base-config.yaml | 19 +++++++++----- compile.bash | 9 +++++++ consumerntfy.py | 66 +++++++++++++++++++++++++----------------------- maubot.yaml | 2 +- readme.md | 10 +++++++- 5 files changed, 67 insertions(+), 39 deletions(-) create mode 100644 compile.bash diff --git a/base-config.yaml b/base-config.yaml index 07fedc5..e8b8b24 100644 --- a/base-config.yaml +++ b/base-config.yaml @@ -2,8 +2,11 @@ # DO NOT CHANGE THIS UNLESS YOU KNOW WHAT YOU ARE DOING command_prefix: report -# Which regions the bot should pay attention to -# Additionally, optionally whitelist specific accounts for each region +# Which regions the bot should pay attention to. +# Pass through empty list to allow all senders for a region. +# Add a list of MatrixIDs to whitelist senders for a region. +# Use __global__ to refer to all regions. +# Code looks to see if a sender is allowed for either a specific region or __global__. allowed_regions: foo: - "@reports:fiftyfiftyonerarizona.org" @@ -14,6 +17,9 @@ allowed_regions: aaa: # Bot-specific configurations per region +# Code will assume no authentication if either username or password are missing. +# Use __global__ to specify an endpoint for all regions. +# Code will push to both __global__ and region-specific endpoint. region_configs: arizona: # Where and how the plugin will send the data to @@ -21,10 +27,11 @@ region_configs: server_topic: "changeme" # If the plugin should use a username and password to send notifications - server_use_authentication: true - server_username: "no" - server_password: "way" + username: "no" + password: "way" # If this bot should send a reaction to the message -# if the notification was successful +# Will send 👍 on successful POST to NTFY, and 👎 on failiure. +# Failiure to post to either the region specific, or __global__ endpoint is +# considered complete failiure. send_reaction: true \ No newline at end of file diff --git a/compile.bash b/compile.bash new file mode 100644 index 0000000..66c85a3 --- /dev/null +++ b/compile.bash @@ -0,0 +1,9 @@ +#!/bin/bash + +# Get filename +id=$(cat maubot.yaml | grep "id:" | tr ' ' '\n' | tail -n1) +version=$(cat maubot.yaml | grep "version:" | tr ' ' '\n' | tail -n1) +filename="$id-$version.mbp" + +# Compress to zip +zip $filename * \ No newline at end of file diff --git a/consumerntfy.py b/consumerntfy.py index 822d18d..833a057 100644 --- a/consumerntfy.py +++ b/consumerntfy.py @@ -30,39 +30,44 @@ class ConsumerNTFY(Plugin): return self.config["command_prefix"] # Checks if a sender if allowed to send for a particular region - def validateSender(self, region: str, sender: str): - # Check that config value exists - if not self.config["allowed_regions"]: return False - # Check that region is allowed - if not self.config["allowed_regions"][region]: return False - # All senders allowed for this region - if len(self.config["allowed_regions"][region]) == 0: return True - # Check that sender is allowed for region - if not sender in self.config["allowed_regions"][region]: return False - return True + def validate_sender(self, region: str, sender: str): + # Mautrix isn't documented, like at all, so I'm just gonna catch the + # error because IDK how to see if a map is inside a map. + try: allowed_list = self.config["allowed_regions"][region] + except: return False + + if len(allowed_list) == 0: return True + if sender in allowed_list: return True + + # Sender not allowed in region config + return False - # Does the necesary config checks for the given event - # Returns list of regions to process (strings) - # Currently just the specified region and "__global__" - def validateReport(self, evt: MessageEvent, message: str): + # Does the necessary config checks for the given event + # Returns list of recursive configs to process + def validate_report(self, evt: MessageEvent, message: str): # Split command (minus !command_name) into tokens tokens = message.split() region = tokens[0].lower() # Each command must have a state/territory designation and a message - if len(tokens) < 2: return None + if len(tokens) < 2: return [] + # And we must have self.config["region_configs"] + try: trashvariable = self.config["allowed_regions"][region] + except: return [] - self.log.debug(region) + configs = [] # To be returned - # This is a list of regions to process for this specific message - # This is only used to consider __global__ - regions_to_process = [] - if (self.validateSender("__global__", evt.sender)): - regions_to_process.append("__global__") - if (self.validateSender(region, evt.sender)): - regions_to_process.append(region) + allowed_globally = self.validate_sender("__global__", evt.sender) + allowed_region = self.validate_sender(region, evt.sender) - return regions_to_process + # If user is allowed globally and/or for a region, + # the plugin should process both the region and __global__ configs + if allowed_globally or allowed_region: + for i in ["__global__", region]: + try: configs.append(self.config["region_configs"][i]) + except: trashvariable = None + + return configs # What gets called when !command_name message is sent @command.new(name=get_command_name, help="Report Something") @@ -73,12 +78,11 @@ class ConsumerNTFY(Plugin): # Iterate through each endpoint that the message should be pushed to # (if any) - for region in self.validateReport(evt, message): + for region_config in self.validate_report(evt, message): # Detect no regions in reaction if ntfy_posts_passed is None: ntfy_posts_passed = True - # Grab region-specific conrfig - region_config = self.config["region_configs"][region] + self.log.debug(region_config) # Create notification text split_message = message.split() @@ -88,12 +92,12 @@ class ConsumerNTFY(Plugin): url = region_config["server_url"] + "/" + region_config["server_topic"] # Consider authentication - authentication = None - if region_config["server_use_authentication"]: - authentication = aiohttp.BasicAuth(region_config["server_username"], region_config["server_password"]) + auth = None + if "username" in region_config and "password" in region_config: + auth = aiohttp.BasicAuth(region_config["username"], region_config["password"]) # Send notification - async with self.http.post(url, data=text, auth=authentication) as response: + async with self.http.post(url, data=text, auth=auth) as response: if not response.status == 200: ntfy_posts_passed = False diff --git a/maubot.yaml b/maubot.yaml index 899e4fe..d040b22 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: org.fiftyfiftyonearizona.reports.consumerntfy -version: 1.1.0 +version: 1.2.0 license: MIT modules: - consumerntfy diff --git a/readme.md b/readme.md index 4b621b7..5b3d88d 100644 --- a/readme.md +++ b/readme.md @@ -2,4 +2,12 @@ Please see https://git.fiftyfiftyonearizona.org/webmaster/matrix-report-documentation -This plugin supports whitelisting state/territory designators, allowed senders, reaction receipt, and authenticated NTFY posting. \ No newline at end of file +This plugin supports the following: + +* whitelisting regions, or allowing all regions. +* allowed senders per region, or all regions. +* reaction receipt upon successful POST to NTFY. +* authenticated NTFY POSTing. +* Different NTFY endpoint, topic, and authentication per region. + +Please see [base-config.yaml](base-config.yaml) for explanation of behavior. \ No newline at end of file From beefb8c73ebb95be4a88cb5c9ac22e4106d7d0f2 Mon Sep 17 00:00:00 2001 From: 50501AZ Webmaster Date: Mon, 30 Jun 2025 11:08:09 -0700 Subject: [PATCH 4/4] Minor bug fixes. Will re-release 1.2.0 --- consumerntfy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consumerntfy.py b/consumerntfy.py index 833a057..5bce54e 100644 --- a/consumerntfy.py +++ b/consumerntfy.py @@ -36,7 +36,7 @@ class ConsumerNTFY(Plugin): try: allowed_list = self.config["allowed_regions"][region] except: return False - if len(allowed_list) == 0: return True + if allowed_list is None: return True # Empty list if sender in allowed_list: return True # Sender not allowed in region config @@ -52,7 +52,7 @@ class ConsumerNTFY(Plugin): # Each command must have a state/territory designation and a message if len(tokens) < 2: return [] # And we must have self.config["region_configs"] - try: trashvariable = self.config["allowed_regions"][region] + try: trashvariable = self.config["region_configs"] except: return [] configs = [] # To be returned