Skip to content

Commit 724fa4f

Browse files
JulienPalardflashcode
authored andcommitted
pyrnotify.py 2.0: modernize and various fixes
1 parent 99fb3ad commit 724fa4f

File tree

1 file changed

+79
-50
lines changed

1 file changed

+79
-50
lines changed

python/pyrnotify.py

Lines changed: 79 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# -*- coding: utf-8 -*-
22
# ex:sw=4 ts=4:ai:
33
#
4-
# Copyright (c) 2012 by Krister Svanlund <[email protected]>
4+
# SPDX-FileCopyrightText: 2012 Krister Svanlund <[email protected]>
5+
#
6+
# SPDX-License-Identifier: GPL3
7+
#
58
# based on tcl version:
69
# Remote Notification Script v1.1
710
# by Gotisch <[email protected]>
@@ -25,23 +28,25 @@
2528
#
2629
# On the "client" (where the notifications will end up), host is
2730
# the remote host where weechat is running:
28-
# python2 location/of/pyrnotify.py 4321 & ssh -R 4321:localhost:4321 username@host
31+
# python2 location/of/pyrnotify.py 4321 & ssh -R 4321:localhost:4321 username@host
2932
# You can have a second argument to specified the time to display the notification
3033
# python2 location/of/pyrnotify.py 4321 2000 & ssh -R 4321:localhost:4321 username@host
3134
# Important to remember is that you should probably setup the
3235
# connection with public key encryption and use something like
3336
# autossh to do this in the background.
3437
#
3538
# In weechat:
36-
# /python load pyrnotify.py
37-
# and set the port
38-
# /set plugins.var.python.pyrnotify.port 4321
39+
# /python load pyrnotify.py
40+
# and set the port
41+
# /set plugins.var.python.pyrnotify.port 4321
3942
#
4043
# It is also possible to set which host pyrnotify shall connect to,
4144
# this is not recommended. Using a ssh port-forward is much safer
4245
# and doesn't require any ports but ssh to be open.
4346

4447
# ChangeLog:
48+
# 2025-12-06: Modernize code, avoid escaping using regex by using json
49+
# instead of shell-like serialization.
4550
#
4651
# 2018-08-20: Make it work with python3
4752
# use of sendall instead of send
@@ -50,109 +55,133 @@
5055
# 2012-06-19: Added simple escaping to the title and body strings for
5156
# the script to handle trailing backslashes.
5257

53-
from __future__ import print_function
5458

5559
try:
5660
import weechat as w
61+
5762
in_weechat = True
5863
except ImportError as e:
5964
in_weechat = False
6065

61-
import os, sys, re
66+
import json
67+
import os
68+
import re
6269
import socket
6370
import subprocess
64-
import shlex
71+
import sys
6572

66-
SCRIPT_NAME = "pyrnotify"
67-
SCRIPT_AUTHOR = "Krister Svanlund <[email protected]>"
68-
SCRIPT_VERSION = "1.1"
73+
SCRIPT_NAME = "pyrnotify"
74+
SCRIPT_AUTHOR = "Krister Svanlund <[email protected]>"
75+
SCRIPT_VERSION = "2.0"
6976
SCRIPT_LICENSE = "GPL3"
70-
SCRIPT_DESC = "Send remote notifications over SSH"
77+
SCRIPT_DESC = "Send remote notifications over SSH"
7178

72-
def escape(s):
73-
return re.sub(r'([\\"\'])', r'\\\1', s)
7479

7580
def run_notify(icon, nick, chan, message):
76-
host = w.config_get_plugin('host')
81+
host = w.config_get_plugin("host")
7782
try:
7883
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
79-
s.connect((host, int(w.config_get_plugin('port'))))
80-
msg = "normal {0} \"{1} to {2}\" \"{3}\"".format(icon, nick, escape(chan), escape(message))
81-
s.sendall(msg.encode('utf-8'))
84+
s.connect((host, int(w.config_get_plugin("port"))))
85+
msg = {
86+
"urgency": "normal",
87+
"icon": icon,
88+
"nick": nick,
89+
"chan": chan,
90+
"message": message,
91+
}
92+
s.sendall(json.dumps(msg, ensure_ascii=False).encode("UTF-8"))
8293
s.close()
8394
except Exception as e:
8495
w.prnt("", "Could not send notification: {0}".format(e))
8596

