import re import aiohttp from typing import Type from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper from maubot import Plugin, MessageEvent from maubot.handlers import event, command # Setup config file class Config(BaseProxyConfig): def do_update(self, helper: ConfigUpdateHelper) -> None: helper.copy("hoarder_url") helper.copy("hoarder_api_key") helper.copy("hoarder_list_name") helper.copy("command_prefix") class HoarderForwarder(Plugin): async def start(self) -> None: await super().start() self.config.load_and_update() @classmethod def get_config_class(cls) -> Type[BaseProxyConfig]: return Config async def send_message(self, evt: MessageEvent, message: str, formatted: bool = False, in_thread: bool = False): """ Envoie un message en réponse à un événement Matrix, avec support des threads. Args: evt (MessageEvent): L'événement de type message auquel répondre. message (str): Le texte du message à envoyer. formatted (bool): Si True, le message sera envoyé en tant que texte formaté (HTML). in_thread (bool): Si True, le message sera envoyé en tant que réponse dans un thread. """ content = { "body": message, "msgtype": "m.text" if not formatted else "m.notice", } if formatted: content["formatted_body"] = message content["format"] = "org.matrix.custom.html" if in_thread: # Vérifier que evt contient event_id if hasattr(evt, "event_id"): content["m.relates_to"] = { "rel_type": "m.thread", "event_id": evt.event_id # Utiliser evt.event_id pour le thread } else: self.log.error("Impossible de créer un thread : event_id manquant.") await evt.reply("Erreur interne : impossible de créer un thread.") return # Envoyer le message try: await evt.client.send_message( room_id=evt.room_id, # Envoyer dans la même salle content=content ) except Exception as e: self.log.exception(f"Erreur lors de l'envoi du message : {e}") @command.new("hoarder", require_subcommand=True, help="Commandes pour gérer Hoarder.") async def base_command(self, evt: MessageEvent): """Commande principale pour Hoarder (placeholder).""" #await evt.reply("Utilisez `!hoarder help` pour voir les commandes disponibles.") pass @base_command.subcommand("bookmark", help="Ajoute un bookmark à Hoarder.") @command.argument("url", pass_raw=True, required=True) async def bookmark(self, evt: MessageEvent, url: str) -> None: await evt.mark_read() hoarder_list = await self.ensure_list_exists(evt, self.config["hoarder_list_name"]) hoarder_url = self.config["hoarder_url"] api_key = self.config["hoarder_api_key"] create_endpoint = f"{hoarder_url}/api/trpc/bookmarks.createBookmark" self.log.info(hoarder_url) #title = await self.get_page_title(url) # Récupère le titre de la page if not hoarder_url or not api_key: self.log.warning("L'URL ou la clé API de Hoarder n'est pas configurée.") return headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "Accept": "application/json", } payload = { "json": { "type": "link", "url": url } } try: async with aiohttp.ClientSession() as session: async with session.post(create_endpoint, json=payload, headers=headers) as response: if response.status == 200 or response.status == 201 or response.status == 204: data = await response.json() bookmark_id = data[0]["result"]["data"]["json"]["id"] await self.send_message(evt, f"Bookmark créé avec succès : {bookmark_id}", in_thread=False) self.add_bookmark_to_list(bookmark_id, hoarder_list) return bookmark_id else: await self.send_message(evt, f"Erreur lors de la création du bookmark : {response.status}", in_thread=False) print(await response.text()) except Exception as e: await self.send_message(evt, f"Erreur lors de la création du bookmark : {e}", in_thread=False) return None async def get_all_lists(self, evt: MessageEvent): """Récupère la liste des listes sur Hoarder.""" hoarder_url = self.config["hoarder_url"] api_key = self.config["hoarder_api_key"] list_endpoint = f"{hoarder_url}/api/v1/lists" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "Accept": "application/json", } try: async with aiohttp.ClientSession() as session: async with session.get(list_endpoint, headers=headers) as response: if response.status == 200: data = await response.json() return data.get("lists", []) else: await self.send_message(evt, f"Erreur lors de la récupération des listes : {response.status}", in_thread=False) return [] except Exception as e: await self.send_message(evt, f"Erreur lors de la récupération des listes : {e}", in_thread=False) return [] async def find_list_by_name(self, evt: MessageEvent, name: str) -> str: """Vérifie si une liste existe et retourne son ID si elle est trouvée.""" lists = await self.get_all_lists(evt) for lst in lists: if lst["name"] == name: await self.send_message(evt, f"Liste trouvée : {name} (ID : {lst['id']})", in_thread=False) return lst["id"] await self.send_message(evt, f"Aucune liste trouvée avec le nom : {name}", in_thread=False) return None async def create_list(self, evt: MessageEvent, name: str) -> str: """Crée une nouvelle liste avec le nom donné et retourne son ID.""" hoarder_url = self.config["hoarder_url"] api_key = self.config["hoarder_api_key"] create_endpoint = f"{hoarder_url}/api/v1/lists" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "Accept": "application/json", } payload = { "name": f"{name}", "icon": "💬" } try: async with aiohttp.ClientSession() as session: async with session.post(create_endpoint, json=payload, headers=headers) as response: if response.status == 200 or response.status == 201 or response.status == 204: data = await response.json() self.log.info(response.json()) list_id = data["id"] self.log.info("STATUS OK") self.log.info(data) await self.send_message(evt, f"Liste créée avec succès : {name} (ID : {list_id})", in_thread=False) return list_id else: self.log.info("STATUS NOK") await self.send_message(evt, f"Erreur lors de la création de la liste : {response.status}", in_thread=False) return None except Exception as e: self.log.error("EXCEPT - create_list") self.log.error(f"EXCEPT - {e}") await self.send_message(evt, f"Erreur lors de la création de la liste : {e}", in_thread=False) return None async def ensure_list_exists(self, evt: MessageEvent, name: str) -> str: """Vérifie si une liste existe, sinon la crée.""" list_id = await self.find_list_by_name(evt, name) if list_id is None: await self.send_message(evt, f"La liste '{name}' n\'existe pas, création en cours...", in_thread=False) list_id = await self.create_list(evt, name) return list_id async def add_bookmark_to_list(self, evt: MessageEvent, bookmark_id: str): """Ajoute un bookmark à une liste donnée.""" hoarder_url = self.config["hoarder_url"] api_key = self.config["hoarder_api_key"] list_id = await self.ensure_list_exists(evt, self.config["hoarder_list_name"]) add_to_list_endpoint = f"{hoarder_url}/api/v1/lists/{list_id}/bookmarks" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "Accept": "application/json", } payload = { "bookmarkId": bookmark_id } try: async with aiohttp.ClientSession() as session: async with session.put(add_to_list_endpoint, headers=headers) as response: if response.status == 200 or response.status == 201 or response.status == 204: await self.send_message(evt, f"Bookmark {bookmark_id} ajouté à la liste {list_id} avec succès.", in_thread=False) return True else: await self.send_message(evt, f"Erreur lors de l'ajout à la liste : {response.status}", in_thread=False) await self.send_message(evt, await response.text(), in_thread=False) return False except Exception as e: await self.send_message(evt, f"Erreur lors de l'ajout du bookmark à la liste : {e}", in_thread=False) return False @base_command.subcommand("help", help="Affiche l'aide pour les commandes Hoarder.") async def hoarder_help(self, evt: MessageEvent): """Affiche un message d'aide pour les commandes Hoarder.""" help_message = ( "💬 **Commandes disponibles pour Hoarder Bot** 💬\n" "- `!hoarder bookmark ` : Ajoute le lien spécifié à votre liste Hoarder.\n" "- `!hoarder help` : Affiche ce message d'aide.\n\n" "⚙️ **Configuration** :\n" "Assurez-vous que l'URL de base de votre installation d'Hoarder est définie dans la configuration sous `hoarder_url`.\n\n" "Assurez-vous que la clef API d'Hoarder est définie dans la configuration sous `hoarder_api_key`.\n\n" "Assurez-vous que la liste cible est définie dans la configuration sous `hoarder_list_name`.\n\n" "📘 **Documentation supplémentaire** :\n" "Consultez les guides sur [Hoarder](https://docs.hoarder.app)." ) await evt.reply(help_message)