fix formatting and replies

This commit is contained in:
William Kray
2024-11-25 09:11:52 -08:00
parent 0ba094394a
commit 331c5adf93
2 changed files with 82 additions and 48 deletions

View File

@@ -1,6 +1,6 @@
maubot: 0.3.0
id: cloud.catgirl.ntfy
version: 0.1.0
version: 0.1.1
license: AGPL-3.0-or-later
modules:
- ntfy

View File

@@ -6,8 +6,7 @@ from typing import Any, Dict, Tuple
from aiohttp import ClientTimeout
from maubot import MessageEvent, Plugin
from maubot.handlers import command
from mautrix.types import (EventType, Format, MessageType,
TextMessageEventContent)
from mautrix.types import EventType, Format, MessageType, TextMessageEventContent
from mautrix.util.async_db import UpgradeTable
from mautrix.util.config import BaseProxyConfig
from mautrix.util.formatter import parse_html
@@ -27,8 +26,7 @@ class NtfyBot(Plugin):
self.config.load_and_update()
self.db = DB(self.database, self.log)
if EMOJI_FALLBACK:
self.log.warn(
"Please install the `emoji` package for full emoji support")
self.log.warn("Please install the `emoji` package for full emoji support")
await self.resubscribe()
async def stop(self) -> None:
@@ -63,19 +61,33 @@ class NtfyBot(Plugin):
async def can_use_command(self, evt: MessageEvent) -> bool:
if evt.sender in self.config["admins"]:
return True
levels = await self.client.get_state_event(evt.room_id, EventType.ROOM_POWER_LEVELS)
levels = await self.client.get_state_event(
evt.room_id, EventType.ROOM_POWER_LEVELS
)
user_level = levels.get_user_level(evt.sender)
if user_level < 50:
await evt.reply("You don't have the permission to manage ntfy subscriptions in this room.")
await evt.reply(
"You don't have the permission to manage ntfy subscriptions in this room."
)
return False
return True
@command.new(name=lambda self: self.config["command_prefix"], help="Manage ntfy subscriptions.", require_subcommand=True)
@command.new(
name=lambda self: self.config["command_prefix"],
help="Manage ntfy subscriptions.",
require_subcommand=True,
)
async def ntfy(self) -> None:
pass
@ntfy.subcommand("subscribe", aliases=("sub",), help="Subscribe this room to a ntfy topic.")
@command.argument("topic", "topic URL", matches="(([a-zA-Z0-9-]{1,63}\\.)+[a-zA-Z]{2,6}/[a-zA-Z0-9_-]{1,64})")
@ntfy.subcommand(
"subscribe", aliases=("sub",), help="Subscribe this room to a ntfy topic."
)
@command.argument(
"topic",
"topic URL",
matches="(([a-zA-Z0-9-]{1,63}\\.)+[a-zA-Z]{2,6}/[a-zA-Z0-9_-]{1,64})",
)
async def subscribe(self, evt: MessageEvent, topic: Tuple[str, Any]) -> None:
# see https://github.com/binwiederhier/ntfy/blob/82df434d19e3ef45ada9c00dfe9fc0f8dfba15e6/server/server.go#L61 for the valid topic regex
if not await self.can_use_command(evt):
@@ -83,20 +95,30 @@ class NtfyBot(Plugin):
server, topic = topic[0].split("/")
db_topic = await self.db.get_topic(server, topic)
if not db_topic:
db_topic = await self.db.create_topic(Topic(id=-1, server=server, topic=topic, last_event_id=None))
db_topic = await self.db.create_topic(
Topic(id=-1, server=server, topic=topic, last_event_id=None)
)
existing_subscriptions = await self.db.get_subscriptions(db_topic.id)
sub, _ = await self.db.get_subscription(db_topic.id, evt.room_id)
if sub:
await evt.reply("This room is already subscribed to %s/%s", server, topic)
await evt.reply(f"This room is already subscribed to {server}/{topic}")
else:
await self.db.add_subscription(db_topic.id, evt.room_id)
await evt.reply("Subscribed this room to %s/%s", server, topic)
await evt.reply(f"Subscribed this room to {server}/{topic}")
await evt.react(WHITE_CHECK_MARK)
if not existing_subscriptions:
self.subscribe_to_topic(db_topic)
@ntfy.subcommand("unsubscribe", aliases=("unsub",), help="Unsubscribe this room from a ntfy topic.")
@command.argument("topic", "topic URL", matches="(([a-zA-Z0-9-]{1,63}\\.)+[a-zA-Z]{2,6}/[a-zA-Z0-9_-]{1,64})")
@ntfy.subcommand(
"unsubscribe",
aliases=("unsub",),
help="Unsubscribe this room from a ntfy topic.",
)
@command.argument(
"topic",
"topic URL",
matches="(([a-zA-Z0-9-]{1,63}\\.)+[a-zA-Z]{2,6}/[a-zA-Z0-9_-]{1,64})",
)
async def unsubscribe(self, evt: MessageEvent, topic: Tuple[str, Any]) -> None:
# see https://github.com/binwiederhier/ntfy/blob/82df434d19e3ef45ada9c00dfe9fc0f8dfba15e6/server/server.go#L61 for the valid topic regex
if not await self.can_use_command(evt):
@@ -104,17 +126,17 @@ class NtfyBot(Plugin):
server, topic = topic[0].split("/")
db_topic = await self.db.get_topic(server, topic)
if not db_topic:
await evt.reply("This room is not subscribed to %s/%s", server, topic)
await evt.reply(f"This room is not subscribed to {server}/{topic}")
return
sub, _ = await self.db.get_subscription(db_topic.id, evt.room_id)
if not sub:
await evt.reply("This room is not subscribed to %s/%s", server, topic)
await evt.reply(f"This room is not subscribed to {server}/{topic}")
return
await self.db.remove_subscription(db_topic.id, evt.room_id)
if not await self.db.get_subscriptions(db_topic.id):
self.tasks[db_topic.id].cancel()
await self.db.clear_topic_id(db_topic.id)
await evt.reply("Unsubscribed this room from %s/%s", server, topic)
await evt.reply(f"Unsubscribed this room from {server}/{topic}")
await evt.react(WHITE_CHECK_MARK)
async def subscribe_to_topics(self) -> None:
@@ -130,22 +152,22 @@ class NtfyBot(Plugin):
if task.done() and not task.cancelled():
exc = task.exception()
self.log.exception(
"Subscription task errored, resubscribing", exc_info=exc)
self.tasks[topic.id] = self.loop.create_task(
asyncio.sleep(10.0))
"Subscription task errored, resubscribing", exc_info=exc
)
self.tasks[topic.id] = self.loop.create_task(asyncio.sleep(10.0))
self.tasks[topic.id].add_done_callback(
lambda _: self.subscribe_to_topic(topic))
lambda _: self.subscribe_to_topic(topic)
)
self.log.info("Subscribing to %s/%s", topic.server, topic.topic)
url = "%s/%s/json" % (topic.server, topic.topic)
self.log.info(f"Subscribing to {topic.server}/{topic.topic}")
url = f"{topic.server}/{topic.topic}/json"
if not url.startswith(("http://", "https://")):
url = "https://" + url
if topic.last_event_id:
url += "?since=%s" % topic.last_event_id
url += f"?since={topic.last_event_id}"
self.log.debug("Subscribing to URL %s", url)
task = self.loop.create_task(
self.run_topic_subscription(topic, url))
self.log.debug(f"Subscribing to URL {url}")
task = self.loop.create_task(self.run_topic_subscription(topic, url))
self.tasks[topic.id] = task
task.add_done_callback(log_task_exc)
@@ -155,17 +177,16 @@ class NtfyBot(Plugin):
line = await resp.content.readline()
# convert to string and remove trailing newline
line = line.decode("utf-8").strip()
self.log.trace("Received notification: %s", line)
self.log.trace(f"Received notification: {line}")
message = json.loads(line)
if message["event"] != "message":
continue
self.log.debug("Received message event: %s", line)
self.log.debug(f"Received message event: {line}")
# persist the received message id
await self.db.update_topic_id(topic.id, message["id"])
# build matrix message
html_content = self.build_message_content(
topic.server, message)
html_content = self.build_message_content(topic.server, message)
text_content = await parse_html(html_content.strip())
content = TextMessageEventContent(
@@ -181,7 +202,8 @@ class NtfyBot(Plugin):
await self.client.send_message(sub.room_id, content)
except Exception as exc:
self.log.exception(
"Failed to send matrix message!", exc_info=exc)
"Failed to send matrix message!", exc_info=exc
)
def build_message_content(self, server: str, message) -> str:
topic = message["topic"]
@@ -190,7 +212,9 @@ class NtfyBot(Plugin):
tags = message.get("tags", None)
click = message.get("click", None)
attachment = message.get("attachment", None)
view_actions = [a for a in message.get("actions", []) if a.get("action", "") == "view"]
view_actions = [
a for a in message.get("actions", []) if a.get("action", "") == "view"
]
if tags:
(emoji, non_emoji) = parse_tags(self.log, tags)
@@ -199,40 +223,50 @@ class NtfyBot(Plugin):
else:
emoji = tags = ""
html_content = "<span>Ntfy message in topic <code>%s/%s</code></span><blockquote>" % (
html.escape(server), html.escape(topic))
html_content = "<span>Ntfy message in topic <code>{server}/{topic}</code></span><blockquote>".format(
server=html.escape(server), topic=html.escape(topic)
)
# build title
if title and click:
html_content += "<p><h4>%s<a href=\"%s\">%s</a></h4></p>" % (
emoji, html.escape(click), html.escape(title))
html_content += (
'<p><h4>{emoji}<a href="{click}">{title}</a></h4></p>'.format(
emoji=emoji, click=html.escape(click), title=html.escape(title)
)
)
emoji = ""
elif title:
html_content += "<p><h4>%s%s</h4></p>" % (emoji, html.escape(title))
html_content += "<p><h4>{emoji}{title}</h4></p>".format(
emoji=emoji, title=html.escape(title)
)
emoji = ""
# build body
if click and not title:
html_content += "<p>%s<a href=\"%s\">%s</a></p>" % (
emoji, html.escape(click), html.escape(body).replace("\n", "<br />"))
html_content += '<p>{emoji}<a href="{click}">{body}</a></p>'.format(
emoji=emoji,
click=html.escape(click),
body=html.escape(body).replace("\n", "<br />"),
)
else:
html_content += "<p>%s%s</p>" % (emoji, html.escape(body).replace("\n", "<br />"))
html_content += "<p>{emoji}{body}</p>".format(
emoji=emoji, body=html.escape(body).replace("\n", "<br />")
)
# add non-emoji tags
if tags:
html_content += "<p><small>Tags: <code>%s</code></small></p>" % html.escape(
tags)
html_content += (
f"<p><small>Tags: <code>{html.escape(tags)}</code></small></p>"
)
# add actions
if len(view_actions) > 0:
html_content += "<p>"
for action in view_actions:
html_content += "<a href=\"%s\">%s</a>" % (html.escape(action["url"]), html.escape(action["label"]))
html_content += f"<a href=\"{html.escape(action['url'])}\">{html.escape(action['label'])}</a>"
html_content += "</p>"
# build attachment
if attachment:
html_content += "<p><a href=\"%s\">View %s</a></p>" % (html.escape(
attachment["url"]), html.escape(attachment["name"]))
html_content += f"<p><a href=\"{html.escape(attachment['url'])}\">View {html.escape(attachment['name'])}</a></p>"
html_content += "</blockquote>"
return html_content