97+
8698
def on_msg(*a):
8799
if len(a) == 8:
88100
data, buffer, timestamp, tags, displayed, highlight, sender, message = a
89101
if data == "private" or int(highlight):
90-
if data == "private" and w.config_get_plugin('pm-icon'):
91-
icon = w.config_get_plugin('pm-icon')
102+
if data == "private" and w.config_get_plugin("pm-icon"):
103+
icon = w.config_get_plugin("pm-icon")
92104
else:
93-
icon = w.config_get_plugin('icon')
94-
buffer = "me" if data == "private" else w.buffer_get_string(buffer, "short_name")
105+
icon = w.config_get_plugin("icon")
106+
buffer = (
107+
"me" if data == "private" else w.buffer_get_string(buffer, "short_name")
108+
)
95109
run_notify(icon, sender, buffer, message)
96-
#w.prnt("", str(a))
97110
return w.WEECHAT_RC_OK
98111

112+
99113
def weechat_script():
100-
settings = {'host' : "localhost",
101-
'port' : "4321",
102-
'icon' : "utilities-terminal",
103-
'pm-icon' : "emblem-favorite"}
104-
if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""):
105-
for (kw, v) in settings.items():
114+
settings = {
115+
"host": "localhost",
116+
"port": "4321",
117+
"icon": "utilities-terminal",
118+
"pm-icon": "emblem-favorite",
119+
}
120+
if w.register(
121+
SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""
122+
):
123+
for kw, v in settings.items():
106124
if not w.config_get_plugin(kw):
107125
w.config_set_plugin(kw, v)
108126
w.hook_print("", "notify_message", "", 1, "on_msg", "")
109127
w.hook_print("", "notify_private", "", 1, "on_msg", "private")
110-
w.hook_print("", "notify_highlight", "", 1, "on_msg", "") # Not sure if this is needed
128+
w.hook_print(
129+
"", "notify_highlight", "", 1, "on_msg", ""
130+
) # Not sure if this is needed
111131

112132

113133
######################################
114134
## This is where the client starts, except for the global if-check nothing below this line is
115135
## supposed to be executed in weechat, instead it runs when the script is executed from
116136
## commandline.
117137

138+
118139
def accept_connections(s, timeout=None):
119140
conn, addr = s.accept()
120141
try:
121-
data = ""
122-
d = conn.recv(1024)
123-
while d:
124-
data += d.decode('utf-8')
125-
d = conn.recv(1024)
142+
data = b""
143+
while d := conn.recv(1024):
144+
data += d
126145
finally:
127146
conn.close()
128147
if data:
129148
try:
130-
urgency, icon, title, body = shlex.split(data)
149+
notif = json.loads(data.decode("UTF-8"))
150+
argv = [
151+
"notify-send",
152+
"-u",
153+
notif["urgency"],
154+
"-c",
155+
"IRC",
156+
"-i",
157+
notif["icon"],
158+
"--",
159+
notif["chan"],
160+
notif["message"],
161+
]
131162
if timeout:
132-
subprocess.call(["notify-send", "-t", timeout, "-u", urgency, "-c", "IRC", "-i", icon, escape(title), escape(body)])
133-
else:
134-
subprocess.call(["notify-send", "-u", urgency, "-c", "IRC", "-i", icon, escape(title), escape(body)])
135-
136-
except ValueError as e:
137-
print(e)
138-
except OSError as e:
163+
argv.extend(["-t", timeout])
164+
subprocess.run(argv)
165+
except (ValueError, OSError) as e:
139166
print(e)
140-
accept_connections(s, timeout)
167+
141168

142169
def weechat_client(argv):
170+
port = int(argv[1]) if len(sys.argv) > 1 else 4321
143171
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
144172
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
145-
s.bind(("localhost", int(argv[1] if len(sys.argv) > 1 else 4321)))
173+
s.bind(("localhost", port))
146174
s.listen(5)
147175
try:
148-
accept_connections(s, argv[2] if len(sys.argv) > 2 else None)
149-
except KeyboardInterrupt as e:
150-
print("Keyboard interrupt")
151-
print(e)
176+
while True:
177+
accept_connections(s, argv[2] if len(sys.argv) > 2 else None)
178+
except KeyboardInterrupt:
179+
return
152180
finally:
153181
s.close()
154182

155-
if __name__ == '__main__':
183+
184+
if __name__ == "__main__":
156185
if in_weechat:
157186
weechat_script()
158187
else:

0 commit comments

Comments
 (0)