import re from typing import List, Tuple, Type, Set from mautrix.types import UserID from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper from maubot import Plugin, MessageEvent from maubot.handlers import command class Config(BaseProxyConfig): def do_update(self, helper: ConfigUpdateHelper) -> None: helper.copy("prefix") helper.copy("url") helper.copy("user") helper.copy("pass") helper.copy("whitelist") class RTLinksPlugin(Plugin): prefix: str whitelist: Set[UserID] headers = {"User-agent": "rtlinksmaubot"} regex_number = re.compile(r'[0-9]{6}') regex_properties = re.compile(r'([a-zA-z]+): (.+)') regex_history = re.compile(r'([0-9]+): (.+)') async def start(self) -> None: self.on_external_config_update() def on_external_config_update(self) -> None: self.config.load_and_update() self.prefix = self.config["prefix"] self.whitelist = set(self.config["whitelist"]) self.api = '{}/REST/1.0/'.format(self.config['url']) self.post_data = {'user': self.config['user'], 'pass': self.config['pass']} @classmethod def get_config_class(cls) -> Type[BaseProxyConfig]: return Config async def can_manage(self, evt: MessageEvent) -> bool: if evt.sender in self.whitelist: return True return False def is_valid_number(self, number: str) -> bool: if self.regex_number.match(number): return True return False async def get_markdown_link(self, number: str) -> str: link = "{}/Ticket/Display.html?id={}".format(self.config['url'], number) markdown = "[rt#{}]({})".format(number, link) return markdown async def _properties(self, number: str) -> dict: await self.http.post(self.api, data=self.post_data, headers=self.headers) api_show = '{}ticket/{}/show'.format(self.api, number) async with self.http.get(api_show, headers=self.headers) as response: content = await response.text() ticket = dict(self.regex_properties.findall(content)) return ticket async def _edit(self, number: str, status: str) -> None: api_edit = '{}ticket/{}/edit'.format(self.api, number) content = {'content': 'Status: {}'.format(status)} data = {**self.post_data, **content} await self.http.post(api_edit, data=data, headers=self.headers) async def _comment(self, number: str, comment: str) -> None: api_comment = '{}ticket/{}/comment'.format(self.api, number) content = {'content': 'id: {}\nAction: comment\nText: {}'.format(number, comment)} data = {**self.post_data, **content} await self.http.post(api_comment, data=data, headers=self.headers) async def _history(self, number: str) -> dict: await self.http.post(self.api, data=self.post_data, headers=self.headers) api_history = '{}ticket/{}/history'.format(self.api, number) async with self.http.get(api_history, headers=self.headers) as response: content = await response.text() ticket = dict(self.regex_history.findall(content)) return ticket async def _entry(self, number: str, entry: str) -> dict: await self.http.post(self.api, data=self.post_data, headers=self.headers) api_entry = '{}ticket/{}/history/id/{}'.format(self.api, number, entry) async with self.http.get(api_entry, headers=self.headers) as response: content = await response.text() ticket = dict(self.regex_properties.findall(content)) return ticket @command.passive("((^| )([rR][tT]#?))([0-9]{6})", multiple=True) async def handler(self, evt: MessageEvent, subs: List[Tuple[str, str]]) -> None: await evt.mark_read() msg_lines = [] await self.http.post(self.api, data=self.post_data, headers=self.headers) for sub in subs: number = sub[4] api_show = '{}ticket/{}/show'.format(self.api, number) async with self.http.get(api_show, headers=self.headers) as response: content = await response.text() ticket = dict(self.regex_properties.findall(content)) markdown_link = await self.get_markdown_link(number) markdown = "{} ({}) is **{}** in **{}** from {}".format( markdown_link, ticket['Subject'], ticket['Status'], ticket['Queue'], ticket['Creator'] ) msg_lines.append(markdown) if msg_lines: await evt.respond("\n".join(msg_lines)) @command.new(name=lambda self: self.prefix, help="Manage RT tickets", require_subcommand=True) async def rt(self) -> None: pass @rt.subcommand("properties", aliases=("p", "prop"), help="Show all ticket properties.") @command.argument("number", "ticket number", pass_raw=True) async def properties(self, evt: MessageEvent, number: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() properties_dict = await self._properties(number) properties_list = ["{}: {}".format(k, v) for k, v in properties_dict.items()] markdown_link = await self.get_markdown_link(number) markdown = '{} properties: \n{}'.format(markdown_link, ' \n'.join(properties_list)) await evt.respond(markdown) @rt.subcommand("resolve", aliases=("r", "res"), help="Mark the ticket as resolved.") @command.argument("number", "ticket number", pass_raw=True) async def resolve(self, evt: MessageEvent, number: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() await self._edit(number, 'resolved') markdown_link = await self.get_markdown_link(number) await evt.respond('{} resolved'.format(markdown_link)) @rt.subcommand("open", aliases=("o", "op"),help="Mark the ticket as open.") @command.argument("number", "ticket number", pass_raw=True) async def open(self, evt: MessageEvent, number: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() await self._edit(number, 'open') markdown_link = await self.get_markdown_link(number) await evt.respond('{} opened'.format(markdown_link)) @rt.subcommand("stall", aliases=("st", "sta"), help="Mark the ticket as stalled.") @command.argument("number", "ticket number", pass_raw=True) async def stall(self, evt: MessageEvent, number: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() await self._edit(number, 'stalled') markdown_link = await self.get_markdown_link(number) await evt.respond('{} stalled'.format(markdown_link)) @rt.subcommand("delete", aliases=("d", "del"), help="Mark the ticket as deleted.") @command.argument("number", "ticket number", pass_raw=True) async def delete(self, evt: MessageEvent, number: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() await self._edit(number, 'deleted') markdown_link = await self.get_markdown_link(number) await evt.respond('{} deleted'.format(markdown_link)) @rt.subcommand("autoresolve", help="Enable automatic ticket resolve mode.") async def autoresolve(self, evt: MessageEvent) -> None: if not await self.can_manage(evt): return await evt.mark_read() await evt.reply('😂 lol, this is your job!') @rt.subcommand("comment", aliases=("c", "com"), help="Add a comment.") @command.argument("number", "ticket number", parser=str) @command.argument("comment", "comment text", pass_raw=True) async def comment(self, evt: MessageEvent, number: str, comment: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() await self._comment(number, comment) markdown_link = await self.get_markdown_link(number) await evt.respond('{} comment added'.format(markdown_link)) @rt.subcommand("history", aliases=("h", "hist"), help="Get a list of all history items for a given ticket.") @command.argument("number", "ticket number", pass_raw=True) async def history(self, evt: MessageEvent, number: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() history_dict = await self._history(number) history_list = ["{}: {}".format(k, v) for k, v in history_dict.items()] markdown_link = await self.get_markdown_link(number) markdown = '{} history entries: \n{}'.format(markdown_link, ' \n'.join(history_list)) await evt.respond(markdown) @rt.subcommand("entry", aliases=("e", "ent"), help="Gets the history information for a single history entry.") @command.argument("number", "ticket number", parser=str) @command.argument("entry", "history entry number", parser=str) async def entry(self, evt: MessageEvent, number: str, entry: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() entry_dict = await self._entry(number, entry) entry_list = ["{}: {}".format(k, v) for k, v in entry_dict.items()] markdown_link = await self.get_markdown_link(number) markdown = '{} history entry {}: \n{}'.format(markdown_link, entry, ' \n'.join(entry_list)) await evt.respond(markdown) @rt.subcommand("last", aliases=("l", "la"), help="Gets the history information for the last history entry.") @command.argument("number", "ticket number", parser=str) async def last(self, evt: MessageEvent, number: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() history_dict = await self._history(number) entry = max(history_dict, key=int) entry_dict = await self._entry(number, entry) entry_list = ["{}: {}".format(k, v) for k, v in entry_dict.items()] markdown_link = await self.get_markdown_link(number) markdown = '{} history entry {}: \n{}'.format(markdown_link, entry, ' \n'.join(entry_list)) await evt.respond(markdown) @rt.subcommand("show", aliases=("s", "sh"), help="Show all information about the ticket.") @command.argument("number", "ticket number", parser=str) async def show(self, evt: MessageEvent, number: str) -> None: if not await self.can_manage(evt) or not self.is_valid_number(number): return await evt.mark_read() properties_dict = await self._properties(number) properties_list = ["{}: {}".format(k, v) for k, v in properties_dict.items()] markdown_link = await self.get_markdown_link(number) markdown = '{} properties: \n{} \n\n'.format(markdown_link, ' \n'.join(properties_list)) history_dict = await self._history(number) for entry in history_dict.keys(): entry_dict = await self._entry(number, entry) entry_list = ["{}: {}".format(k, v) for k, v in entry_dict.items()] markdown += '{} history entry {}: \n{} \n\n'.format(markdown_link, entry, ' \n'.join(entry_list)) await evt.respond(markdown)