diff --git a/client.py b/client.py index e968623..cc38777 100644 --- a/client.py +++ b/client.py @@ -1,49 +1,85 @@ -import pyperclip -import socketio -import reauests -import os +""" +This script is a daemon that, on event, send and sync the clipboard with a +distant one +""" +import threading +import time import sys -import subprocess +import json +import pyperclip +import requests +import socketio import zc.lockfile -# standard Python +import notification as notif # to put in a conf file -ip = 'simailadjalim.fr' +ip = 'localhost' port = "9564" hostname = "WarMachine" - username = "neotaku67" password = "un bon mot de passe de prefererance mais en sah tant qu'il est hashe ca passe" +sign = "[AllSync] " -""" -This script is a daemon that, on event, send and sync the clipboard with a distant one -""" -ip = f"http://{ip}:{port}/" +ip = f"http://{ip}:{port}" sio = socketio.Client() sio.connect(ip) - print("[Debug] Connected to Server .w.") +auth = requests.post(f"{ip}/user", + data={"username": username, "password": password}, + timeout=10000) +if auth.status_code != 200: + print("invalid credentials") + sys.exit() +token = json.loads(auth.content.decode())['token'] +notify_stop_event = threading.Event() +# clipboard_stop_event = threading.Event() + +def send_notification(): + notif.start_monitoring() + while not notify_stop_event.is_set(): + while not notif.notification_queue.empty(): + notification = notif.notification_queue.get() + if notification.title.find(sign) == -1: + continue + requests.put(f"{ip}/notification", + data={"token": token, + "title": notification.title, + "content": notification.content, + "deviceName": hostname}, + timeout=5000) + time.sleep(1) + + +notification_thread = threading.Thread(target=send_notification) +# clipboard_thread = threading.Thread(target=) -def sendSystemNotification(title:str,content:str): - """ - Une fonction pour 1. rendre le truc plus secure et - eviter que thomas face des rce sur mon pc .w. lmao - """ - subprocess.run(["notify-send",title,content]) @sio.event def NotificationUpdate(data): - content = data["content"] - clipCmd = f'echo {content} | xclip' - print(f"[ClipEvent] received data from ") - os.system(clipCmd) + if data["device_name"] == hostname: + return + response = requests.get(f"{ip}/notification/-1?token={token}", + timeout=2000) + response = json.loads(response.content.decode())["notifications"] + notification = notif.Notification(title=sign+response["title"], + content=response["content"]) + notification.show() + print("[NotificationEvent] received data") + @sio.event def ClipboardUpdate(data): - title, content = data["title"], data["content"] - command = f'notify-send "{title}" "{content}"' - print(command) - os.system(command) + if data["device_name"] == hostname: + return + clipboard = requests.get(f"{ip}/clipboard/-1?token={token}", + timeout=2000) + clipboard = json.loads(clipboard.content.decode()) + clipboard = clipboard["clipboard"] + pyperclip.copy(clipboard) + print("[ClipboardEvent] received data") -sio.wait() + +if __name__ == "__main__": + notification_thread.start() + sio.wait() diff --git a/notification.py b/notification.py new file mode 100644 index 0000000..0509372 --- /dev/null +++ b/notification.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +import dbus +import time +import queue +import threading +from gi.repository import GLib +import dbus +from dbus.mainloop.glib import DBusGMainLoop + + +class Notification: + """ + Reprensent a notification + + :param name str: + This is the optional name of the application sending the notification. + This should be the application's formal name, rather than some sort of + ID. An example would be "FredApp E-Mail Client," rather than + "fredapp-email-client." + + :param notification_id int: + An optional ID of an existing notification that this notification is + intended to replace. + + :param notification_icon str: + The notification icon.(not yet fully understood, use at your own risks) + + :param title str: + This is a single line overview of the notification. For instance, "You + have mail" or "A friend has come online". It should generally not be + longer than 40 characters, though this is not a requirement, and server + implementations should word wrap if necessary. + + :param content str: + This is a multi-line body of text. Each line is a paragraph, server + implementations are free to word wrap them as they see fit. + The body mayfrom gi.repository import GLib +import dbus +from dbus.mainloop.glib import DBusGMainLoop + functionality may not be implemented by the notification + server, conforming clients should check if it is available before using + it (see the GetCapabilities message in Protocol). An implementation is + free to ignore any requested by the client. As an example one possible + rendering of actions would be as buttons in the notification popup. + Actions are sent over as a list of pairs. Each even element in the list + (starting at index 0) represents the identifier for the action. Each + odd element in the list is the localized string that will be displayed + to the user. The default action (usually invoked by clicking the + notification) should have a key named "default". The name can be + anything, though implementations are free not to display it. + + :param hints dict: + Hints are a way to provide extra data to a notification server that the + server may be able to make use of. See + https://specifications.freedesktop.org/notification-spec/notification-\ + spec-latest.html#hints for a list of available hints. + + :param timeout int: + The timeout time in milliseconds since the display of the notification + at which the notification should automatically close. If -1, + the notification's expiration time is dependent on the notification + server's settings, and may vary for the type of notification. + If 0, the notification never expires. + """ + def __init__(self, + name: str = "", + notification_id: int = 0, + notification_icon: str = "", + title: str = "", + content: str = "", + actions: list = None, + hints: dict = None, + timeout: int = -1 + ) -> None: + self.name = name + self.notification_id = notification_id + self.notification_icon = notification_icon + self.title = title + self.content = content + self.actions = actions if actions is not None else [] + self.hints = hints if hints is not None else {"urgency": 1} + self.timeout = timeout + + def __str__(self) -> str: + return f"""{80*"_"} +Notification : +name = {self.name} +notification_id = {self.notification_id}sleep +notification_icon = {self.notification_icon} +title = {self.title} +content = {self.content} +actions = {self.actions} +hints = {self.hints} +timeout = {self.timeout} +{80*"_"}""" + + def show(self): + """ + Display the notification by sending notification event + to the dbus interface + """ + obj = dbus.Interface(dbus.SessionBus() + .get_object("org.freedesktop.Notifications", + "/org/freedesktop/Notifications"), + "org.freedesktop.Notifications") + obj.Notify(self.name, + self.notification_id, + self.notification_icon, + self.title, + self.content, + self.actions, + self.hints, + self.timeout) + + +notification_queue = queue.Queue() + +def log_notification(bus, message): + keys = ["app_name", "replaces_id", "app_icon", "summary", + "body", "actions", "hints", "expire_timeout"] + args = message.get_args_list() + if len(args) == 8: + notif = dict([(keys[i], args[i]) for i in range(8)]) + notif = Notification(args[0], + args[1], + args[2], + args[3], + args[4], + list(args[5]), + dict(args[6]), + args[7],) + notification_queue.put(notif) + + +stop_event = threading.Event() + +def monitor_notifications(): + loop = DBusGMainLoop(set_as_default=True) + session_bus = dbus.SessionBus() + session_bus.add_match_string( + "type='method_call',interface='org.freedesktop.Notifications'\ +,member='Notify',eavesdrop=true") + session_bus.add_message_filter(log_notification) + while not stop_event.is_set(): + GLib.MainLoop().run() + +thread = threading.Thread(target=monitor_notifications) +thread.daemon = True + + +def start_monitoring(): + thread.start() + +def stop_monitoring(): + stop_event.set() + +if __name__ == "__main__": + n = Notification(title="test",content="je suis le test") + n.show() diff --git a/serveur.py b/serveur.py index 112177d..aecb7bf 100644 --- a/serveur.py +++ b/serveur.py @@ -116,7 +116,10 @@ def add_notification(): notification) con.commit() con.close() - socketio.emit("NotificationUpdate", broadcast=True) + socketio.emit("NotificationUpdate", + data={"device_name": device_name}, + broadcast=True) + return {"status": "ok"}, 200 except: return {"status": "error"}, 500 @@ -132,9 +135,46 @@ def get_notifications(): token = request.values.get("token") con = sqlite3.connect("database.db") cur = con.cursor() - notifications = cur.execute("SELECT title,content FROM NOTIFICATION WHERE \ - token=?", (token, )).fetchall() - return {"status": "ok", "notifications": notifications}, 200 + notifications = cur.execute("SELECT title,content \ + FROM NOTIFICATION \ + WHERE token=?", + (token, )).fetchall() + data = {"status": "ok"} + data["notifications"] = [] + for notification in notifications: + data["notifications"].append({"title": notification[0],"content": notification[1]}) + return data, 200 + + +@app.route("/notification/", methods=['GET']) +def get_notification_by_id(notifid): + """ + Le but de cet app se resume a cette fonction, elle recoit une requete + http et renvoie via le websocket le contenu de la requette a tout les + client. + """ + notifid = int(notifid) + token = request.values.get("token") + con = sqlite3.connect("database.db") + cur = con.cursor() + if notifid == -1: + notifications = cur.execute("SELECT title,content \ + FROM NOTIFICATION \ + WHERE token=?\ + ORDER BY id DESC \ + LIMIT 1", + (token,)).fetchone() + else: + notifications = cur.execute("SELECT title,content \ + FROM NOTIFICATION \ + WHERE token=? AND id=?", + (token, notifid)).fetchone() + + return {"status": "ok", + "notifications": {"title": notifications[0], + "content": notifications[1] + } + }, 200 ############################################################################### @@ -159,7 +199,9 @@ def add_clipboard(): cur.execute("INSERT INTO CLIPBOARD VALUES (null,?,?,?,?)", clipboard) con.commit() con.close() - socketio.emit("ClipboardUpdate", broadcast=True) + socketio.emit("ClipboardUpdate", + data={"device_name": device_name}, + broadcast=True) return {"status": "ok"}, 200 except: return {"status": "error"}, 500 @@ -175,10 +217,14 @@ def get_clipboard(): token = request.values.get("token") con = sqlite3.connect("database.db") cur = con.cursor() - clipboard = cur.execute("SELECT content, id FROM CLIPBOARD WHERE \ - token=?", (token, )).fetchall() + clipboard = cur.execute("SELECT content \ + FROM CLIPBOARD \ + WHERE token=?", + (token, )).fetchall() + return {"status": "ok", "clipboard": clipboard}, 200 + @app.route("/clipboard/", methods=['GET']) def get_clipboard_by_id(clipid): """ @@ -191,19 +237,19 @@ def get_clipboard_by_id(clipid): con = sqlite3.connect("database.db") cur = con.cursor() if clipid == -1: - clipboard = cur.execute("SELECT content,id \ - FROM CLIPBOARD \ - WHERE token=? \ - ORDER BY id DESC \ - LIMIT 1", - (token,)).fetchall() + clipboard = cur.execute("SELECT content \ + FROM CLIPBOARD \ + WHERE token=? \ + ORDER BY id DESC \ + LIMIT 1", + (token,)).fetchone() else: - clipboard = cur.execute("SELECT content,id FROM CLIPBOARD WHERE \ - token=? AND\ - id=?", + clipboard = cur.execute("SELECT content \ + FROM CLIPBOARD \ + token=? AND id=?", (token, clipid)).fetchone() - return {"status": "ok", "clipboard": clipboard}, 200 + return {"status": "ok", "clipboard": clipboard[0]}, 200 if __name__ == '__main__':