summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ruff.toml109
-rw-r--r--Changelog4
-rw-r--r--README.md8
-rwxr-xr-xbeinc_generic_client.py41
-rwxr-xr-xbeinc_pull.py42
-rwxr-xr-xbeinc_server.py118
-rw-r--r--beinc_weechat.py66
7 files changed, 237 insertions, 151 deletions
diff --git a/.ruff.toml b/.ruff.toml
new file mode 100644
index 0000000..1037228
--- /dev/null
+++ b/.ruff.toml
@@ -0,0 +1,109 @@
1cache-dir = "~/.cache/ruff"
2indent-width = 4
3line-length = 79
4target-version = "py39"
5
6[lint]
7select = ["ALL", "D101", "D102", "D103", "D104"]
8ignore = [
9 "ANN",
10 "BLE001",
11 "COM812",
12 "D",
13 "EM101", # Exception must not use a string literal, assign to variable first
14 "EM102", # Exception must not use an f-string literal, assign to variable first
15 "ERA001",
16 "FBT001",
17 "FBT002",
18 "INP001",
19 "ISC001",
20 "N802",
21 "N806",
22 "PLR2004",
23 "PTH111",
24 "RUF012",
25 "RUF013",
26 "S101",
27 "T201",
28 "TRY003",
29 "TRY300",
30 "UP020"
31]
32
33# D101 - Missing docstring in public class
34# D102 - Missing docstring in public method
35# D200 - One-line docstring should fit on one line
36# D203 - 1 blank line required before class docstring
37# D205 - 1 blank line required between summary line and description
38# D403 - First word of the first line should be capitalized: `str` -> `Str`
39# FBT001 - Boolean-typed positional argument in function definition
40# FBT002 - Boolean default positional argument in function definition
41# INP001 - File `beinc_weechat.py` is part of an implicit namespace package. Add an `__init__.py`
42# N802 - Function name `do_GET` should be lowercase
43# N806 - Variable `POST_data` in function should be lowercase
44# PTH111 - `os.path.expanduser()` should be replaced by `Path.expanduser()`
45# PLR2004 - Magic value used in comparison, consider replacing `200` with a constant variable
46# RUF012 - Mutable class attributes should be annotated with `typing.ClassVar`
47# RUF013 - PEP 484 prohibits implicit `Optional`
48# S101 - Use of `assert` detected
49# T201 - `print` found
50# TRY003 - Avoid specifying long messages outside the exception class
51# TRY300 - Consider moving this statement to an `else` block
52# UP020 - Use builtin `open`
53
54# Allow fix for all enabled rules (when `--fix`) is provided.
55fixable = ["ALL"]
56unfixable = []
57
58# custom settings
59[lint.per-file-ignores]
60"beinc_generic_client.py" = [
61 "S310" # Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected
62]
63"beinc_pull.py" = [
64 "S310", # Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected
65 "TRY002", # Create your own exception
66 "TRY301" # Abstract `raise` to an inner function
67]
68"beinc_weechat.py" = [
69 "ARG001", # Unused function argument: `data`
70 "C901", # `beinc_cmd_target_handler` is too complex (13 > 10)
71 "DTZ005", # `datetime.datetime.now()` called without a `tz` argument
72 "PLR0912", # Too many branches (16 > 12)
73 "PLW0603", # Using the global statement to update `global_values` is discouraged
74 "PTH118", # `os.path.join()` should be replaced by `Path` with `/` operator
75 "S310", # Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected
76 "TRY002", # Create your own exception
77 "TRY301" # Abstract `raise` to an inner function
78]
79
80
81[format]
82# Like Black, use double quotes for strings.
83quote-style = "single"
84
85# Like Black, indent with spaces, rather than tabs.
86indent-style = "space"
87
88# Like Black, respect magic trailing commas.
89skip-magic-trailing-comma = true
90
91# Like Black, automatically detect the appropriate line ending.
92line-ending = "auto"
93
94# Enable auto-formatting of code examples in docstrings. Markdown,
95# reStructuredText code/literal blocks and doctests are all supported.
96#
97# This is currently disabled by default, but it is planned for this
98# to be opt-out in the future.
99docstring-code-format = false
100
101# Set the line length limit used when formatting code snippets in
102# docstrings.
103#
104# This only has an effect when the `docstring-code-format` setting is
105# enabled.
106docstring-code-line-length = "dynamic"
107
108[lint.flake8-quotes]
109inline-quotes = "single"
diff --git a/Changelog b/Changelog
index 6692d08..a7f8b6b 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,7 @@
1v4.4:
2 - replace the functionality from the deprecated cgi module
3 - do some more format / linting (add .ruff.toml)
4
1v4.3: 5v4.3:
2 - remove obsolete SSL/TLS code 6 - remove obsolete SSL/TLS code
3 - use black alike coding style 7 - use black alike coding style
diff --git a/README.md b/README.md
index 1994c15..f7a446c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
1Copyright (C) 2014-2022 - Simeon Simeonov 1Copyright (C) 2014-2024 - Simeon Simeonov
2See the end of the file for license conditions. 2See the end of the file for license conditions.
3 3
4 4
@@ -90,13 +90,11 @@ your URL will be: https://hostname:port
90## Supported systems & requirements 90## Supported systems & requirements
91 91
92Any system running the software required for the selected components. 92Any system running the software required for the selected components.
93All components tested on: Gentoo GNU/Linux, 93All components tested on: Gentoo GNU/Linux, FreeBSD (12.x, 13.x)
94 Ubuntu GNU/Linux 18.4,
95 FreeBSD 12.x
96 94
97 95
98### Requirements 96### Requirements
99All components: Python >= 3.6.* 97All components: Python >= 3.9.*
100 98
101beinc_server.py: pynotify >= 0.1 (optional) 99beinc_server.py: pynotify >= 0.1 (optional)
102beinc_weechat.py: Weechat >= 0.4.0 100beinc_weechat.py: Weechat >= 0.4.0
diff --git a/beinc_generic_client.py b/beinc_generic_client.py
index 476287e..44e8698 100755
--- a/beinc_generic_client.py
+++ b/beinc_generic_client.py
@@ -1,8 +1,7 @@
1#!/usr/bin/env python 1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3 2
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.3 3# Blackmore's Enhanced IRC-Notification Collection (BEINC)
5# Copyright (C) 2013-2022 Simeon Simeonov 4# Copyright (C) 2013-2024 Simeon Simeonov
6 5
7# 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
8# 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,12 +16,14 @@
17# You should have received a copy of the GNU General Public License 16# You should have received a copy of the GNU General Public License
18# 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/>.
19"""A generic BEINC client that can be used for testing or as a template""" 18"""A generic BEINC client that can be used for testing or as a template"""
19
20import argparse 20import argparse
21import errno 21import errno
22import getpass 22import getpass
23import io 23import io
24import json 24import json
25import os 25import os
26import pathlib
26import socket 27import socket
27import ssl 28import ssl
28import sys 29import sys
@@ -30,7 +31,7 @@ import urllib.parse
30import urllib.request 31import urllib.request
31 32
32__author__ = 'Simeon Simeonov' 33__author__ = 'Simeon Simeonov'
33__version__ = '4.3' 34__version__ = '4.4'
34__license__ = 'GPL3' 35__license__ = 'GPL3'
35 36
36 37
@@ -55,14 +56,14 @@ def fetch_password(args_password):
55 except KeyboardInterrupt: 56 except KeyboardInterrupt:
56 eprint(os.linesep + 'Prompt terminated') 57 eprint(os.linesep + 'Prompt terminated')
57 sys.exit(errno.EACCES) 58 sys.exit(errno.EACCES)
58 elif os.path.isfile(args_password): 59 elif pathlib.Path(args_password).is_file():
59 try: 60 try:
60 with io.open(args_password, 'r', encoding='utf-8') as fp: 61 with io.open(args_password, 'r', encoding='utf-8') as fp:
61 passwd = fp.readline() 62 passwd = fp.readline()
62 if passwd.strip(): 63 if passwd.strip():
63 return passwd.strip() 64 return passwd.strip()
64 except Exception as e: 65 except Exception as exp:
65 eprint(f'Unable to open password file: {e}') 66 eprint(f'Unable to open password file: {exp}')
66 sys.exit(1) 67 sys.exit(1)
67 return args_password 68 return args_password
68 69
@@ -83,17 +84,14 @@ def pull_notifications(ssl_context, args):
83 response = urllib.request.urlopen( 84 response = urllib.request.urlopen(
84 args.url, 85 args.url,
85 data=urllib.parse.urlencode( 86 data=urllib.parse.urlencode(
86 ( 87 (('resource_name', args.rname), ('password', args.password))
87 ('resource_name', args.rname),
88 ('password', args.password),
89 )
90 ).encode('utf-8'), 88 ).encode('utf-8'),
91 timeout=args.socket_timeout, 89 timeout=args.socket_timeout,
92 context=ssl_context, 90 context=ssl_context,
93 ) 91 )
94 response_dict = json.loads(response.read().decode('utf-8')) 92 response_dict = json.loads(response.read().decode('utf-8'))
95 if response.code != 200: 93 if response.code != 200:
96 raise socket.error(response_dict.get('message', '')) 94 raise OSError(response_dict.get('message', ''))
97 return response_dict['data']['messages'] 95 return response_dict['data']['messages']
98 96
99 97
@@ -122,7 +120,7 @@ def push_notification(ssl_context, args):
122 ) 120 )
123 response_dict = json.loads(response.read().decode('utf-8')) 121 response_dict = json.loads(response.read().decode('utf-8'))
124 if response.code != 200: 122 if response.code != 200:
125 raise socket.error(response_dict.get('message', '')) 123 raise OSError(response_dict.get('message', ''))
126 124
127 125
128def main(inargs=None): 126def main(inargs=None):
@@ -131,10 +129,7 @@ def main(inargs=None):
131 description='The following options are available' 129 description='The following options are available'
132 ) 130 )
133 parser.add_argument( 131 parser.add_argument(
134 'url', 132 'url', metavar='URL', type=str, help='BEINC server destination URL'
135 metavar='URL',
136 type=str,
137 help='BEINC server destination URL',
138 ) 133 )
139 parser.add_argument( 134 parser.add_argument(
140 '-c', 135 '-c',
@@ -242,14 +237,14 @@ def main(inargs=None):
242 push_notification(context, args) 237 push_notification(context, args)
243 print('OK') 238 print('OK')
244 sys.exit(0) 239 sys.exit(0)
245 except ssl.SSLError as e: 240 except ssl.SSLError as err:
246 eprint(f'BEINC SSL/TLS error: {e}') 241 eprint(f'BEINC SSL/TLS error: {err}')
247 sys.exit(errno.EPERM) 242 sys.exit(errno.EPERM)
248 except socket.error as e: 243 except OSError as err:
249 eprint(f'BEINC connection error: {e}') 244 eprint(f'BEINC connection error: {err}')
250 sys.exit(errno.EPERM) 245 sys.exit(errno.EPERM)
251 except Exception as e: 246 except Exception as exp:
252 eprint(f'BEINC generic client error: {e}') 247 eprint(f'BEINC generic client error: {exp}')
253 sys.exit(errno.EPERM) 248 sys.exit(errno.EPERM)
254 249
255 250
diff --git a/beinc_pull.py b/beinc_pull.py
index 118a08a..6ce9c2f 100755
--- a/beinc_pull.py
+++ b/beinc_pull.py
@@ -1,8 +1,7 @@
1#!/usr/bin/env python 1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3 2
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.3 3# Blackmore's Enhanced IRC-Notification Collection (BEINC)
5# Copyright (C) 2013-2022 Simeon Simeonov 4# Copyright (C) 2013-2024 Simeon Simeonov
6 5
7# 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
8# 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,14 +16,15 @@
17# You should have received a copy of the GNU General Public License 16# You should have received a copy of the GNU General Public License
18# 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/>.
19"""A simple client that pulls notifications from a BEINC server""" 18"""A simple client that pulls notifications from a BEINC server"""
19
20import argparse 20import argparse
21import errno 21import errno
22import getpass 22import getpass
23import io 23import io
24import json 24import json
25import os 25import os
26import pathlib
26import sched 27import sched
27import socket
28import ssl 28import ssl
29import sys 29import sys
30import time 30import time
@@ -38,7 +38,7 @@ except ImportError:
38 38
39 39
40__author__ = 'Simeon Simeonov' 40__author__ = 'Simeon Simeonov'
41__version__ = '4.3' 41__version__ = '4.4'
42__license__ = 'GPL3' 42__license__ = 'GPL3'
43 43
44 44
@@ -63,14 +63,14 @@ def fetch_password(args_password):
63 except KeyboardInterrupt: 63 except KeyboardInterrupt:
64 eprint(os.linesep + 'Prompt terminated') 64 eprint(os.linesep + 'Prompt terminated')
65 sys.exit(errno.EACCES) 65 sys.exit(errno.EACCES)
66 elif os.path.isfile(args_password): 66 elif pathlib.Path(args_password).is_file():
67 try: 67 try:
68 with io.open(args_password, 'r', encoding='utf-8') 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()
72 except Exception as e: 72 except Exception as exp:
73 eprint(f'Unable to open password file: {e}') 73 eprint(f'Unable to open password file: {exp}')
74 sys.exit(1) 74 sys.exit(1)
75 return args_password 75 return args_password
76 76
@@ -98,8 +98,7 @@ def display_notification(args, title, message):
98 if not pynotify.init('BEINC Notify'): 98 if not pynotify.init('BEINC Notify'):
99 raise Exception('There was a problem with libnotify') 99 raise Exception('There was a problem with libnotify')
100 notification_obj = pynotify.Notification( 100 notification_obj = pynotify.Notification(
101 summary=title, 101 summary=title, message=message
102 message=message,
103 ) 102 )
104 notification_obj.timeout = 1000 * args.osd_timeout 103 notification_obj.timeout = 1000 * args.osd_timeout
105 notification_obj.set_category('im.received') 104 notification_obj.set_category('im.received')
@@ -139,25 +138,23 @@ def pull_notifications(scheduler, args):
139 ) 138 )
140 response_dict = json.loads(response.read().decode('utf-8')) 139 response_dict = json.loads(response.read().decode('utf-8'))
141 if response.code != 200: 140 if response.code != 200:
142 raise socket.error(response_dict.get('message', '')) 141 raise OSError(response_dict.get('message', ''))
143 for entry in response_dict['data']['messages']: 142 for entry in response_dict['data']['messages']:
144 display_notification( 143 display_notification(
145 args, 144 args, entry.get('title', ''), entry.get('message', '')
146 entry.get('title', ''),
147 entry.get('message', ''),
148 ) 145 )
149 response.close() 146 response.close()
150 scheduler.enter( 147 scheduler.enter(
151 args.frequency, 1, pull_notifications, (scheduler, args) 148 args.frequency, 1, pull_notifications, (scheduler, args)
152 ) 149 )
153 except ssl.SSLError as e: 150 except ssl.SSLError as err:
154 eprint(f'BEINC SSL/TLS error: {e}') 151 eprint(f'BEINC SSL/TLS error: {err}')
155 sys.exit(errno.EPERM) 152 sys.exit(errno.EPERM)
156 except socket.error as e: 153 except OSError as err:
157 eprint(f'BEINC connection error: {e}') 154 eprint(f'BEINC connection error: {err}')
158 sys.exit(errno.EPERM) 155 sys.exit(errno.EPERM)
159 except Exception as e: 156 except Exception as exp:
160 eprint(f'BEINC generic client error: {e}') 157 eprint(f'BEINC generic client error: {exp}')
161 sys.exit(errno.EPERM) 158 sys.exit(errno.EPERM)
162 159
163 160
@@ -167,10 +164,7 @@ def main(inargs=None):
167 description='The following options are available' 164 description='The following options are available'
168 ) 165 )
169 parser.add_argument( 166 parser.add_argument(
170 'url', 167 'url', metavar='URL', type=str, help='BEINC server destination URL'
171 metavar='URL',
172 type=str,
173 help='BEINC server destination URL',
174 ) 168 )
175 parser.add_argument( 169 parser.add_argument(
176 '-c', 170 '-c',
diff --git a/beinc_server.py b/beinc_server.py
index 0098fba..647d7f1 100755
--- a/beinc_server.py
+++ b/beinc_server.py
@@ -1,8 +1,7 @@
1#!/usr/bin/env python 1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3 2
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.3 3# Blackmore's Enhanced IRC-Notification Collection (BEINC)
5# Copyright (C) 2013-2023 Simeon Simeonov 4# Copyright (C) 2013-2024 Simeon Simeonov
6 5
7# 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
8# 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
@@ -16,14 +15,16 @@
16 15
17# You should have received a copy of the GNU General Public License 16# You should have received a copy of the GNU General Public License
18# 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/>.
19 18"""BEINC standalone server implementation"""
20 19
21import argparse 20import argparse
21import contextlib
22import errno 22import errno
23import io 23import io
24import json 24import json
25import logging 25import logging
26import os 26import os
27import pathlib
27import ssl 28import ssl
28import sys 29import sys
29import urllib.parse 30import urllib.parse
@@ -38,7 +39,7 @@ except ImportError:
38 39
39 40
40__author__ = 'Simeon Simeonov' 41__author__ = 'Simeon Simeonov'
41__version__ = '4.3' 42__version__ = '4.4'
42__license__ = 'GPL3' 43__license__ = 'GPL3'
43 44
44 45
@@ -48,24 +49,24 @@ BEINC_OSD_TYPE_PYNOTIFY = 1
48BEINC_CURRENT_CONFIG_VERSION = 3 49BEINC_CURRENT_CONFIG_VERSION = 3
49 50
50 51
51class BEINCError400(Exception): 52class BEINC400Error(Exception):
52 """BEINCError400""" 53 """BEINC400Error"""
53 54
54 55
55class BEINCError401(Exception): 56class BEINC401Error(Exception):
56 """BEINCError401""" 57 """BEINC401Error"""
57 58
58 59
59class BEINCError403(Exception): 60class BEINC403Error(Exception):
60 """BEINCError403""" 61 """BEINC403Error"""
61 62
62 63
63class BEINCError404(Exception): 64class BEINC404Error(Exception):
64 """BEINCError404""" 65 """BEINC404Error"""
65 66
66 67
67class BEINCError405(Exception): 68class BEINC405Error(Exception):
68 """BEINCError405""" 69 """BEINC405Error"""
69 70
70 71
71def eprint(*arg, **kwargs): 72def eprint(*arg, **kwargs):
@@ -79,15 +80,15 @@ def beinc_login_required(method):
79 @wraps(method) 80 @wraps(method)
80 def wrapper(self, data, *arg, **kwargs): 81 def wrapper(self, data, *arg, **kwargs):
81 if data.get('resource_name') is None: 82 if data.get('resource_name') is None:
82 raise BEINCError403('Resource-name missing') 83 raise BEINC403Error('Resource-name missing')
83 if data.get('password') is None: 84 if data.get('password') is None:
84 raise BEINCError401('Password missing') 85 raise BEINC401Error('Password missing')
85 try: 86 try:
86 instance = self.server.instances[data.get('resource_name')] 87 instance = self.server.instances[data.get('resource_name')]
87 except Exception: 88 except Exception:
88 raise BEINCError401('Wrong instance or password') from None 89 raise BEINC401Error('Wrong instance or password') from None
89 if not instance.password_match(data.get('password')): 90 if not instance.password_match(data.get('password')):
90 raise BEINCError401('Wrong instance or password') 91 raise BEINC401Error('Wrong instance or password')
91 return method(self, data, *arg, **kwargs) 92 return method(self, data, *arg, **kwargs)
92 93
93 return wrapper 94 return wrapper
@@ -124,10 +125,10 @@ class BEINCInstance:
124 ) 125 )
125 self._osd_notification.set_category('im.received') 126 self._osd_notification.set_category('im.received')
126 self._osd_type = BEINC_OSD_TYPE_PYNOTIFY 127 self._osd_type = BEINC_OSD_TYPE_PYNOTIFY
127 except Exception as e: 128 except Exception as exp:
128 eprint( 129 eprint(
129 f'Unable to set up a pynotify notification object ' 130 f'Unable to set up a pynotify notification object '
130 f'for "{self._name}" ({e})' 131 f'for "{self._name}" ({exp})'
131 ) 132 )
132 sys.exit(errno.EPERM) 133 sys.exit(errno.EPERM)
133 134
@@ -217,13 +218,11 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
217 if headers is None: 218 if headers is None:
218 headers = {} 219 headers = {}
219 if 'content-length' in headers: 220 if 'content-length' in headers:
220 try: 221 with contextlib.suppress(ValueError):
221 clen = int(headers['content-length']) 222 clen = int(headers['content-length'])
222 except ValueError:
223 pass
224 return dict(urllib.parse.parse_qsl(fp.read(clen).decode('utf-8'))) 223 return dict(urllib.parse.parse_qsl(fp.read(clen).decode('utf-8')))
225 except Exception as e: 224 except Exception as exp:
226 raise BEINCError400('Invalid POST request') from e 225 raise BEINC400Error('Invalid POST request') from exp
227 226
228 def do_POST(self): 227 def do_POST(self):
229 """Handle POST requests""" 228 """Handle POST requests"""
@@ -245,18 +244,18 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
245 elif self.path.strip('/') == 'beinc/pull': 244 elif self.path.strip('/') == 'beinc/pull':
246 result = self._handle_pull(POST_data) 245 result = self._handle_pull(POST_data)
247 self._render_to_JSON_response(result) 246 self._render_to_JSON_response(result)
248 except BEINCError400 as e: 247 except BEINC400Error as err:
249 self._generate_json_error(400, str(e)) 248 self._generate_json_error(400, str(err))
250 except BEINCError401 as e: 249 except BEINC401Error as err:
251 self._generate_json_error(401, str(e)) 250 self._generate_json_error(401, str(err))
252 except BEINCError403 as e: 251 except BEINC403Error as err:
253 self._generate_json_error(403, str(e)) 252 self._generate_json_error(403, str(err))
254 except BEINCError404 as e: 253 except BEINC404Error as err:
255 self._generate_json_error(404, str(e)) 254 self._generate_json_error(404, str(err))
256 except BEINCError405 as e: 255 except BEINC405Error as err:
257 self._generate_json_error(405, str(e)) 256 self._generate_json_error(405, str(err))
258 except Exception as e: 257 except Exception as exp:
259 self._generate_json_error(500, f'Unexpected error: {e}') 258 self._generate_json_error(500, f'Unexpected error: {exp}')
260 259
261 def do_GET(self): 260 def do_GET(self):
262 """Handle GET Requests""" 261 """Handle GET Requests"""
@@ -266,25 +265,19 @@ class BEINCCustomHandler(BaseHTTPRequestHandler):
266 def _handle_push(self, data): 265 def _handle_push(self, data):
267 """Handle push""" 266 """Handle push"""
268 instance = self.server.instances[data.get('resource_name')] 267 instance = self.server.instances[data.get('resource_name')]
269 try: 268 instance.send_message(data.get('title'), data.get('message'))
270 instance.send_message(data.get('title'), data.get('message')) 269 return {'message': 'OK. Sent.'}
271 return {'message': 'OK. Sent.'}
272 except Exception as e:
273 self._generate_json_error(500, str(e))
274 270
275 @beinc_login_required 271 @beinc_login_required
276 def _handle_pull(self, data): 272 def _handle_pull(self, data):
277 """Handle pull""" 273 """Handle pull"""
278 instance = self.server.instances[data.get('resource_name')] 274 instance = self.server.instances[data.get('resource_name')]
279 try: 275 if not instance.queueable:
280 if not instance.queueable: 276 raise BEINC405Error('This instance does not support queuing')
281 raise BEINCError405('This instance does not support queuing') 277 return {
282 return { 278 'message': 'OK. Fetched.',
283 'message': 'OK. Fetched.', 279 'data': {'messages': instance.get_queue()},
284 'data': {'messages': instance.get_queue()}, 280 }
285 }
286 except Exception as e:
287 self._generate_json_error(500, str(e))
288 281
289 def _generate_json_error(self, code, message): 282 def _generate_json_error(self, code, message):
290 """ 283 """
@@ -349,15 +342,13 @@ class BEINCNotifyServer(HTTPServer):
349 for instance in self._config['server']['instances']: 342 for instance in self._config['server']['instances']:
350 self._instances[instance['name']] = BEINCInstance(instance) 343 self._instances[instance['name']] = BEINCInstance(instance)
351 logger.info('Instance %s added', instance['name']) 344 logger.info('Instance %s added', instance['name'])
352 except Exception as e: 345 except Exception as exp:
353 eprint(f"Unable to create instance \"{instance['name']}\": {e}") 346 eprint(f"Unable to create instance \"{instance['name']}\": {exp}")
354 sys.exit(1) 347 sys.exit(1)
355 348
356 @property 349 @property
357 def instances(self): 350 def instances(self):
358 """ 351 """a property that returns the instance list (read-only)"""
359 a property that returns the instance list (read-only)
360 """
361 return self._instances 352 return self._instances
362 353
363 354
@@ -424,11 +415,11 @@ if __name__ == '__main__':
424 try: 415 try:
425 with io.open(args.config_file, 'r', encoding='utf-8') as fp: 416 with io.open(args.config_file, 'r', encoding='utf-8') as fp:
426 config_dict = json.load(fp) 417 config_dict = json.load(fp)
427 except Exception as e: 418 except Exception as exp:
428 eprint(f'Unable to parse {args.config_file}: {e}') 419 eprint(f'Unable to parse {args.config_file}: {exp}')
429 sys.exit(errno.EIO) 420 sys.exit(errno.EIO)
430 try: 421 try:
431 if os.path.isfile(args.logger_config): 422 if pathlib.Path(args.logger_config).is_file():
432 fileConfig(args.logger_config) 423 fileConfig(args.logger_config)
433 logger = logging.getLogger(args.logger_name) 424 logger = logging.getLogger(args.logger_name)
434 else: 425 else:
@@ -456,8 +447,7 @@ if __name__ == '__main__':
456 'ssl_ciphers' 447 'ssl_ciphers'
457 ) 448 )
458 beinc_server = BEINCNotifyServer( 449 beinc_server = BEINCNotifyServer(
459 (args.hostname, args.port), 450 (args.hostname, args.port), BEINCCustomHandler
460 BEINCCustomHandler,
461 ) 451 )
462 beinc_server.set_config(config_dict) 452 beinc_server.set_config(config_dict)
463 if ssl_certificate and ssl_private_key: 453 if ssl_certificate and ssl_private_key:
@@ -474,7 +464,7 @@ if __name__ == '__main__':
474 beinc_server.serve_forever() 464 beinc_server.serve_forever()
475 except KeyboardInterrupt: 465 except KeyboardInterrupt:
476 print('\n\nTerminating...') 466 print('\n\nTerminating...')
477 except Exception as e: 467 except Exception as exp:
478 eprint(f'BEINCServer critical error: {e}') 468 eprint(f'BEINCServer critical error: {exp}')
479 sys.exit(1) 469 sys.exit(1)
480 sys.exit(0) 470 sys.exit(0)
diff --git a/beinc_weechat.py b/beinc_weechat.py
index b3550d8..cdbfe1f 100644
--- a/beinc_weechat.py
+++ b/beinc_weechat.py
@@ -1,7 +1,5 @@
1# -*- coding: utf-8 -*- 1# Blackmore's Enhanced IRC-Notification Collection (BEINC)
2 2# Copyright (C) 2013-2024 Simeon Simeonov
3# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.3
4# Copyright (C) 2013-2022 Simeon Simeonov
5 3
6# This program is free software: you can redistribute it and/or modify 4# 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 5# it under the terms of the GNU General Public License as published by
@@ -16,11 +14,11 @@
16# You should have received a copy of the GNU General Public License 14# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>. 15# along with this program. If not, see <http://www.gnu.org/licenses/>.
18"""BEINC client for Weechat""" 16"""BEINC client for Weechat"""
17
19import datetime 18import datetime
20import io 19import io
21import json 20import json
22import os 21import os
23import socket
24import ssl 22import ssl
25import urllib.parse 23import urllib.parse
26import urllib.request 24import urllib.request
@@ -28,7 +26,7 @@ import urllib.request
28import weechat 26import weechat
29 27
30__author__ = 'Simeon Simeonov' 28__author__ = 'Simeon Simeonov'
31__version__ = '4.3' 29__version__ = '4.4'
32__license__ = 'GPL3' 30__license__ = 'GPL3'
33 31
34 32
@@ -178,19 +176,18 @@ class WeechatTarget:
178 try: 176 try:
179 title = self._fetch_formatted_str(self._pm_title_template, values) 177 title = self._fetch_formatted_str(self._pm_title_template, values)
180 message = self._fetch_formatted_str( 178 message = self._fetch_formatted_str(
181 self._pm_message_template, 179 self._pm_message_template, values
182 values,
183 ) 180 )
184 if not self._send_beinc_message(title, message) and self._debug: 181 if not self._send_beinc_message(title, message) and self._debug:
185 beinc_prnt( 182 beinc_prnt(
186 f'BEINC DEBUG: send_private_message_notification-ERROR ' 183 f'BEINC DEBUG: send_private_message_notification-ERROR '
187 f'for "{self._name}": _send_beinc_message -> False' 184 f'for "{self._name}": _send_beinc_message -> False'
188 ) 185 )
189 except Exception as e: 186 except Exception as exp:
190 if self._debug: 187 if self._debug:
191 beinc_prnt( 188 beinc_prnt(
192 f'BEINC DEBUG: send_private_message_notification-ERROR ' 189 f'BEINC DEBUG: send_private_message_notification-ERROR '
193 f'for "{self._name}": {e}' 190 f'for "{self._name}": {exp}'
194 ) 191 )
195 192
196 def send_channel_message_notification(self, values): 193 def send_channel_message_notification(self, values):
@@ -210,11 +207,11 @@ class WeechatTarget:
210 f'BEINC DEBUG: send_channel_message_notification-ERROR ' 207 f'BEINC DEBUG: send_channel_message_notification-ERROR '
211 f'for "{self._name}": _send_beinc_message -> False' 208 f'for "{self._name}": _send_beinc_message -> False'
212 ) 209 )
213 except Exception as e: 210 except Exception as exp:
214 if self._debug: 211 if self._debug:
215 beinc_prnt( 212 beinc_prnt(
216 f'BEINC DEBUG: send_channel_message_notification-ERROR ' 213 f'BEINC DEBUG: send_channel_message_notification-ERROR '
217 f'for "{self._name}": {e}' 214 f'for "{self._name}": {exp}'
218 ) 215 )
219 216
220 def send_notify_message_notification(self, values): 217 def send_notify_message_notification(self, values):
@@ -234,11 +231,11 @@ class WeechatTarget:
234 f'BEINC DEBUG: send_notify_message_notification-ERROR ' 231 f'BEINC DEBUG: send_notify_message_notification-ERROR '
235 f'for "{self._name}": _send_beinc_message -> False' 232 f'for "{self._name}": _send_beinc_message -> False'
236 ) 233 )
237 except Exception as e: 234 except Exception as exp:
238 if self._debug: 235 if self._debug:
239 beinc_prnt( 236 beinc_prnt(
240 f'BEINC DEBUG: send_notify_message_notification-ERROR ' 237 f'BEINC DEBUG: send_notify_message_notification-ERROR '
241 f'for "{self._name}": {e}' 238 f'for "{self._name}": {exp}'
242 ) 239 )
243 240
244 def send_broadcast_notification(self, message): 241 def send_broadcast_notification(self, message):
@@ -256,11 +253,11 @@ class WeechatTarget:
256 f'BEINC DEBUG: send_broadcast_notification-ERROR ' 253 f'BEINC DEBUG: send_broadcast_notification-ERROR '
257 f'for "{self._name}": _send_beinc_message -> False' 254 f'for "{self._name}": _send_beinc_message -> False'
258 ) 255 )
259 except Exception as e: 256 except Exception as exp:
260 if self._debug: 257 if self._debug:
261 beinc_prnt( 258 beinc_prnt(
262 f'BEINC DEBUG: send_broadcast_notification-ERROR ' 259 f'BEINC DEBUG: send_broadcast_notification-ERROR '
263 f'for "{self._name}": {e}' 260 f'for "{self._name}": {exp}'
264 ) 261 )
265 262
266 def _context_setup(self): 263 def _context_setup(self):
@@ -282,12 +279,12 @@ class WeechatTarget:
282 context.set_ciphers(self._ssl_ciphers) 279 context.set_ciphers(self._ssl_ciphers)
283 self._context = context 280 self._context = context
284 return True 281 return True
285 except ssl.SSLError as e: 282 except ssl.SSLError as err:
286 if self._debug: 283 if self._debug:
287 beinc_prnt(f'BEINC DEBUG: SSL/TLS error: {e}\n') 284 beinc_prnt(f'BEINC DEBUG: SSL/TLS error: {err}\n')
288 except Exception as e: 285 except Exception as exp:
289 if self._debug: 286 if self._debug:
290 beinc_prnt(f'BEINC DEBUG: Generic context error: {e}\n') 287 beinc_prnt(f'BEINC DEBUG: Generic context error: {exp}\n')
291 self._context = None 288 self._context = None
292 return False 289 return False
293 290
@@ -351,7 +348,7 @@ class WeechatTarget:
351 ) 348 )
352 response_dict = json.loads(response.read().decode('utf-8')) 349 response_dict = json.loads(response.read().decode('utf-8'))
353 if response.code != 200: 350 if response.code != 200:
354 raise socket.error(response_dict.get('message', '')) 351 raise OSError(response_dict.get('message', ''))
355 if self._debug: 352 if self._debug:
356 beinc_prnt( 353 beinc_prnt(
357 "BEINC DEBUG: Server responded: " 354 "BEINC DEBUG: Server responded: "
@@ -359,15 +356,15 @@ class WeechatTarget:
359 ) 356 )
360 self._last_message = datetime.datetime.now() 357 self._last_message = datetime.datetime.now()
361 return True 358 return True
362 except ssl.SSLError as e: 359 except ssl.SSLError as err:
363 if self._debug: 360 if self._debug:
364 beinc_prnt(f'BEINC DEBUG: SSL/TLS error: {e}\n') 361 beinc_prnt(f'BEINC DEBUG: SSL/TLS error: {err}\n')
365 except socket.error as e: 362 except OSError as err:
366 if self._debug: 363 if self._debug:
367 beinc_prnt(f'BEINC DEBUG: Connection error: {e}\n') 364 beinc_prnt(f'BEINC DEBUG: Connection error: {err}\n')
368 except Exception as e: 365 except Exception as exp:
369 if self._debug: 366 if self._debug:
370 beinc_prnt(f'BEINC DEBUG: Unable to send message: {e}\n') 367 beinc_prnt(f'BEINC DEBUG: Unable to send message: {exp}\n')
371 return False 368 return False
372 369
373 370
@@ -398,7 +395,7 @@ def beinc_cmd_target_handler(cmd_tokens):
398 if cmd_tokens[0] == 'list': 395 if cmd_tokens[0] == 'list':
399 beinc_prnt('--- Globals ---') 396 beinc_prnt('--- Globals ---')
400 for key, value in global_values.items(): 397 for key, value in global_values.items():
401 beinc_prnt(f'{key} -> {str(value)}') 398 beinc_prnt(f'{key} -> {value}')
402 beinc_prnt('--- Targets ---') 399 beinc_prnt('--- Targets ---')
403 for target in target_list: 400 for target in target_list:
404 beinc_prnt(str(target)) 401 beinc_prnt(str(target))
@@ -539,8 +536,7 @@ def beinc_init():
539 536
540 try: 537 try:
541 beinc_config_file_str = os.path.join( 538 beinc_config_file_str = os.path.join(
542 weechat.info_get('weechat_dir', ''), 539 weechat.info_get('weechat_dir', ''), 'beinc_weechat.json'
543 'beinc_weechat.json',
544 ) 540 )
545 beinc_prnt(f'Parsing {beinc_config_file_str}...') 541 beinc_prnt(f'Parsing {beinc_config_file_str}...')
546 custom_error = 'load error' 542 custom_error = 'load error'
@@ -565,8 +561,8 @@ def beinc_init():
565 for target in config_dict['irc_client']['targets']: 561 for target in config_dict['irc_client']['targets']:
566 try: 562 try:
567 new_target = WeechatTarget(target) 563 new_target = WeechatTarget(target)
568 except Exception as e: 564 except Exception as exp:
569 beinc_prnt(f'Unable to add target: {e}') 565 beinc_prnt(f'Unable to add target: {exp}')
570 continue 566 continue
571 if new_target.channel_messages_policy: 567 if new_target.channel_messages_policy:
572 global_values['global_channel_messages_policy'] = True 568 global_values['global_channel_messages_policy'] = True
@@ -577,10 +573,10 @@ def beinc_init():
577 target_list.append(new_target) 573 target_list.append(new_target)
578 beinc_prnt(f'BEINC target "{new_target.name}" added') 574 beinc_prnt(f'BEINC target "{new_target.name}" added')
579 beinc_prnt('Done!') 575 beinc_prnt('Done!')
580 except Exception as e: 576 except Exception as exp:
581 beinc_prnt( 577 beinc_prnt(
582 f'ERROR: unable to parse {beinc_config_file_str}: ' 578 f'ERROR: unable to parse {beinc_config_file_str}: '
583 f'{custom_error} - {e}\nBEINC is now disabled' 579 f'{custom_error} - {exp}\nBEINC is now disabled'
584 ) 580 )
585 enabled = False 581 enabled = False
586 # do not return error / exit the script 582 # do not return error / exit the script
@@ -594,7 +590,7 @@ weechat.register(
594 __author__, 590 __author__,
595 __version__, 591 __version__,
596 __license__, 592 __license__,
597 'Blackmore\'s Extended IRC Notification Collection (Weechat Client)', 593 "Blackmore's Extended IRC Notification Collection (Weechat Client)",
598 '', 594 '',
599 '', 595 '',
600) 596)