Skip to content

Commit 382a3da

Browse files
committed
pyrnotify.py: Modernize and various fixes.
1 parent 99fb3ad commit 382a3da

File tree

1 file changed

+78
-50
lines changed

1 file changed

+78
-50
lines changed

python/pyrnotify.py

Lines changed: 78 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
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 by Krister Svanlund <[email protected]>
55
# based on tcl version:
66
# Remote Notification Script v1.1
77
# by Gotisch <[email protected]>
88
#
9+
# SPDX-License-Identifier: GPL3
10+
#
911
# This program is free software; you can redistribute it and/or modify
1012
# it under the terms of the GNU General Public License as published by
1113
# the Free Software Foundation; either version 3 of the License, or
@@ -25,23 +27,25 @@
2527
#
2628
# On the "client" (where the notifications will end up), host is
2729
# the remote host where weechat is running:
28-
# python2 location/of/pyrnotify.py 4321 & ssh -R 4321:localhost:4321 username@host
30+
# python2 location/of/pyrnotify.py 4321 & ssh -R 4321:localhost:4321 username@host
2931
# You can have a second argument to specified the time to display the notification
3032
# python2 location/of/pyrnotify.py 4321 2000 & ssh -R 4321:localhost:4321 username@host
3133
# Important to remember is that you should probably setup the
3234
# connection with public key encryption and use something like
3335
# autossh to do this in the background.
3436
#
3537
# In weechat:
36-
# /python load pyrnotify.py
37-
# and set the port
38-
# /set plugins.var.python.pyrnotify.port 4321
38+
# /python load pyrnotify.py
39+
# and set the port
40+
# /set plugins.var.python.pyrnotify.port 4321
3941
#
4042
# It is also possible to set which host pyrnotify shall connect to,
4143
# this is not recommended. Using a ssh port-forward is much safer
4244
# and doesn't require any ports but ssh to be open.
4345

4446
# ChangeLog:
47+
# 2025-12-06: Modernize code, avoid escaping using regex by using json
48+
# instead of shell-like serialisation.
4549
#
4650
# 2018-08-20: Make it work with python3
4751
# use of sendall instead of send
@@ -50,109 +54,133 @@
5054
# 2012-06-19: Added simple escaping to the title and body strings for
5155
# the script to handle trailing backslashes.
5256

53-
from __future__ import print_function
5457

5558
try:
5659
import weechat as w
60+
5761
in_weechat = True
5862
except ImportError as e:
5963
in_weechat = False
6064

61-
import os, sys, re
65+
import json
66+
import os
67+
import re
6268
import socket
6369
import subprocess
64-
import shlex
70+
import sys
6571

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

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

7579
def run_notify(icon, nick, chan, message):
76-
host = w.config_get_plugin('host')
80+
host = w.config_get_plugin("host")
7781
try:
7882
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'))
83+
s.connect((host, int(w.config_get_plugin("port"))))
84+
msg = {
85+
"urgency": "normal",
86+
"icon": icon,
87+
"nick": nick,
88+
"chan": chan,
89+
"message": message,
90+
}
91+
s.sendall(json.dumps(msg, ensure_ascii=False).encode("UTF-8"))
8292
s.close()
8393
except Exception as e:
8494
w.prnt("", "Could not send notification: {0}".format(e))
8595

96+
8697
def on_msg(*a):
8798
if len(a) == 8:
8899
data, buffer, timestamp, tags, displayed, highlight, sender, message = a
89100
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')
101+
if data == "private" and w.config_get_plugin("pm-icon"):
102+
icon = w.config_get_plugin("pm-icon")
92103
else:
93-
icon = w.config_get_plugin('icon')
94-
buffer = "me" if data == "private" else w.buffer_get_string(buffer, "short_name")
104+
icon = w.config_get_plugin("icon")
105+
buffer = (
106+
"me" if data == "private" else w.buffer_get_string(buffer, "short_name")
107+
)
95108
run_notify(icon, sender, buffer, message)
96-
#w.prnt("", str(a))
97109
return w.WEECHAT_RC_OK
98110

111+
99112
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():
113+
settings = {
114+
"host": "localhost",
115+
"port": "4321",
116+
"icon": "utilities-terminal",
117+
"pm-icon": "emblem-favorite",
118+
}
119+
if w.register(
120+
SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""
121+
):
122+
for kw, v in settings.items():
106123
if not w.config_get_plugin(kw):
107124
w.config_set_plugin(kw, v)
108125
w.hook_print("", "notify_message", "", 1, "on_msg", "")
109126
w.hook_print("", "notify_private", "", 1, "on_msg", "private")
110-
w.hook_print("", "notify_highlight", "", 1, "on_msg", "") # Not sure if this is needed
127+
w.hook_print(
128+
"", "notify_highlight", "", 1, "on_msg", ""
129+
) # Not sure if this is needed
111130

112131

113132
######################################
114133
## This is where the client starts, except for the global if-check nothing below this line is
115134
## supposed to be executed in weechat, instead it runs when the script is executed from
116135
## commandline.
117136

137+
118138
def accept_connections(s, timeout=None):
119139
conn, addr = s.accept()
120140
try:
121-
data = ""
122-
d = conn.recv(1024)
123-
while d:
124-
data += d.decode('utf-8')
125-
d = conn.recv(1024)
141+
data = b""
142+
while d := conn.recv(1024):
143+
data += d
126144
finally:
127145
conn.close()
128146
if data:
129147
try:
130-
urgency, icon, title, body = shlex.split(data)
148+
notif = json.loads(data.decode("UTF-8"))
149+
argv = [
150+
"notify-send",
151+
"-u",
152+
notif["urgency"],
153+
"-c",
154+
"IRC",
155+
"-i",
156+
notif["icon"],
157+
"--",
158+
notif["chan"],
159+
notif["message"],
160+
]
131161
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:
162+
argv.extend(["-t", timeout])
163+
subprocess.run(argv)
164+
except (ValueError, OSError) as e:
139165
print(e)
140-
accept_connections(s, timeout)
166+
141167

142168
def weechat_client(argv):
169+
port = int(argv[1]) if len(sys.argv) > 1 else 4321
143170
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
144171
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
145-
s.bind(("localhost", int(argv[1] if len(sys.argv) > 1 else 4321)))
172+
s.bind(("localhost", port))
146173
s.listen(5)
147174
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)
175+
while True:
176+
accept_connections(s, argv[2] if len(sys.argv) > 2 else None)
177+
except KeyboardInterrupt:
178+
return
152179
finally:
153180
s.close()
154181

155-
if __name__ == '__main__':
182+
183+
if __name__ == "__main__":
156184
if in_weechat:
157185
weechat_script()
158186
else:

0 commit comments

Comments
 (0)