Compare commits

..

3 Commits

Author SHA1 Message Date
MrRaph_
2abb14e7c0 feat: list creation/search if exists 2024-12-03 13:53:02 +01:00
MrRaph_
a270bc3296 v0.1.0 2024-12-03 11:44:43 +01:00
MrRaph_
9688aa5957 ignore releases 2024-12-03 09:44:51 +01:00
4 changed files with 194 additions and 30 deletions

2
.gitignore vendored
View File

@@ -160,4 +160,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
fr.mrraph.hoarder-bot-v0.0.2.mbp *.mbp

View File

@@ -1,3 +1,4 @@
hoarder_url: "http://hoarder.local" hoarder_url: "http://hoarder.local"
hoarder_api_key: "votre_cle_api" hoarder_api_key: "votre_cle_api"
hoarder_list_name: "list_name"
command_prefix: hoarder command_prefix: hoarder

View File

@@ -4,13 +4,13 @@ from typing import Type
from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
from maubot import Plugin, MessageEvent from maubot import Plugin, MessageEvent
from maubot.handlers import event, command from maubot.handlers import event, command
from datetime import datetime
# Setup config file # Setup config file
class Config(BaseProxyConfig): class Config(BaseProxyConfig):
def do_update(self, helper: ConfigUpdateHelper) -> None: def do_update(self, helper: ConfigUpdateHelper) -> None:
helper.copy("hoarder_url") helper.copy("hoarder_url")
helper.copy("hoarder_api_key") helper.copy("hoarder_api_key")
helper.copy("hoarder_list_name")
helper.copy("command_prefix") helper.copy("command_prefix")
class HoarderForwarder(Plugin): class HoarderForwarder(Plugin):
@@ -22,35 +22,63 @@ class HoarderForwarder(Plugin):
def get_config_class(cls) -> Type[BaseProxyConfig]: def get_config_class(cls) -> Type[BaseProxyConfig]:
return Config return Config
async def get_page_title(self, url: str) -> str: async def send_message(self, evt: MessageEvent, message: str, formatted: bool = False, in_thread: bool = False):
"""Récupère le titre de la page web à partir de l'URL sans utiliser BeautifulSoup.""" """
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: try:
async with aiohttp.ClientSession() as session: await evt.client.send_message(
async with session.get(url) as response: room_id=evt.room_id, # Envoyer dans la même salle
if response.status == 200: content=content
html = await response.text()
# Rechercher la balise <title>
match = re.search(r"<title>(.*?)</title>", html, re.IGNORECASE | re.DOTALL)
if match:
return match.group(1).strip()
else:
return "Sans titre"
else:
return "Titre introuvable"
except Exception as e:
print(f"Erreur lors de la récupération du titre: {e}")
return "Erreur"
@command.new(
"hoarder",
) )
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) @command.argument("url", pass_raw=True, required=True)
async def send_to_hoarder(self, evt: MessageEvent, url: str) -> None: async def bookmark(self, evt: MessageEvent, url: str) -> None:
await evt.mark_read() await evt.mark_read()
hoarder_url = self.config["hoarder_url"] + '/api/trpc/bookmarks.createBookmark' 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"] api_key = self.config["hoarder_api_key"]
create_endpoint = f"{hoarder_url}/api/trpc/bookmarks.createBookmark"
self.log.info(hoarder_url) self.log.info(hoarder_url)
#title = await self.get_page_title(url) # Récupère le titre de la page #title = await self.get_page_title(url) # Récupère le titre de la page
@@ -73,10 +101,145 @@ class HoarderForwarder(Plugin):
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post(hoarder_url, json=payload, headers=headers) as response: async with session.post(create_endpoint, json=payload, headers=headers) as response:
if response.status == 201: if response.status == 200 or response.status == 201 or response.status == 204:
self.log.info(f"URL envoyée avec succès à Hoarder : {url}") 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: else:
self.log.error(f"Échec de l'envoi de l'URL à Hoarder : {response.status}") 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: except Exception as e:
self.log.exception(f"Erreur lors de l'envoi de l'URL à Hoarder : {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 <url>` : 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)

View File

@@ -8,7 +8,7 @@ maubot: 0.1.0
id: fr.mrraph.hoarder-bot id: fr.mrraph.hoarder-bot
# A PEP 440 compliant version string. # A PEP 440 compliant version string.
version: 0.0.2 version: 0.1.0
# The SPDX license identifier for the plugin. https://spdx.org/licenses/ # The SPDX license identifier for the plugin. https://spdx.org/licenses/
# Optional, assumes all rights reserved if omitted. # Optional, assumes all rights reserved if omitted.