summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimeon Simeonov2022-09-10 10:27:18 +0200
committerSimeon Simeonov2022-09-10 10:27:18 +0200
commit003cd12be734b05d7281579d3d377f79425531fa (patch)
treeedac9e287a9d8a38b97df2c1722bfc732bc781d3
parent1dab5dbd8da35d7c7f9f44915fe5be7f7b1e5b79 (diff)
Remove obsolete code and use black alike code styling
-rwxr-xr-xbeinc_generic_client.py86
-rwxr-xr-xbeinc_pull.py110
-rwxr-xr-xbeinc_server.py156
-rw-r--r--beinc_weechat.py286
4 files changed, 376 insertions, 262 deletions
diff --git a/beinc_generic_client.py b/beinc_generic_client.py
index ca3847a..af1e74f 100755
--- a/beinc_generic_client.py
+++ b/beinc_generic_client.py
@@ -2,7 +2,7 @@
2# -*- coding: utf-8 -*- 2# -*- coding: utf-8 -*-
3 3
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0
5# Copyright (C) 2013-2020 Simeon Simeonov 5# Copyright (C) 2013-2022 Simeon Simeonov
6 6
7# This program is free software: you can redistribute it and/or modify 7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by 8# it under the terms of the GNU General Public License as published by
@@ -29,9 +29,8 @@ import sys
29import urllib.parse 29import urllib.parse
30import urllib.request 30import urllib.request
31 31
32
33__author__ = 'Simeon Simeonov' 32__author__ = 'Simeon Simeonov'
34__version__ = '4.1' 33__version__ = '4.2'
35__license__ = 'GPL3' 34__license__ = 'GPL3'
36 35
37 36
@@ -58,7 +57,7 @@ def fetch_password(args_password):
58 sys.exit(errno.EACCES) 57 sys.exit(errno.EACCES)
59 elif os.path.isfile(args_password): 58 elif os.path.isfile(args_password):
60 try: 59 try:
61 with io.open(args_password, 'r') as fp: 60 with io.open(args_password, 'r', encoding='utf-8') as fp:
62 passwd = fp.readline() 61 passwd = fp.readline()
63 if passwd.strip(): 62 if passwd.strip():
64 return passwd.strip() 63 return passwd.strip()
@@ -86,10 +85,12 @@ def pull_notifications(ssl_context, args):
86 data=urllib.parse.urlencode( 85 data=urllib.parse.urlencode(
87 ( 86 (
88 ('resource_name', args.rname), 87 ('resource_name', args.rname),
89 ('password', args.password) 88 ('password', args.password),
90 )).encode('utf-8'), 89 )
90 ).encode('utf-8'),
91 timeout=args.socket_timeout, 91 timeout=args.socket_timeout,
92 context=ssl_context) 92 context=ssl_context,
93 )
93 response_dict = json.loads(response.read().decode('utf-8')) 94 response_dict = json.loads(response.read().decode('utf-8'))
94 if response.code != 200: 95 if response.code != 200:
95 raise socket.error(response_dict.get('message', '')) 96 raise socket.error(response_dict.get('message', ''))
@@ -113,10 +114,12 @@ def push_notification(ssl_context, args):
113 ('resource_name', args.rname), 114 ('resource_name', args.rname),
114 ('password', args.password), 115 ('password', args.password),
115 ('title', args.title), 116 ('title', args.title),
116 ('message', args.message) 117 ('message', args.message),
117 )).encode('utf-8'), 118 )
119 ).encode('utf-8'),
118 timeout=args.socket_timeout, 120 timeout=args.socket_timeout,
119 context=ssl_context) 121 context=ssl_context,
122 )
120 response_dict = json.loads(response.read().decode('utf-8')) 123 response_dict = json.loads(response.read().decode('utf-8'))
121 if response.code != 200: 124 if response.code != 200:
122 raise socket.error(response_dict.get('message', '')) 125 raise socket.error(response_dict.get('message', ''))
@@ -125,91 +128,112 @@ def push_notification(ssl_context, args):
125def main(inargs=None): 128def main(inargs=None):
126 """main entry""" 129 """main entry"""
127 parser = argparse.ArgumentParser( 130 parser = argparse.ArgumentParser(
128 description='The following options are available') 131 description='The following options are available'
132 )
129 parser.add_argument( 133 parser.add_argument(
130 'url', 134 'url',
131 metavar='URL', 135 metavar='URL',
132 type=str, 136 type=str,
133 help='BEINC server destination URL') 137 help='BEINC server destination URL',
138 )
134 parser.add_argument( 139 parser.add_argument(
135 '-c', '--cert-file', 140 '-c',
141 '--cert-file',
136 metavar='FILE', 142 metavar='FILE',
137 type=str, 143 type=str,
138 dest='cert', 144 dest='cert',
139 default='', 145 default='',
140 help='CA-cert to check the server-cert against' 146 help='CA-cert to check the server-cert against'
141 '(default: Check disabled)') 147 '(default: Check disabled)',
148 )
142 parser.add_argument( 149 parser.add_argument(
143 '--ciphers', 150 '--ciphers',
144 metavar='CIPHERS', 151 metavar='CIPHERS',
145 type=str, 152 type=str,
146 dest='ciphers', 153 dest='ciphers',
147 default='', 154 default='',
148 help='Preferred ciphers list (default: auto)') 155 help='Preferred ciphers list (default: auto)',
156 )
149 parser.add_argument( 157 parser.add_argument(
150 '--disable-hostname-check', 158 '--disable-hostname-check',
151 action='store_true', 159 action='store_true',
152 dest='disable_hostname_check', 160 dest='disable_hostname_check',
153 default=False, 161 default=False,
154 help='Do not check whether server cert matches server hostname') 162 help='Do not check whether server cert matches server hostname',
163 )
155 parser.add_argument( 164 parser.add_argument(
156 '-m', '--message', 165 '-m',
166 '--message',
157 metavar='MESSAGE', 167 metavar='MESSAGE',
158 type=str, 168 type=str,
159 dest='message', 169 dest='message',
160 default='BEINC message', 170 default='BEINC message',
161 help='BEINC message (default: "BEINC message")') 171 help='BEINC message (default: "BEINC message")',
172 )
162 parser.add_argument( 173 parser.add_argument(
163 '-n', '--resource-name', 174 '-n',
175 '--resource-name',
164 metavar='NAME', 176 metavar='NAME',
165 type=str, 177 type=str,
166 dest='rname', 178 dest='rname',
167 required=True, 179 required=True,
168 help='The name of the BEINC-resource on the remote server') 180 help='The name of the BEINC-resource on the remote server',
181 )
169 parser.add_argument( 182 parser.add_argument(
170 '-p', '--password', 183 '-p',
184 '--password',
171 metavar='PASSWORD[FILE]', 185 metavar='PASSWORD[FILE]',
172 type=str, 186 type=str,
173 dest='password', 187 dest='password',
174 default='', 188 default='',
175 help='BEINC taget-password / text-file containing the target password' 189 help='BEINC taget-password / text-file containing the target password'
176 ' (default & recommended: prompt for passwd)') 190 ' (default & recommended: prompt for passwd)',
191 )
177 parser.add_argument( 192 parser.add_argument(
178 '--pull', 193 '--pull',
179 action='store_true', 194 action='store_true',
180 dest='pull', 195 dest='pull',
181 default=False, 196 default=False,
182 help='Perform a pull operation (default: push)') 197 help='Perform a pull operation (default: push)',
198 )
183 parser.add_argument( 199 parser.add_argument(
184 '-T', '--socket-timeout', 200 '-T',
201 '--socket-timeout',
185 metavar='SECONDS', 202 metavar='SECONDS',
186 type=int, 203 type=int,
187 dest='socket_timeout', 204 dest='socket_timeout',
188 default=3, 205 default=3,
189 help='Socket timeout in seconds (0=Python default) (default: 3)') 206 help='Socket timeout in seconds (0=Python default) (default: 3)',
207 )
190 parser.add_argument( 208 parser.add_argument(
191 '-t', '--title', 209 '-t',
210 '--title',
192 metavar='TITLE', 211 metavar='TITLE',
193 type=str, 212 type=str,
194 dest='title', 213 dest='title',
195 default='BEINC title', 214 default='BEINC title',
196 help='BEINC title (default: "BEINC title"') 215 help='BEINC title (default: "BEINC title"',
216 )
197 parser.add_argument( 217 parser.add_argument(
198 '-v', '--version', 218 '-v',
219 '--version',
199 action='version', 220 action='version',
200 version=f'%(prog)s {__version__}', 221 version=f'%(prog)s {__version__}',
201 help='display program-version and exit') 222 help='display program-version and exit',
223 )
202 args = parser.parse_args(inargs) 224 args = parser.parse_args(inargs)
203 if args.socket_timeout: 225 if args.socket_timeout:
204 socket.setdefaulttimeout(args.socket_timeout) 226 socket.setdefaulttimeout(args.socket_timeout)
205 args.password = fetch_password(args.password) 227 args.password = fetch_password(args.password)
206 try: 228 try:
207 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) 229 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
208 context.verify_mode = ssl.CERT_NONE
209 if args.cert: 230 if args.cert:
210 context.verify_mode = ssl.CERT_REQUIRED 231 context.verify_mode = ssl.CERT_REQUIRED
211 context.load_verify_locations(cafile=os.path.expanduser(args.cert)) 232 context.load_verify_locations(cafile=os.path.expanduser(args.cert))
212 context.check_hostname = bool(not args.disable_hostname_check) 233 context.check_hostname = bool(not args.disable_hostname_check)
234 else:
235 context.check_hostname = False
236 context.verify_mode = ssl.CERT_NONE
213 if args.ciphers: 237 if args.ciphers:
214 context.set_ciphers(args.ciphers) 238 context.set_ciphers(args.ciphers)
215 if args.pull: 239 if args.pull:
diff --git a/beinc_pull.py b/beinc_pull.py
index c99cde8..67b6e63 100755
--- a/beinc_pull.py
+++ b/beinc_pull.py
@@ -2,7 +2,7 @@
2# -*- coding: utf-8 -*- 2# -*- coding: utf-8 -*-
3 3
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0
5# Copyright (C) 2013-2020 Simeon Simeonov 5# Copyright (C) 2013-2022 Simeon Simeonov
6 6
7# This program is free software: you can redistribute it and/or modify 7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by 8# it under the terms of the GNU General Public License as published by
@@ -38,7 +38,7 @@ except ImportError:
38 38
39 39
40__author__ = 'Simeon Simeonov' 40__author__ = 'Simeon Simeonov'
41__version__ = '4.1' 41__version__ = '4.2'
42__license__ = 'GPL3' 42__license__ = 'GPL3'
43 43
44 44
@@ -65,7 +65,7 @@ def fetch_password(args_password):
65 sys.exit(errno.EACCES) 65 sys.exit(errno.EACCES)
66 elif os.path.isfile(args_password): 66 elif os.path.isfile(args_password):
67 try: 67 try:
68 with io.open(args_password, 'r') as fp: 68 with io.open(args_password, 'r', encoding='utf-8') as fp:
69 passwd = fp.readline() 69 passwd = fp.readline()
70 if passwd.strip(): 70 if passwd.strip():
71 return passwd.strip() 71 return passwd.strip()
@@ -93,11 +93,14 @@ def display_notification(args, title, message):
93 raise Exception( 93 raise Exception(
94 'Could not load "pynotify".' 94 'Could not load "pynotify".'
95 'Please install "pynotify" or use a different osd-system!' 95 'Please install "pynotify" or use a different osd-system!'
96 'Terminating...') 96 'Terminating...'
97 )
97 if not pynotify.init('BEINC Notify'): 98 if not pynotify.init('BEINC Notify'):
98 raise Exception('There was a problem with libnotify') 99 raise Exception('There was a problem with libnotify')
99 notification_obj = pynotify.Notification(summary=title, 100 notification_obj = pynotify.Notification(
100 message=message) 101 summary=title,
102 message=message,
103 )
101 notification_obj.timeout = 1000 * args.osd_timeout 104 notification_obj.timeout = 1000 * args.osd_timeout
102 notification_obj.set_category('im.received') 105 notification_obj.set_category('im.received')
103 notification_obj.show() 106 notification_obj.show()
@@ -116,35 +119,37 @@ def pull_notifications(scheduler, args):
116 :type args: argparse.Namespace 119 :type args: argparse.Namespace
117 """ 120 """
118 try: 121 try:
119 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) 122 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
120 context.verify_mode = ssl.CERT_NONE
121 if args.cert: 123 if args.cert:
122 context.verify_mode = ssl.CERT_REQUIRED 124 context.verify_mode = ssl.CERT_REQUIRED
123 context.load_verify_locations(cafile=os.path.expanduser(args.cert)) 125 context.load_verify_locations(cafile=os.path.expanduser(args.cert))
124 context.check_hostname = bool(not args.disable_hostname_check) 126 context.check_hostname = bool(not args.disable_hostname_check)
127 else:
128 context.check_hostname = False
129 context.verify_mode = ssl.CERT_NONE
125 if args.ciphers: 130 if args.ciphers:
126 context.set_ciphers(args.ciphers) 131 context.set_ciphers(args.ciphers)
127 response = urllib.request.urlopen( 132 response = urllib.request.urlopen(
128 args.url, 133 args.url,
129 data=urllib.parse.urlencode( 134 data=urllib.parse.urlencode(
130 ( 135 (('resource_name', args.rname), ('password', args.password))
131 ('resource_name', args.rname), 136 ).encode('utf-8'),
132 ('password', args.password)
133 )).encode('utf-8'),
134 timeout=args.socket_timeout, 137 timeout=args.socket_timeout,
135 context=context) 138 context=context,
139 )
136 response_dict = json.loads(response.read().decode('utf-8')) 140 response_dict = json.loads(response.read().decode('utf-8'))
137 if response.code != 200: 141 if response.code != 200:
138 raise socket.error(response_dict.get('message', '')) 142 raise socket.error(response_dict.get('message', ''))
139 for entry in response_dict['data']['messages']: 143 for entry in response_dict['data']['messages']:
140 display_notification(args, 144 display_notification(
141 entry.get('title', ''), 145 args,
142 entry.get('message', '')) 146 entry.get('title', ''),
147 entry.get('message', ''),
148 )
143 response.close() 149 response.close()
144 scheduler.enter(args.frequency, 150 scheduler.enter(
145 1, 151 args.frequency, 1, pull_notifications, (scheduler, args)
146 pull_notifications, 152 )
147 (scheduler, args))
148 except ssl.SSLError as e: 153 except ssl.SSLError as e:
149 eprint(f'BEINC SSL/TLS error: {e}') 154 eprint(f'BEINC SSL/TLS error: {e}')
150 sys.exit(errno.EPERM) 155 sys.exit(errno.EPERM)
@@ -159,88 +164,105 @@ def pull_notifications(scheduler, args):
159def main(inargs=None): 164def main(inargs=None):
160 """main entry""" 165 """main entry"""
161 parser = argparse.ArgumentParser( 166 parser = argparse.ArgumentParser(
162 description='The following options are available') 167 description='The following options are available'
168 )
163 parser.add_argument( 169 parser.add_argument(
164 'url', 170 'url',
165 metavar='URL', 171 metavar='URL',
166 type=str, 172 type=str,
167 help='BEINC server destination URL') 173 help='BEINC server destination URL',
174 )
168 parser.add_argument( 175 parser.add_argument(
169 '-c', '--cert-file', 176 '-c',
177 '--cert-file',
170 metavar='FILE', 178 metavar='FILE',
171 type=str, 179 type=str,
172 dest='cert', 180 dest='cert',
173 default='', 181 default='',
174 help='CA-cert to check the server-cert against ' 182 help='CA-cert to check the server-cert against '
175 '(default: Check disabled)') 183 '(default: Check disabled)',
184 )
176 parser.add_argument( 185 parser.add_argument(
177 '--ciphers', 186 '--ciphers',
178 metavar='CIPHERS', 187 metavar='CIPHERS',
179 type=str, 188 type=str,
180 dest='ciphers', 189 dest='ciphers',
181 default='', 190 default='',
182 help='Preferred ciphers list (default: auto)') 191 help='Preferred ciphers list (default: auto)',
192 )
183 parser.add_argument( 193 parser.add_argument(
184 '--disable-hostname-check', 194 '--disable-hostname-check',
185 action='store_true', 195 action='store_true',
186 dest='disable_hostname_check', 196 dest='disable_hostname_check',
187 default=False, 197 default=False,
188 help='Do not check whether server cert matches server hostname') 198 help='Do not check whether server cert matches server hostname',
199 )
189 parser.add_argument( 200 parser.add_argument(
190 '-f', '--frequency', 201 '-f',
202 '--frequency',
191 metavar='SECONDS', 203 metavar='SECONDS',
192 type=int, 204 type=int,
193 dest='frequency', 205 dest='frequency',
194 default=10, 206 default=10,
195 help='Pulling frequency in seconds (default: 10)') 207 help='Pulling frequency in seconds (default: 10)',
208 )
196 parser.add_argument( 209 parser.add_argument(
197 '-n', '--resource-name', 210 '-n',
211 '--resource-name',
198 metavar='NAME', 212 metavar='NAME',
199 type=str, 213 type=str,
200 dest='rname', 214 dest='rname',
201 required=True, 215 required=True,
202 help='The name of the BEINC-resource on the remote server') 216 help='The name of the BEINC-resource on the remote server',
217 )
203 parser.add_argument( 218 parser.add_argument(
204 '-o', '--osd-system', 219 '-o',
220 '--osd-system',
205 metavar='SYSTEM', 221 metavar='SYSTEM',
206 type=str, 222 type=str,
207 dest='osd_sys', 223 dest='osd_sys',
208 default='pynotify', 224 default='pynotify',
209 help='BEINC osd-system: "pynotify" (default)') 225 help='BEINC osd-system: "pynotify" (default)',
226 )
210 parser.add_argument( 227 parser.add_argument(
211 '-p', '--password', 228 '-p',
229 '--password',
212 metavar='PASSWORD[FILE]', 230 metavar='PASSWORD[FILE]',
213 type=str, 231 type=str,
214 dest='password', 232 dest='password',
215 default='', 233 default='',
216 help='BEINC taget-password / text-file containing the target password' 234 help='BEINC taget-password / text-file containing the target password'
217 ' (default & recommended: prompt for passwd)') 235 ' (default & recommended: prompt for passwd)',
236 )
218 parser.add_argument( 237 parser.add_argument(
219 '-T', '--socket-timeout', 238 '-T',
239 '--socket-timeout',
220 metavar='SECONDS', 240 metavar='SECONDS',
221 type=int, 241 type=int,
222 dest='socket_timeout', 242 dest='socket_timeout',
223 default=3, 243 default=3,
224 help='Socket timeout in seconds (0=Python default) (default: 3)') 244 help='Socket timeout in seconds (0=Python default) (default: 3)',
245 )
225 parser.add_argument( 246 parser.add_argument(
226 '-t', '--osd-timeout', 247 '-t',
248 '--osd-timeout',
227 metavar='SECONDS', 249 metavar='SECONDS',
228 type=int, 250 type=int,
229 dest='osd_timeout', 251 dest='osd_timeout',
230 default=5, 252 default=5,
231 help='OSD timeout (default: 5)') 253 help='OSD timeout (default: 5)',
254 )
232 parser.add_argument( 255 parser.add_argument(
233 '-v', '--version', 256 '-v',
257 '--version',
234 action='version', 258 action='version',
235 version=f'%(prog)s {__version__}', 259 version=f'%(prog)s {__version__}',
236 help='display program-version and exit') 260 help='display program-version and exit',
261 )
237 args = parser.parse_args(inargs) 262 args = parser.parse_args(inargs)
238 args.password = fetch_password(args.password) 263 args.password = fetch_password(args.password)
239 scheduler = sched.scheduler(time.time, time.sleep) 264 scheduler = sched.scheduler(time.time, time.sleep)
240 scheduler.enter(args.frequency, 265 scheduler.enter(args.frequency, 1, pull_notifications, (scheduler, args))
241 1,
242 pull_notifications,
243 (scheduler, args))
244 scheduler.run() 266 scheduler.run()
245 267
246 268
diff --git a/beinc_server.py b/beinc_server.py
index 8957e73..77aa35e 100755
--- a/beinc_server.py
+++ b/beinc_server.py
@@ -2,7 +2,7 @@
2# -*- coding: utf-8 -*- 2# -*- coding: utf-8 -*-
3 3
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0
5# Copyright (C) 2013-2020 Simeon Simeonov 5# Copyright (C) 2013-2022 Simeon Simeonov
6 6
7# This program is free software: you can redistribute it and/or modify 7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by 8# it under the terms of the GNU General Public License as published by
@@ -21,15 +21,15 @@
21import argparse 21import argparse
22import cgi 22import cgi
23import errno 23import errno
24import io
24import json 25import json
25import logging 26import logging
26import os 27import os
27import ssl 28import ssl
28import sys 29import sys
29
30from functools import wraps 30from functools import wraps
31from logging.config import fileConfig
32from http.server import BaseHTTPRequestHandler, HTTPServer 31from http.server import BaseHTTPRequestHandler, HTTPServer
32from logging.config import fileConfig
33 33
34try: 34try:
35 import notify2 as pynotify 35 import notify2 as pynotify
@@ -38,7 +38,7 @@ except ImportError:
38 38
39 39
40__author__ = 'Simeon Simeonov' 40__author__ = 'Simeon Simeonov'
41__version__ = '4.1' 41__version__ = '4.2'
42__license__ = 'GPL3' 42__license__ = 'GPL3'
43 43
44 44
@@ -71,6 +71,7 @@ def eprint(*arg, **kwargs):
71 71
72def beinc_login_required(method): 72def beinc_login_required(method):
73 """Decorator for checking login credentials""" 73 """Decorator for checking login credentials"""
74
74 @wraps(method) 75 @wraps(method)
75 def wrapper(self, data, *arg, **kwargs): 76 def wrapper(self, data, *arg, **kwargs):
76 if data.get('resource_name') is None: 77 if data.get('resource_name') is None:
@@ -84,6 +85,7 @@ def beinc_login_required(method):
84 if not instance.password_match(data.get('password')): 85 if not instance.password_match(data.get('password')):
85 raise BEINCError401('Wrong instance or password') 86 raise BEINCError401('Wrong instance or password')
86 return method(self, data, *arg, **kwargs) 87 return method(self, data, *arg, **kwargs)
88
87 return wrapper 89 return wrapper
88 90
89 91
@@ -105,19 +107,24 @@ class BEINCInstance:
105 self._queue_size = 0 # disable queueing 107 self._queue_size = 0 # disable queueing
106 if pynotify is None: 108 if pynotify is None:
107 eprint('This server does not possess pynotify capability') 109 eprint('This server does not possess pynotify capability')
108 eprint(f'Remove the instance {self._name} or define it with ' 110 eprint(
109 f'"osd_system": "none" or other ' 111 f'Remove the instance {self._name} or define it with '
110 f'available backend') 112 f'"osd_system": "none" or other '
113 f'available backend'
114 )
111 sys.exit(errno.EPERM) 115 sys.exit(errno.EPERM)
112 try: 116 try:
113 self._osd_notification = pynotify.Notification(' ') 117 self._osd_notification = pynotify.Notification(' ')
114 self._osd_notification.timeout = 1000 * int( 118 self._osd_notification.timeout = 1000 * int(
115 instance_dict.get('osd_timeout', 5)) 119 instance_dict.get('osd_timeout', 5)
120 )
116 self._osd_notification.set_category('im.received') 121 self._osd_notification.set_category('im.received')
117 self._osd_type = BEINC_OSD_TYPE_PYNOTIFY 122 self._osd_type = BEINC_OSD_TYPE_PYNOTIFY
118 except Exception as e: 123 except Exception as e:
119 eprint(f'Unable to set up a pynotify notification object ' 124 eprint(
120 f'for "{self._name}" ({e})') 125 f'Unable to set up a pynotify notification object '
126 f'for "{self._name}" ({e})'
127 )
121 sys.exit(errno.EPERM) 128 sys.exit(errno.EPERM)
122 129
123 @property 130 @property
@@ -195,6 +202,7 @@ class BEINCInstance:
195 202
196class BEINCCustomHandler(BaseHTTPRequestHandler): 203class BEINCCustomHandler(BaseHTTPRequestHandler):
197 """Custom handler""" 204 """Custom handler"""
205
198 def do_POST(self): 206 def do_POST(self):
199 """Handle POST requests""" 207 """Handle POST requests"""
200 if self.path.strip('/') not in ('beinc/push', 'beinc/pull'): 208 if self.path.strip('/') not in ('beinc/push', 'beinc/pull'):
@@ -203,14 +211,18 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
203 form = cgi.FieldStorage( 211 form = cgi.FieldStorage(
204 fp=self.rfile, 212 fp=self.rfile,
205 headers=self.headers, 213 headers=self.headers,
206 environ={'REQUEST_METHOD': 'POST', 214 environ={
207 'CONTENT_TYPE': self.headers['Content-Type']}) 215 'REQUEST_METHOD': 'POST',
216 'CONTENT_TYPE': self.headers['Content-Type'],
217 },
218 )
208 # extract all known fields 219 # extract all known fields
209 POST_data = dict( 220 POST_data = dict(
210 resource_name=form.getvalue('resource_name'), 221 resource_name=form.getvalue('resource_name'),
211 password=form.getvalue('password'), 222 password=form.getvalue('password'),
212 title=form.getvalue('title', ''), 223 title=form.getvalue('title', ''),
213 message=form.getvalue('message', '')) 224 message=form.getvalue('message', ''),
225 )
214 try: 226 try:
215 result = {} 227 result = {}
216 if self.path.strip('/') == 'beinc/push': 228 if self.path.strip('/') == 'beinc/push':
@@ -249,10 +261,11 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
249 instance = self.server.instances[data.get('resource_name')] 261 instance = self.server.instances[data.get('resource_name')]
250 try: 262 try:
251 if not instance.queueable: 263 if not instance.queueable:
252 raise BEINCError405( 264 raise BEINCError405('This instance does not support queuing')
253 'This instance does not support queuing') 265 return {
254 return {'message': 'OK. Fetched.', 266 'message': 'OK. Fetched.',
255 'data': {'messages': instance.get_queue()}} 267 'data': {'messages': instance.get_queue()},
268 }
256 except Exception as e: 269 except Exception as e:
257 self._generate_json_error(500, str(e)) 270 self._generate_json_error(500, str(e))
258 271
@@ -270,9 +283,9 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
270 self.send_header('Content-type', 'application/json; charset=utf-8') 283 self.send_header('Content-type', 'application/json; charset=utf-8')
271 self.end_headers() 284 self.end_headers()
272 msg = {'code': code, 'message': message, 'data': {}} 285 msg = {'code': code, 'message': message, 'data': {}}
273 self.wfile.write(json.dumps(msg, 286 self.wfile.write(
274 sort_keys=True, 287 json.dumps(msg, sort_keys=True, indent=4).encode('utf-8')
275 indent=4).encode('utf-8')) 288 )
276 289
277 def _render_to_JSON_response(self, context): 290 def _render_to_JSON_response(self, context):
278 """ 291 """
@@ -284,13 +297,14 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
284 self.send_response(200) 297 self.send_response(200)
285 self.send_header('Content-type', 'application/json; charset=utf-8') 298 self.send_header('Content-type', 'application/json; charset=utf-8')
286 self.end_headers() 299 self.end_headers()
287 self.wfile.write(json.dumps(response, 300 self.wfile.write(
288 sort_keys=True, 301 json.dumps(response, sort_keys=True, indent=4).encode('utf-8')
289 indent=4).encode('utf-8')) 302 )
290 303
291 304
292class BEINCNotifyServer(HTTPServer): 305class BEINCNotifyServer(HTTPServer):
293 """BEINCNotifyServer class""" 306 """BEINCNotifyServer class"""
307
294 def __init__(self, *arg, **kwargs): 308 def __init__(self, *arg, **kwargs):
295 """Default constructor""" 309 """Default constructor"""
296 super().__init__(*arg, **kwargs) 310 super().__init__(*arg, **kwargs)
@@ -319,9 +333,7 @@ class BEINCNotifyServer(HTTPServer):
319 self._instances[instance['name']] = BEINCInstance(instance) 333 self._instances[instance['name']] = BEINCInstance(instance)
320 logger.info('Instance %s added', instance['name']) 334 logger.info('Instance %s added', instance['name'])
321 except Exception as e: 335 except Exception as e:
322 eprint('Unable to create instance "{0}": {1}'.format( 336 eprint(f"Unable to create instance \"{instance['name']}\": {e}")
323 instance['name'],
324 e))
325 sys.exit(1) 337 sys.exit(1)
326 338
327 @property 339 @property
@@ -334,51 +346,66 @@ class BEINCNotifyServer(HTTPServer):
334 346
335if __name__ == '__main__': 347if __name__ == '__main__':
336 parser = argparse.ArgumentParser( 348 parser = argparse.ArgumentParser(
337 description='The following options are available') 349 description='The following options are available'
350 )
338 parser.add_argument( 351 parser.add_argument(
339 '-H', '--hostname', 352 '-H',
353 '--hostname',
340 metavar='HOSTNAME', 354 metavar='HOSTNAME',
341 type=str, 355 type=str,
342 dest='hostname', 356 dest='hostname',
343 default='127.0.0.1', 357 default='127.0.0.1',
344 help='BEINC server IP / hostname (default: 127.0.0.1)') 358 help='BEINC server IP / hostname (default: 127.0.0.1)',
359 )
345 parser.add_argument( 360 parser.add_argument(
346 '-L', '--logger-name', 361 '-L',
362 '--logger-name',
347 metavar='NAME', 363 metavar='NAME',
348 type=str, 364 type=str,
349 dest='logger_name', 365 dest='logger_name',
350 default='', 366 default='',
351 help="BEINC logger name (default: 'beinc')") 367 help="BEINC logger name (default: 'beinc')",
368 )
352 parser.add_argument( 369 parser.add_argument(
353 '-l', '--logger-config', 370 '-l',
371 '--logger-config',
354 metavar='CONFIG', 372 metavar='CONFIG',
355 type=str, 373 type=str,
356 dest='logger_config', 374 dest='logger_config',
357 default=os.path.expanduser('~/.beinc_server_logger.ini'), 375 default=os.path.expanduser('~/.beinc_server_logger.ini'),
358 help=('BEINC logger config (.ini) ' 376 help=(
359 '(default: ~/.beinc_server_logger.ini)')) 377 'BEINC logger config (.ini) '
378 '(default: ~/.beinc_server_logger.ini)'
379 ),
380 )
360 parser.add_argument( 381 parser.add_argument(
361 '-p', '--port', 382 '-p',
383 '--port',
362 metavar='PORT', 384 metavar='PORT',
363 type=int, 385 type=int,
364 dest='port', 386 dest='port',
365 default=9998, 387 default=9998,
366 help='BEINC server port (default: 9998)') 388 help='BEINC server port (default: 9998)',
389 )
367 parser.add_argument( 390 parser.add_argument(
368 '-f', '--config-file', 391 '-f',
392 '--config-file',
369 metavar='FILE', 393 metavar='FILE',
370 type=str, 394 type=str,
371 default=os.path.expanduser('~/.beinc_server.json'), 395 default=os.path.expanduser('~/.beinc_server.json'),
372 dest='config_file', 396 dest='config_file',
373 help='BEINC config file (default: ~/.beinc_server.json)') 397 help='BEINC config file (default: ~/.beinc_server.json)',
398 )
374 parser.add_argument( 399 parser.add_argument(
375 '-v', '--version', 400 '-v',
401 '--version',
376 action='version', 402 action='version',
377 version=f'%(prog)s {__version__}', 403 version=f'%(prog)s {__version__}',
378 help='Display program-version and exit') 404 help='Display program-version and exit',
405 )
379 args = parser.parse_args() 406 args = parser.parse_args()
380 try: 407 try:
381 with open(args.config_file, 'r') as fp: 408 with io.open(args.config_file, 'r', encoding='utf-8') as fp:
382 config_dict = json.load(fp) 409 config_dict = json.load(fp)
383 except Exception as e: 410 except Exception as e:
384 eprint(f'Unable to parse {args.config_file}: {e}') 411 eprint(f'Unable to parse {args.config_file}: {e}')
@@ -390,35 +417,42 @@ if __name__ == '__main__':
390 else: 417 else:
391 logging.basicConfig( 418 logging.basicConfig(
392 format='%(asctime)s - %(levelname)s - %(message)s', 419 format='%(asctime)s - %(levelname)s - %(message)s',
393 level=logging.DEBUG) 420 level=logging.DEBUG,
421 )
394 logger = logging.getLogger('beinc') 422 logger = logging.getLogger('beinc')
395 logger.info('BEINC starting. Loading config...') 423 logger.info('BEINC starting. Loading config...')
396 if config_dict.get('config_version') != BEINC_CURRENT_CONFIG_VERSION: 424 if config_dict.get('config_version') != BEINC_CURRENT_CONFIG_VERSION:
397 eprint( 425 eprint(
398 'WARNING: The version of the config-file: {0} ({1}) ' 426 f'WARNING: The version of the config-file: {args.config_file} '
399 'does not correspond to the latest version supported ' 427 f'({config_dict.get("config_version", "Not set")}) does not '
400 'by this program ({2})\nCheck beinc_config_sample.json ' 428 'correspond to the latest version supported by this program '
401 'for the newest features!'.format( 429 f'({BEINC_CURRENT_CONFIG_VERSION})\n'
402 args.config_file, 430 'Check beinc_config_sample.json for the newest features!'
403 config_dict.get('config_version', 'Not set'), 431 )
404 BEINC_CURRENT_CONFIG_VERSION))
405 ssl_certificate = config_dict['server']['general'].get( 432 ssl_certificate = config_dict['server']['general'].get(
406 'ssl_certificate') 433 'ssl_certificate'
434 )
407 ssl_private_key = config_dict['server']['general'].get( 435 ssl_private_key = config_dict['server']['general'].get(
408 'ssl_private_key') 436 'ssl_private_key'
437 )
409 ssl_acceptable_ciphers_str = config_dict['server']['general'].get( 438 ssl_acceptable_ciphers_str = config_dict['server']['general'].get(
410 'ssl_ciphers') 439 'ssl_ciphers'
411 beinc_server = BEINCNotifyServer((args.hostname, args.port), 440 )
412 BEINCCustomHandler) 441 beinc_server = BEINCNotifyServer(
442 (args.hostname, args.port),
443 BEINCCustomHandler,
444 )
413 beinc_server.set_config(config_dict) 445 beinc_server.set_config(config_dict)
414 if ssl_certificate and ssl_private_key: 446 if ssl_certificate and ssl_private_key:
415 beinc_server.socket = ssl.wrap_socket( 447 context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
416 beinc_server.socket, 448 context.load_cert_chain(
417 keyfile=ssl_private_key, 449 certfile=ssl_certificate, keyfile=ssl_private_key
418 certfile=ssl_certificate, 450 )
419 server_side=True, 451 if ssl_acceptable_ciphers_str is not None:
420 ssl_version=ssl.PROTOCOL_TLSv1_2, 452 context.set_ciphers(ssl_acceptable_ciphers_str)
421 ciphers=ssl_acceptable_ciphers_str) 453 beinc_server.socket = context.wrap_socket(
454 beinc_server.socket, server_side=True
455 )
422 logger.info('Done!') 456 logger.info('Done!')
423 beinc_server.serve_forever() 457 beinc_server.serve_forever()
424 except KeyboardInterrupt: 458 except KeyboardInterrupt:
diff --git a/beinc_weechat.py b/beinc_weechat.py
index 75af86a..2288b74 100644
--- a/beinc_weechat.py
+++ b/beinc_weechat.py
@@ -1,7 +1,7 @@
1# -*- coding: utf-8 -*- 1# -*- coding: utf-8 -*-
2 2
3# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 3# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0
4# Copyright (C) 2013-2020 Simeon Simeonov 4# Copyright (C) 2013-2022 Simeon Simeonov
5 5
6# This program is free software: you can redistribute it and/or modify 6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by 7# it under the terms of the GNU General Public License as published by
@@ -17,6 +17,7 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>. 17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18"""BEINC client for Weechat""" 18"""BEINC client for Weechat"""
19import datetime 19import datetime
20import io
20import json 21import json
21import os 22import os
22import socket 23import socket
@@ -26,9 +27,8 @@ import urllib.request
26 27
27import weechat 28import weechat
28 29
29
30__author__ = 'Simeon Simeonov' 30__author__ = 'Simeon Simeonov'
31__version__ = '4.1' 31__version__ = '4.2'
32__license__ = 'GPL3' 32__license__ = 'GPL3'
33 33
34 34
@@ -61,38 +61,46 @@ class WeechatTarget:
61 if self._url == '': 61 if self._url == '':
62 raise Exception('"target_url" not defined for target') 62 raise Exception('"target_url" not defined for target')
63 self._password = target_dict.get('target_password', '') 63 self._password = target_dict.get('target_password', '')
64 self._pm_title_template = target_dict.get('pm_title_template', 64 self._pm_title_template = target_dict.get(
65 '%s @ %S') 65 'pm_title_template', '%s @ %S'
66 self._pm_message_template = target_dict.get('pm_message_template', 66 )
67 '%m') 67 self._pm_message_template = target_dict.get(
68 self._cm_title_template = target_dict.get('cm_title_template', 68 'pm_message_template', '%m'
69 '%c @ %S') 69 )
70 self._cm_message_template = target_dict.get('cm_message_template', 70 self._cm_title_template = target_dict.get(
71 '%s -> %m') 71 'cm_title_template', '%c @ %S'
72 self._nm_title_template = target_dict.get('nm_title_template', 72 )
73 '%c @ %S') 73 self._cm_message_template = target_dict.get(
74 self._nm_message_template = target_dict.get('nm_message_template', 74 'cm_message_template', '%s -> %m'
75 '%s -> %m') 75 )
76 self._nm_title_template = target_dict.get(
77 'nm_title_template', '%c @ %S'
78 )
79 self._nm_message_template = target_dict.get(
80 'nm_message_template', '%s -> %m'
81 )
76 self._chans = set(target_dict.get('channel_list', [])) 82 self._chans = set(target_dict.get('channel_list', []))
77 self._nicks = set(target_dict.get('nick_list', [])) 83 self._nicks = set(target_dict.get('nick_list', []))
78 self._chan_messages_policy = int(target_dict.get( 84 self._chan_messages_policy = int(
79 'channel_messages_policy', 85 target_dict.get('channel_messages_policy', BEINC_POLICY_LIST_ONLY)
80 BEINC_POLICY_LIST_ONLY)) 86 )
81 self._priv_messages_policy = int(target_dict.get( 87 self._priv_messages_policy = int(
82 'private_messages_policy', 88 target_dict.get('private_messages_policy', BEINC_POLICY_ALL)
83 BEINC_POLICY_ALL)) 89 )
84 self._notifications_policy = int(target_dict.get( 90 self._notifications_policy = int(
85 'notifications_policy', 91 target_dict.get('notifications_policy', BEINC_POLICY_ALL)
86 BEINC_POLICY_ALL)) 92 )
87 self._cert_file = target_dict.get('target_cert_file') 93 self._cert_file = target_dict.get('target_cert_file')
88 self._timestamp_format = target_dict.get('target_timestamp_format', 94 self._timestamp_format = target_dict.get(
89 '%H:%M:%S') 95 'target_timestamp_format', '%H:%M:%S'
96 )
90 self._debug = bool(target_dict.get('debug', False)) 97 self._debug = bool(target_dict.get('debug', False))
91 self._enabled = bool(target_dict.get('enabled', True)) 98 self._enabled = bool(target_dict.get('enabled', True))
92 self._socket_timeout = int(target_dict.get('socket_timeout', 3)) 99 self._socket_timeout = int(target_dict.get('socket_timeout', 3))
93 self._ssl_ciphers = target_dict.get('ssl_ciphers', '') 100 self._ssl_ciphers = target_dict.get('ssl_ciphers', '')
94 self._disable_hostname_check = bool( 101 self._disable_hostname_check = bool(
95 target_dict.get('disable-hostname-check', False)) 102 target_dict.get('disable-hostname-check', False)
103 )
96 self._ssl_version = target_dict.get('ssl_version', 'auto') 104 self._ssl_version = target_dict.get('ssl_version', 'auto')
97 self._last_message = None # datetime.datetime instance 105 self._last_message = None # datetime.datetime instance
98 self._context = None 106 self._context = None
@@ -143,26 +151,22 @@ class WeechatTarget:
143 last_message = 'never' 151 last_message = 'never'
144 if self._last_message is not None: 152 if self._last_message is not None:
145 last_message = self._last_message.strftime('%Y-%m-%d %H:%M:%S') 153 last_message = self._last_message.strftime('%Y-%m-%d %H:%M:%S')
146 return ('name: {0}\nurl: {1}\nenabled: {2}\nchannel_list: {3}\n' 154 return (
147 'nick_list: {4}\nchannel_messages_policy: {5}\n' 155 f'name: {self._name}\nurl: {self._url}\n'
148 'private_messages_policy: {6}\nnotifications_policy: {7}\n' 156 f"enabled: {'yes' if self._enabled else 'no'}\n"
149 'last message: {8}\nsocket timeout: {9}\nssl-version: {10}\n' 157 f"channel_list: {', '.join(self._chans)}\n"
150 'ciphers: {11}\ndisable hostname check: {12}\n' 158 f"nick_list: {', '.join(self._nicks)}\n"
151 'debug: {13}\n\n'.format( 159 f'channel_messages_policy: {self._chan_messages_policy}\n'
152 self._name, 160 f'private_messages_policy: {self._priv_messages_policy}\n'
153 self._url, 161 f'notifications_policy: {self._notifications_policy}\n'
154 'yes' if self._enabled else 'no', 162 f'last message: {last_message}\n'
155 ', '.join(self._chans), 163 f'socket timeout: {self._socket_timeout}\n'
156 ', '.join(self._nicks), 164 f'ssl-version: {self._ssl_version}\n'
157 self._chan_messages_policy, 165 f"ciphers: {self._ssl_ciphers or 'auto'}\n"
158 self._priv_messages_policy, 166 "disable hostname check: "
159 self._notifications_policy, 167 f"{'yes' if self._disable_hostname_check else 'no'}\n"
160 last_message, 168 f"debug: {'yes' if self._debug else 'no'}\n\n"
161 self._socket_timeout, 169 )
162 self._ssl_version,
163 self._ssl_ciphers or 'auto',
164 'yes' if self._disable_hostname_check else 'no',
165 'yes' if self._debug else 'no'))
166 170
167 def send_private_message_notification(self, values): 171 def send_private_message_notification(self, values):
168 """ 172 """
@@ -172,20 +176,22 @@ class WeechatTarget:
172 :type value: dict 176 :type value: dict
173 """ 177 """
174 try: 178 try:
175 title = self._fetch_formatted_str(self._pm_title_template, 179 title = self._fetch_formatted_str(self._pm_title_template, values)
176 values)
177 message = self._fetch_formatted_str( 180 message = self._fetch_formatted_str(
178 self._pm_message_template, 181 self._pm_message_template,
179 values) 182 values,
183 )
180 if not self._send_beinc_message(title, message) and self._debug: 184 if not self._send_beinc_message(title, message) and self._debug:
181 beinc_prnt( 185 beinc_prnt(
182 f'BEINC DEBUG: send_private_message_notification-ERROR ' 186 f'BEINC DEBUG: send_private_message_notification-ERROR '
183 f'for "{self._name}": _send_beinc_message -> False') 187 f'for "{self._name}": _send_beinc_message -> False'
188 )
184 except Exception as e: 189 except Exception as e:
185 if self._debug: 190 if self._debug:
186 beinc_prnt( 191 beinc_prnt(
187 f'BEINC DEBUG: send_private_message_notification-ERROR ' 192 f'BEINC DEBUG: send_private_message_notification-ERROR '
188 f'for "{self._name}": {e}') 193 f'for "{self._name}": {e}'
194 )
189 195
190 def send_channel_message_notification(self, values): 196 def send_channel_message_notification(self, values):
191 """ 197 """
@@ -195,19 +201,21 @@ class WeechatTarget:
195 :type value: dict 201 :type value: dict
196 """ 202 """
197 try: 203 try:
198 title = self._fetch_formatted_str(self._cm_title_template, 204 title = self._fetch_formatted_str(self._cm_title_template, values)
199 values) 205 message = self._fetch_formatted_str(
200 message = self._fetch_formatted_str(self._cm_message_template, 206 self._cm_message_template, values
201 values) 207 )
202 if not self._send_beinc_message(title, message) and self._debug: 208 if not self._send_beinc_message(title, message) and self._debug:
203 beinc_prnt( 209 beinc_prnt(
204 f'BEINC DEBUG: send_channel_message_notification-ERROR ' 210 f'BEINC DEBUG: send_channel_message_notification-ERROR '
205 f'for "{self._name}": _send_beinc_message -> False') 211 f'for "{self._name}": _send_beinc_message -> False'
212 )
206 except Exception as e: 213 except Exception as e:
207 if self._debug: 214 if self._debug:
208 beinc_prnt( 215 beinc_prnt(
209 f'BEINC DEBUG: send_channel_message_notification-ERROR ' 216 f'BEINC DEBUG: send_channel_message_notification-ERROR '
210 f'for "{self._name}": {e}') 217 f'for "{self._name}": {e}'
218 )
211 219
212 def send_notify_message_notification(self, values): 220 def send_notify_message_notification(self, values):
213 """ 221 """
@@ -217,19 +225,21 @@ class WeechatTarget:
217 :type value: dict 225 :type value: dict
218 """ 226 """
219 try: 227 try:
220 title = self._fetch_formatted_str(self._nm_title_template, 228 title = self._fetch_formatted_str(self._nm_title_template, values)
221 values) 229 message = self._fetch_formatted_str(
222 message = self._fetch_formatted_str(self._nm_message_template, 230 self._nm_message_template, values
223 values) 231 )
224 if not self._send_beinc_message(title, message) and self._debug: 232 if not self._send_beinc_message(title, message) and self._debug:
225 beinc_prnt( 233 beinc_prnt(
226 f'BEINC DEBUG: send_notify_message_notification-ERROR ' 234 f'BEINC DEBUG: send_notify_message_notification-ERROR '
227 f'for "{self._name}": _send_beinc_message -> False') 235 f'for "{self._name}": _send_beinc_message -> False'
236 )
228 except Exception as e: 237 except Exception as e:
229 if self._debug: 238 if self._debug:
230 beinc_prnt( 239 beinc_prnt(
231 f'BEINC DEBUG: send_notify_message_notification-ERROR ' 240 f'BEINC DEBUG: send_notify_message_notification-ERROR '
232 f'for "{self._name}": {e}') 241 f'for "{self._name}": {e}'
242 )
233 243
234 def send_broadcast_notification(self, message): 244 def send_broadcast_notification(self, message):
235 """ 245 """
@@ -244,26 +254,30 @@ class WeechatTarget:
244 if not self._send_beinc_message(title, message) and self._debug: 254 if not self._send_beinc_message(title, message) and self._debug:
245 beinc_prnt( 255 beinc_prnt(
246 f'BEINC DEBUG: send_broadcast_notification-ERROR ' 256 f'BEINC DEBUG: send_broadcast_notification-ERROR '
247 f'for "{self._name}": _send_beinc_message -> False') 257 f'for "{self._name}": _send_beinc_message -> False'
258 )
248 except Exception as e: 259 except Exception as e:
249 if self._debug: 260 if self._debug:
250 beinc_prnt( 261 beinc_prnt(
251 f'BEINC DEBUG: send_broadcast_notification-ERROR ' 262 f'BEINC DEBUG: send_broadcast_notification-ERROR '
252 f'for "{self._name}": {e}') 263 f'for "{self._name}": {e}'
264 )
253 265
254 def _context_setup(self): 266 def _context_setup(self):
255 """Sets up the SSL context""" 267 """Sets up the SSL context"""
256 if self._context is not None: 268 if self._context is not None:
257 return True 269 return True
258 try: 270 try:
259 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) 271 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
260 context.verify_mode = ssl.CERT_NONE
261 if self._cert_file: 272 if self._cert_file:
262 context.verify_mode = ssl.CERT_REQUIRED 273 context.verify_mode = ssl.CERT_REQUIRED
263 context.load_verify_locations(cafile=os.path.expanduser( 274 context.load_verify_locations(
264 self._cert_file)) 275 cafile=os.path.expanduser(self._cert_file)
265 context.check_hostname = bool( 276 )
266 not self._disable_hostname_check) 277 context.check_hostname = bool(not self._disable_hostname_check)
278 else:
279 context.check_hostname = False
280 context.verify_mode = ssl.CERT_NONE
267 if self._ssl_ciphers and self._ssl_ciphers != 'auto': 281 if self._ssl_ciphers and self._ssl_ciphers != 'auto':
268 context.set_ciphers(self._ssl_ciphers) 282 context.set_ciphers(self._ssl_ciphers)
269 self._context = context 283 self._context = context
@@ -292,13 +306,15 @@ class WeechatTarget:
292 :rtype: str 306 :rtype: str
293 """ 307 """
294 timestamp = datetime.datetime.now().strftime(self._timestamp_format) 308 timestamp = datetime.datetime.now().strftime(self._timestamp_format)
295 replacements = {'%S': values['server'], 309 replacements = {
296 '%s': values['source_nick'], 310 '%S': values['server'],
297 '%c': values['channel'], 311 '%s': values['source_nick'],
298 '%m': values['message'], 312 '%c': values['channel'],
299 '%t': timestamp, 313 '%m': values['message'],
300 '%p': 'BEINC', 314 '%t': timestamp,
301 '%n': values['own_nick']} 315 '%p': 'BEINC',
316 '%n': values['own_nick'],
317 }
302 for key, value in replacements.items(): 318 for key, value in replacements.items():
303 template = template.replace(key, value) 319 template = template.replace(key, value)
304 return template 320 return template
@@ -327,16 +343,20 @@ class WeechatTarget:
327 ('resource_name', self._name), 343 ('resource_name', self._name),
328 ('password', self._password), 344 ('password', self._password),
329 ('title', title), 345 ('title', title),
330 ('message', message) 346 ('message', message),
331 )).encode('utf-8'), 347 )
348 ).encode('utf-8'),
332 timeout=self._socket_timeout, 349 timeout=self._socket_timeout,
333 context=self._context) 350 context=self._context,
351 )
334 response_dict = json.loads(response.read().decode('utf-8')) 352 response_dict = json.loads(response.read().decode('utf-8'))
335 if response.code != 200: 353 if response.code != 200:
336 raise socket.error(response_dict.get('message', '')) 354 raise socket.error(response_dict.get('message', ''))
337 if self._debug: 355 if self._debug:
338 beinc_prnt('BEINC DEBUG: Server responded: {0}'.format( 356 beinc_prnt(
339 response_dict.get('message'))) 357 "BEINC DEBUG: Server responded: "
358 f"{response_dict.get('message')}"
359 )
340 self._last_message = datetime.datetime.now() 360 self._last_message = datetime.datetime.now()
341 return True 361 return True
342 except ssl.SSLError as e: 362 except ssl.SSLError as e:
@@ -378,7 +398,7 @@ def beinc_cmd_target_handler(cmd_tokens):
378 if cmd_tokens[0] == 'list': 398 if cmd_tokens[0] == 'list':
379 beinc_prnt('--- Globals ---') 399 beinc_prnt('--- Globals ---')
380 for key, value in global_values.items(): 400 for key, value in global_values.items():
381 beinc_prnt('{key} -> {value}'.format(key=key, value=str(value))) 401 beinc_prnt(f'{key} -> {str(value)}')
382 beinc_prnt('--- Targets ---') 402 beinc_prnt('--- Targets ---')
383 for target in target_list: 403 for target in target_list:
384 beinc_prnt(str(target)) 404 beinc_prnt(str(target))
@@ -430,8 +450,10 @@ def beinc_command(data, buffer_obj, args):
430 elif cmd_tokens[0] == 'target': 450 elif cmd_tokens[0] == 'target':
431 return beinc_cmd_target_handler(cmd_tokens[1:]) 451 return beinc_cmd_target_handler(cmd_tokens[1:])
432 else: 452 else:
433 beinc_prnt('syntax: /beinc < on | off | reload |' 453 beinc_prnt(
434 ' broadcast <text> | target <action> >') 454 'syntax: /beinc < on | off | reload |'
455 ' broadcast <text> | target <action> >'
456 )
435 return weechat.WEECHAT_RC_OK 457 return weechat.WEECHAT_RC_OK
436 458
437 459
@@ -439,8 +461,9 @@ def beinc_privmsg_handler(data, signal, signal_data):
439 """Callback function the *PRIVMSG* IRC messages hooked by Weechat""" 461 """Callback function the *PRIVMSG* IRC messages hooked by Weechat"""
440 if not enabled: 462 if not enabled:
441 return weechat.WEECHAT_RC_OK 463 return weechat.WEECHAT_RC_OK
442 prvmsg_dict = weechat.info_get_hashtable('irc_message_parse', 464 prvmsg_dict = weechat.info_get_hashtable(
443 {'message': signal_data}) 465 'irc_message_parse', {'message': signal_data}
466 )
444 # packing the privmsg handler values 467 # packing the privmsg handler values
445 ph_values = {} 468 ph_values = {}
446 ph_values['server'] = signal.split(',')[0] 469 ph_values['server'] = signal.split(',')[0]
@@ -448,7 +471,8 @@ def beinc_privmsg_handler(data, signal, signal_data):
448 ph_values['channel'] = prvmsg_dict['arguments'].split(':')[0].strip() 471 ph_values['channel'] = prvmsg_dict['arguments'].split(':')[0].strip()
449 ph_values['source_nick'] = prvmsg_dict['nick'] 472 ph_values['source_nick'] = prvmsg_dict['nick']
450 ph_values['message'] = ':'.join( 473 ph_values['message'] = ':'.join(
451 prvmsg_dict['arguments'].split(':')[1:]).strip() 474 prvmsg_dict['arguments'].split(':')[1:]
475 ).strip()
452 if ph_values['channel'] == ph_values['own_nick']: 476 if ph_values['channel'] == ph_values['own_nick']:
453 # priv messages are handled here 477 # priv messages are handled here
454 if not global_values['global_private_messages_policy']: 478 if not global_values['global_private_messages_policy']:
@@ -458,10 +482,10 @@ def beinc_privmsg_handler(data, signal, signal_data):
458 continue 482 continue
459 p_messages_policy = target.private_messages_policy 483 p_messages_policy = target.private_messages_policy
460 if p_messages_policy == BEINC_POLICY_ALL or ( 484 if p_messages_policy == BEINC_POLICY_ALL or (
461 p_messages_policy == BEINC_POLICY_LIST_ONLY and 485 p_messages_policy == BEINC_POLICY_LIST_ONLY
462 '{0}.{1}'.format( 486 and f"{ph_values['server']}.{ph_values['source_nick'].lower()}"
463 ph_values['server'], 487 in target.nicks
464 ph_values['source_nick'].lower()) in target.nicks): 488 ):
465 target.send_private_message_notification(ph_values) 489 target.send_private_message_notification(ph_values)
466 elif ph_values['own_nick'].lower() in ph_values['message'].lower(): 490 elif ph_values['own_nick'].lower() in ph_values['message'].lower():
467 # notify messages are handled here 491 # notify messages are handled here
@@ -471,10 +495,9 @@ def beinc_privmsg_handler(data, signal, signal_data):
471 if not target.enabled: 495 if not target.enabled:
472 continue 496 continue
473 if target.notifications_policy == BEINC_POLICY_ALL or ( 497 if target.notifications_policy == BEINC_POLICY_ALL or (
474 target.notifications_policy == BEINC_POLICY_LIST_ONLY and 498 target.notifications_policy == BEINC_POLICY_LIST_ONLY
475 '{0}.{1}'.format( 499 and f"{ph_values['server']}.{ph_values['channel'].lower()}"
476 ph_values['server'], 500 in target.chans
477 ph_values['channel'].lower()) in target.chans
478 ): 501 ):
479 target.send_notify_message_notification(ph_values) 502 target.send_notify_message_notification(ph_values)
480 elif global_values['global_channel_messages_policy']: 503 elif global_values['global_channel_messages_policy']:
@@ -486,10 +509,10 @@ def beinc_privmsg_handler(data, signal, signal_data):
486 continue 509 continue
487 c_messages_policy = target.channel_messages_policy 510 c_messages_policy = target.channel_messages_policy
488 if c_messages_policy == BEINC_POLICY_ALL or ( 511 if c_messages_policy == BEINC_POLICY_ALL or (
489 c_messages_policy == BEINC_POLICY_LIST_ONLY and 512 c_messages_policy == BEINC_POLICY_LIST_ONLY
490 '{0}.{1}'.format( 513 and f"{ph_values['server']}.{ph_values['channel'].lower()}"
491 ph_values['server'], 514 in target.chans
492 ph_values['channel'].lower()) in target.chans): 515 ):
493 target.send_channel_message_notification(ph_values) 516 target.send_channel_message_notification(ph_values)
494 return weechat.WEECHAT_RC_OK 517 return weechat.WEECHAT_RC_OK
495 518
@@ -517,24 +540,28 @@ def beinc_init():
517 try: 540 try:
518 beinc_config_file_str = os.path.join( 541 beinc_config_file_str = os.path.join(
519 weechat.info_get('weechat_dir', ''), 542 weechat.info_get('weechat_dir', ''),
520 'beinc_weechat.json') 543 'beinc_weechat.json',
544 )
521 beinc_prnt(f'Parsing {beinc_config_file_str}...') 545 beinc_prnt(f'Parsing {beinc_config_file_str}...')
522 custom_error = 'load error' 546 custom_error = 'load error'
523 with open(beinc_config_file_str, 'r') as fp: 547 with io.open(beinc_config_file_str, 'r', encoding='utf-8') as fp:
524 config_dict = json.load(fp) 548 config_dict = json.load(fp)
525 custom_error = 'target parse error' 549 custom_error = 'target parse error'
526 global_values['use_current_buffer'] = bool( 550 global_values['use_current_buffer'] = bool(
527 config_dict['irc_client'].get( 551 config_dict['irc_client'].get('use_current_buffer', False)
528 'use_current_buffer', False)) 552 )
529 if config_dict.get('config_version', 553 if (
530 0) != BEINC_CURRENT_CONFIG_VERSION: 554 config_dict.get('config_version', 0)
531 beinc_prnt('WARNING: The version of the config-file: {0} ({1}) ' 555 != BEINC_CURRENT_CONFIG_VERSION
532 'does not correspond to the latest version supported ' 556 ):
533 'by this program ({2})\nCheck beinc_config_sample.json ' 557 beinc_prnt(
534 'for the newest features!'.format( 558 "WARNING: The version of the config-file: "
535 beinc_config_file_str, 559 f"{beinc_config_file_str} "
536 config_dict.get('config_version', 0), 560 f"({config_dict.get('config_version', 0)}) "
537 BEINC_CURRENT_CONFIG_VERSION)) 561 "does not correspond to the latest version supported "
562 f"by this program ({BEINC_CURRENT_CONFIG_VERSION})\n"
563 "Check beinc_config_sample.json for the newest features!"
564 )
538 for target in config_dict['irc_client']['targets']: 565 for target in config_dict['irc_client']['targets']:
539 try: 566 try:
540 new_target = WeechatTarget(target) 567 new_target = WeechatTarget(target)
@@ -551,8 +578,10 @@ def beinc_init():
551 beinc_prnt(f'BEINC target "{new_target.name}" added') 578 beinc_prnt(f'BEINC target "{new_target.name}" added')
552 beinc_prnt('Done!') 579 beinc_prnt('Done!')
553 except Exception as e: 580 except Exception as e:
554 beinc_prnt(f'ERROR: unable to parse {beinc_config_file_str}: ' 581 beinc_prnt(
555 f'{custom_error} - {e}\nBEINC is now disabled') 582 f'ERROR: unable to parse {beinc_config_file_str}: '
583 f'{custom_error} - {e}\nBEINC is now disabled'
584 )
556 enabled = False 585 enabled = False
557 # do not return error / exit the script 586 # do not return error / exit the script
558 # in order to give a smoother opportunity to fix a 'broken' config 587 # in order to give a smoother opportunity to fix a 'broken' config
@@ -567,19 +596,24 @@ weechat.register(
567 __license__, 596 __license__,
568 'Blackmore\'s Extended IRC Notification Collection (Weechat Client)', 597 'Blackmore\'s Extended IRC Notification Collection (Weechat Client)',
569 '', 598 '',
570 '') 599 '',
600)
571version = weechat.info_get('version_number', '') or 0 601version = weechat.info_get('version_number', '') or 0
572if int(version) < 0x00040000: 602if int(version) < 0x00040000:
573 weechat.prnt('', 'WeeChat version >= 0.4.0 is required to run beinc') 603 weechat.prnt('', 'WeeChat version >= 0.4.0 is required to run beinc')
574else: 604else:
575 weechat.hook_command('beinc', 605 weechat.hook_command(
576 'BEINC command', ('< broadcast <message> | on | off |' 606 'beinc',
577 ' reload | target <action> >'), 607 'BEINC command',
578 ('Available target actions:\n' 608 '< broadcast <message> | on | off | reload | target <action> >',
579 'disable <target name>\nenable <target name>\nlist'), 609 (
580 'None', 610 'Available target actions:\n'
581 'beinc_command', 611 'disable <target name>\nenable <target name>\nlist'
582 '') 612 ),
613 'None',
614 'beinc_command',
615 '',
616 )
583 weechat.hook_signal('*,irc_in2_privmsg', 'beinc_privmsg_handler', '') 617 weechat.hook_signal('*,irc_in2_privmsg', 'beinc_privmsg_handler', '')
584 beinc_init() 618 beinc_init()
585 weechat.prnt('', 'beinc initiated!') 619 weechat.prnt('', 'beinc initiated!')