summaryrefslogtreecommitdiff
path: root/beinc_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'beinc_server.py')
-rwxr-xr-xbeinc_server.py118
1 files changed, 54 insertions, 64 deletions
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)