diff --git a/base-config.yaml b/base-config.yaml new file mode 100644 index 0000000..a2c02e8 --- /dev/null +++ b/base-config.yaml @@ -0,0 +1,11 @@ +synology_host: "192.168.1.100" # Adresse IP ou domaine de votre Synology +synology_port: 5001 # Port DSM, généralement 5000 (HTTP) ou 5001 (HTTPS) +use_ssl: true # Utiliser SSL (https) +username: "votre_nom_utilisateur" # Nom d'utilisateur Synology +password: "votre_mot_de_passe" # Mot de passe Synology +api_version: 7 # Version de l'API DSM (vérifiez la version de votre DSM) +otp_secret_key: None +default_folder: "MatrixDefault" # Dossier par défaut dans Synology Photos +room_folders: # Mappage des rooms Matrix aux dossiers Synology + "!roomid1:matrix.org": "Room1Uploads" + "!roomid2:matrix.org": "Room2Uploads" \ No newline at end of file diff --git a/maubot.yaml b/maubot.yaml new file mode 100644 index 0000000..bf85978 --- /dev/null +++ b/maubot.yaml @@ -0,0 +1,11 @@ +maubot: 0.1.0 +id: fr.mrraph.maubot-syno-photo +version: 0.0.3 +license: MIT +modules: +- syno-media-uploader +main_class: SynologyPhotoPlugin +config: true +extra_files: +- base-config.yaml +database: false diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6e662a7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +synology-api +requests +aiohttp +pyotp \ No newline at end of file diff --git a/syno-media-uploader.py b/syno-media-uploader.py new file mode 100644 index 0000000..8443a29 --- /dev/null +++ b/syno-media-uploader.py @@ -0,0 +1,215 @@ +import os +import aiohttp +import asyncio +from typing import Type +from mautrix.client import Client +from maubot import Plugin, MessageEvent +from maubot.handlers import command, event +from mautrix.types import EventType, MessageType, RelationType, TextMessageEventContent, Format,RelatesTo,InReplyTo +from synology_api.filestation import FileStation +from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper +import pyotp + + +class Config(BaseProxyConfig): + def do_update(self, helper: ConfigUpdateHelper) -> None: + helper.copy("synology_host") + helper.copy("synology_port") + helper.copy("use_ssl") + helper.copy("username") + helper.copy("password") + helper.copy("default_folder") + helper.copy("room_folders") + helper.copy("api_version") + helper.copy("otp_secret_key") + +class SynologyPhotoPlugin(Plugin): + # def __init__(self, bot): + # super().__init__(bot) + # self.synology = None + + @classmethod + def get_config_class(cls) -> Type[BaseProxyConfig]: + return Config + + def save_config(self) -> None: + self.config.save() + + async def update_config(self, new_config: dict) -> None: + self.config.update(new_config) + self.save_config() + self.log.debug("Configuration updated and saved") + + + async def start(self): + await super().start() + + # Récupérer la configuration + self.config.load_and_update() + self.synology_host = self.config['synology_host'] + self.synology_port = self.config['synology_port'] + self.use_ssl = self.config['use_ssl'] + self.username = self.config['username'] + self.password = self.config['password'] + self.default_folder = self.config['default_folder'] + self.room_folders = self.config['room_folders'] + self.api_version = self.config['api_version'] + self.otp_secret_key = self.config['otp_secret_key'] + + self.synology = None + + otp = pyotp.TOTP(self.otp_secret_key) + self.otp_code = otp.now() + + self.log.info("SynologyPhotoPlugin démarré") + + # Initialiser la connexion Synology + try: + self.synology = FileStation( + ip_address=self.config["synology_host"], + port=self.config["synology_port"], + username=self.config["username"], + password=self.config["password"], + secure=bool(self.config["use_ssl"]), + cert_verify=False, + dsm_version=int(self.config["api_version"]), + debug=True, + otp_code=self.otp_code + ) + self.log.info("Connexion à Synology réussie") + except Exception as e: + self.log.error(f"Échec de la connexion à Synology : {e}") + + @event.on(EventType.ROOM_MESSAGE) + async def on_event(self, event): + await self.client.set_typing(event.room_id, timeout=0) + + # Vérifier si l'événement est un message contenant du contenu multimédia + msgtype = str(event.content.msgtype) + if msgtype in ["m.image", "m.video"]: + self.log.info(f"Reçu un {event.content.msgtype} dans le canal {event.room_id}") + self.log.error(event) + await self.handle_media(event) + # else: + # self.log.error("ELSE") + # self.log.error("'" + str(event.content.msgtype) + "'") + + async def handle_media(self, event): + # Extraire l'URL du contenu multimédia + content = event.content + url = content.get("url") + if not url: + self.log.warning("URL du média non trouvée") + return + + # Télécharger le fichier + file_bytes = await self.client.download_media(url) + + filename = str(event.content.filename) + if not filename or filename == "None": + filename = str(event.content.body) + + if not file_bytes: + self.log.error("Échec du téléchargement du média") + return + + with open('/tmp/'+ filename, "wb") as binary_file: + # Write bytes to file + binary_file.write(file_bytes) + + # Déterminer le dossier cible basé sur la room Matrix + room_id = event.room_id + target_folder = self.room_folders.get(room_id, self.default_folder) + self.log.info(f"Dossier cible pour la room {room_id} : {target_folder}") + + # Télécharger le média sur Synology Photos + upload_success = await self.upload_to_synology('/tmp/' + filename, target_folder) + + if upload_success: + self.log.info(f"Média téléchargé avec succès sur Synology Photos : {filename} dans {target_folder}") + message = f"Média téléchargé avec succès sur Synology Photos : {filename} dans {target_folder}" + # Optionnel : supprimer le fichier local après téléchargement + os.remove('/tmp/' + filename) + else: + self.log.error("Échec du téléchargement sur Synology Photos") + message = "Échec du téléchargement sur Synology Photos" + + content = TextMessageEventContent( + msgtype=MessageType.TEXT, + body=message, + format=Format.HTML, + formatted_body=message + ) + in_reply_to = InReplyTo(event_id=event.event_id) + if event.content.relates_to and event.content.relates_to.rel_type == RelationType.THREAD: + await event.respond(content, in_thread=True) + else: + content.relates_to = RelatesTo( + in_reply_to=in_reply_to + ) + await event.respond(content) + + async def get_access_token(self): + # Utiliser le jeton d'accès de Maubot pour télécharger le média + # Vous pouvez personnaliser cette méthode selon votre configuration + token = self.bot.token + return token + + async def download_media(self, url, access_token): + headers = { + "Authorization": f"Bearer {access_token}" + } + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=headers) as resp: + if resp.status == 200: + data = await resp.read() + # Déterminer le nom du fichier + filename = url.split("/")[-1].split("?")[0] # Enlever les paramètres URL + file_path = os.path.join("/tmp/", filename) + with open(file_path, "wb") as f: + f.write(data) + return file_path, filename + else: + self.log.error(f"Erreur lors du téléchargement du média : {resp.status}") + return None, None + + async def upload_to_synology(self, file_full_path, target_folder): + if not self.synology: + self.log.error("Connexion Synology non initialisée") + return False + + try: + # Vérifier si le dossier cible existe, sinon le créer + ### TODO: Gérer les dossiers imbriqués + # folders = self.synology.get_list() + # folder_titles = [folder['title'] for folder in folders['data']['items']] + # if target_folder not in folder_titles: + # self.synology.create_folder(target_folder) + # self.log.info(f"Dossier '{target_folder}' créé sur Synology Photos") + + # Télécharger le fichier + with open(file_full_path, 'rb') as f: + file_data = f.read() + + # Télécharger le fichier dans le dossier cible + self.synology.upload_file( + dest_path=target_folder, + file_path=file_full_path, + # file_data=file_data, + # path=target_folder, + create_parents=True, + # filename=filename + ) + return True + except Exception as e: + self.log.error(f"Erreur lors de l'upload sur Synology Photos : {e}") + return False + + async def stop(self): + if self.synology: + try: + self.synology.logout() + self.log.info("Déconnexion de Synology réussie") + except Exception as e: + self.log.error(f"Erreur lors de la déconnexion de Synology : {e}") + await super().stop() \ No newline at end of file