diff options
| -rw-r--r-- | INSTALL.md | 34 | ||||
| -rw-r--r-- | README.md | 42 | ||||
| -rwxr-xr-x | beinc_generic_client.py | 269 | ||||
| -rwxr-xr-x | beinc_pull.py (renamed from beinc_poller.py) | 144 | ||||
| -rwxr-xr-x | beinc_server.py | 255 | ||||
| -rw-r--r-- | beinc_weechat.py | 416 |
6 files changed, 551 insertions, 609 deletions
| @@ -1,38 +1,38 @@ | |||
| 1 | ## Installation Instructions | 1 | # Installation Instructions |
| 2 | 2 | ||
| 3 | Refer to README for a basic information about the different BEINC components | 3 | Refer to README for a basic information about the different BEINC components |
| 4 | and their software requirements! | 4 | and their software requirements! |
| 5 | 5 | ||
| 6 | 6 | ||
| 7 | ### Installing the dependencies | 7 | ## Installing the dependencies |
| 8 | 8 | ||
| 9 | #### Gentoo GNU/Linux 2014, 2015 | 9 | ### Gentoo GNU/Linux |
| 10 | 10 | ||
| 11 | # emerge -va dev-python/pyopenssl dev-python/notify-python | 11 | ```bash |
| 12 | emerge -va dev-python/notify2 | ||
| 13 | ``` | ||
| 12 | 14 | ||
| 13 | 15 | ||
| 14 | #### Ubuntu GNU/Linux | 16 | ### Ubuntu GNU/Linux |
| 15 | 17 | ||
| 16 | # apt-get install python-openssl python-notify2 | 18 | ```bash |
| 19 | apt-get install python-notify2 | ||
| 20 | ``` | ||
| 17 | 21 | ||
| 18 | 22 | ||
| 19 | #### FreeBSD 10, 11 | 23 | ### FreeBSD 12 |
| 20 | |||
| 21 | # cd /usr/ports/security/py-openssl | ||
| 22 | # make install clean | ||
| 23 | 24 | ||
| 24 | # cd /usr/ports/devel/py-notify | 25 | # cd /usr/ports/devel/py-notify |
| 25 | # make install clean | 26 | # make install clean |
| 26 | 27 | ||
| 27 | 28 | ||
| 28 | #### Intallation using virtualenv and pip | 29 | ### Intallation using virtualenv and pip |
| 29 | 30 | ||
| 30 | Very often you are not the administrator of the server hosting your BEINC server | 31 | Very often you are not the administrator of the server hosting your BEINC server |
| 31 | This is where virtualenv may become handy (provided that it has been installed): | 32 | This is where virtualenv may become handy (provided that it has been installed): |
| 32 | $ virtualenv beinc_installation | 33 | $ virtualenv beinc_installation |
| 33 | $ cd beinc_installation | 34 | $ cd beinc_installation |
| 34 | $ source bin/activate | 35 | $ source bin/activate |
| 35 | $ pip install pyopenssl | ||
| 36 | $ cd beinc-<version> | 36 | $ cd beinc-<version> |
| 37 | $ ./beinc_server.py -h | 37 | $ ./beinc_server.py -h |
| 38 | 38 | ||
| @@ -59,13 +59,13 @@ The example loads the server listening on 10.0.0.44, port 9678 using the default | |||
| 59 | ~/.beinc_server.json as a configuration file and usually gives you the Twisted | 59 | ~/.beinc_server.json as a configuration file and usually gives you the Twisted |
| 60 | log console | 60 | log console |
| 61 | 61 | ||
| 62 | beinc_poller.py: | 62 | beinc_pull.py: |
| 63 | $ ./beinc_poller.py -h | 63 | $ ./beinc_pull.py -h |
| 64 | The example lists all available command options | 64 | The example lists all available command options |
| 65 | $ ./beinc_poller.py -n weechat -t 10 -f 7 --cert-file /home/sgs/weechat_cert.crt \ | 65 | $ ./beinc_pull.py -n weechat -t 10 -f 7 --cert-file /home/sgs/weechat_cert.crt \ |
| 66 | -p /home/sgs/.weechat_beinc_passwd https://10.0.0.44:9678 | 66 | -p /home/sgs/.weechat_beinc_passwd https://10.0.0.44:9678 |
| 67 | Starts polling from the instance (target) defined above, | 67 | Starts pulling from the instance (target) defined above, |
| 68 | defining an OSD timeout of 10 seconds, polling frequency of 7 seconds, | 68 | defining an OSD timeout of 10 seconds, pulling frequency of 7 seconds, |
| 69 | checking whether the server's SSL certificate was signed (issued) by /home/sgs/weechat_cert.crt, | 69 | checking whether the server's SSL certificate was signed (issued) by /home/sgs/weechat_cert.crt, |
| 70 | extracting the target's password from /home/sgs/.weechat_beinc_passwd | 70 | extracting the target's password from /home/sgs/.weechat_beinc_passwd |
| 71 | 71 | ||
| @@ -1,4 +1,4 @@ | |||
| 1 | Copyright (C) 2014-2018 - Simeon Simeonov | 1 | Copyright (C) 2014-2020 - Simeon Simeonov |
| 2 | See the end of the file for license conditions. | 2 | See the end of the file for license conditions. |
| 3 | 3 | ||
| 4 | 4 | ||
| @@ -10,14 +10,14 @@ on-screen-display notification scenarios. | |||
| 10 | 10 | ||
| 11 | The current version of BEINC contains the following components: | 11 | The current version of BEINC contains the following components: |
| 12 | beinc_server.py - server used for queueing or providing on-screen-display (OSD) | 12 | beinc_server.py - server used for queueing or providing on-screen-display (OSD) |
| 13 | beinc_poller.py - client used to fetch enqueued messages from beinc_server.py | 13 | beinc_pull.py - client used to fetch enqueued messages from beinc_server.py |
| 14 | and provide OSD | 14 | and provide OSD |
| 15 | beinc_weechat.py - a complete script / client for the Weechat IRC client >=0.4.0 | 15 | beinc_weechat.py - a complete script / client for the Weechat IRC client >=0.4.0 |
| 16 | used to push notification messages to beinc_server.py | 16 | used to push notification messages to beinc_server.py |
| 17 | beinc_generic_client.py - a simple client used to push notification messages | 17 | beinc_generic_client.py - a simple client used to push notification messages |
| 18 | to beinc_server.py | 18 | to beinc_server.py |
| 19 | Its main purpose is to provide a convenient way to | 19 | Its main purpose is to provide a convenient way to |
| 20 | test beinc_server.py (and beinc_poller.py) as well as | 20 | test beinc_server.py (and beinc_pull.py) as well as |
| 21 | to serve as an example for how to develop BEINC-clients | 21 | to serve as an example for how to develop BEINC-clients |
| 22 | + documentation and a sample configuration file (beinc_config_sample.json) | 22 | + documentation and a sample configuration file (beinc_config_sample.json) |
| 23 | 23 | ||
| @@ -28,7 +28,7 @@ BEINC is a free-software, licensed under the GPL3. It gives you the freedoms | |||
| 28 | of using it, studying it and modifying it. | 28 | of using it, studying it and modifying it. |
| 29 | 29 | ||
| 30 | BEINC is designed to assist you two common scenarios. | 30 | BEINC is designed to assist you two common scenarios. |
| 31 | As an example we will consider the case of an IRC cliet, although BEINC can be | 31 | As an example we will consider the case of an IRC client, although BEINC can be |
| 32 | used by any client that conforms with BEINC's "messaging protocol". | 32 | used by any client that conforms with BEINC's "messaging protocol". |
| 33 | 33 | ||
| 34 | We have the common situation where a user is accessing an IRC client located on | 34 | We have the common situation where a user is accessing an IRC client located on |
| @@ -66,20 +66,20 @@ Solution: | |||
| 66 | - set up the beinc_server.py on it. No OSD capabilities are needed (no X11). | 66 | - set up the beinc_server.py on it. No OSD capabilities are needed (no X11). |
| 67 | Define instance(s) for queueing. Define a queue size | 67 | Define instance(s) for queueing. Define a queue size |
| 68 | (how many notifications to store) for each of them. | 68 | (how many notifications to store) for each of them. |
| 69 | - set up beinc_poller.py on the desktop workstation (the computer where the | 69 | - set up beinc_pull.py on the desktop workstation (the computer where the |
| 70 | notification-message should be displayed). | 70 | notification-message should be displayed). |
| 71 | beinc_poller.py should be able to access the server running beinc_server.py | 71 | beinc_pull.py should be able to access the server running beinc_server.py |
| 72 | Polling interval of 5 seconds should be enough for most users. | 72 | Pulling interval of 5 seconds should be enough for most users. |
| 73 | 73 | ||
| 74 | Read beinc_config_sample.json.readme for details on how to setup | 74 | Read beinc_config_sample.json.readme for details on how to setup |
| 75 | beinc_server.py and beinc_weechat.py! | 75 | beinc_server.py and beinc_weechat.py! |
| 76 | Use "-h" command line parameter to display the available options for | 76 | Use "-h" command line parameter to display the available options for |
| 77 | beinc_poller.py and beinc_generic_client.py | 77 | beinc_pull.py and beinc_generic_client.py |
| 78 | Each configured beinc_server.py instance has an unique name. | 78 | Each configured beinc_server.py instance has an unique name. |
| 79 | There are 2 operations that can be done on an instance: | 79 | There are 2 operations that can be done on an instance: |
| 80 | - push - a client is sending a notification to the BEINC server. | 80 | - push - a client is sending a notification to the BEINC server. |
| 81 | A push is available for all instances. | 81 | A push is available for all instances. |
| 82 | - pull - a poller (f.i. beinc_poller.py) is fetching data from the instance-queue. | 82 | - pull - a pull (f.i. beinc_pull.py) is fetching data from the instance-queue. |
| 83 | A pull is available only for instances defined for queueing. | 83 | A pull is available only for instances defined for queueing. |
| 84 | (read beinc_config_sample.json.readme for details!) | 84 | (read beinc_config_sample.json.readme for details!) |
| 85 | Example: | 85 | Example: |
| @@ -91,28 +91,38 @@ your URL will be: https://hostname:port | |||
| 91 | 91 | ||
| 92 | Any system running the software required for the selected components. | 92 | Any system running the software required for the selected components. |
| 93 | All components tested on: Gentoo GNU/Linux, | 93 | All components tested on: Gentoo GNU/Linux, |
| 94 | Ubuntu GNU/Linux 16.4, 18.4, | 94 | Ubuntu GNU/Linux 18.4, |
| 95 | FreeBSD 10.x, 11.x | 95 | FreeBSD 12.x |
| 96 | 96 | ||
| 97 | 97 | ||
| 98 | ### Requirements | 98 | ### Requirements |
| 99 | All components: Python >= 2.7.9 or Python >= 3.4.* | 99 | All components: Python >= 3.6.* |
| 100 | 100 | ||
| 101 | beinc_server.py: pynotify >= 0.1 (optional) | 101 | beinc_server.py: pynotify >= 0.1 (optional) |
| 102 | beinc_weechat.py: Weechat >= 0.4.0 | 102 | beinc_weechat.py: Weechat >= 0.4.0 |
| 103 | beinc_poller.py: pynotify >= 0.1 | 103 | beinc_pull.py: pynotify >= 0.1 |
| 104 | beinc_generic_client.py: No additional software required | 104 | beinc_generic_client.py: No additional software required |
| 105 | 105 | ||
| 106 | Read INSTALL in this very same folder for more details about installing the requirements! | 106 | Read INSTALL in this very same folder for more details about installing the requirements! |
| 107 | 107 | ||
| 108 | 108 | ||
| 109 | ### License | 109 | ## Support and contributing |
| 110 | |||
| 111 | BEINC is hosted on GitHub: https://github.com/blackm0re/BEINC | ||
| 112 | |||
| 113 | |||
| 114 | ## Author | ||
| 115 | |||
| 116 | Simeon Simeonov - sgs @ Freenode | ||
| 117 | |||
| 118 | |||
| 119 | ## License | ||
| 110 | 120 | ||
| 111 | This file is part of BEINC. | 121 | This file is part of BEINC. |
| 112 | 122 | ||
| 113 | BEINC is free software: you can redistribute it and/or modify | 123 | BEINC is free software: you can redistribute it and/or modify |
| 114 | it under the terms of the GNU General Public License as published by | 124 | it under the terms of the GNU General Public License as published by |
| 115 | the Free Software Foundation, either version 3 of the License, or | 125 | the Free Software Foundation, either version 3 of the License, or |
| 116 | (at your option) any later version. | 126 | (at your option) any later version. |
| 117 | 127 | ||
| 118 | This program is distributed in the hope that it will be useful, | 128 | This program is distributed in the hope that it will be useful, |
diff --git a/beinc_generic_client.py b/beinc_generic_client.py index 4d8fc35..5514642 100755 --- a/beinc_generic_client.py +++ b/beinc_generic_client.py | |||
| @@ -1,8 +1,8 @@ | |||
| 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
| 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
| 3 | 3 | ||
| 4 | # Blackmore's Enhanced IRC-Notification Collection (BEINC) v2.0 | 4 | # Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 |
| 5 | # Copyright (C) 2013-2018 Simeon Simeonov | 5 | # Copyright (C) 2013-2020 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 |
| @@ -16,146 +16,114 @@ | |||
| 16 | 16 | ||
| 17 | # You should have received a copy of the GNU General Public License | 17 | # 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/>. | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 19 | 19 | """A generic BEINC client that can be used for testing or as a template""" | |
| 20 | |||
| 21 | import argparse | 20 | import argparse |
| 22 | import errno | 21 | import errno |
| 23 | import getpass | 22 | import getpass |
| 24 | import httplib # for Python < 2.7.9 | 23 | import io |
| 24 | import json | ||
| 25 | import os | 25 | import os |
| 26 | import socket | 26 | import socket |
| 27 | import ssl | 27 | import ssl |
| 28 | import sys | 28 | import sys |
| 29 | import xmlrpclib | 29 | import urllib.parse |
| 30 | import urllib.request | ||
| 30 | 31 | ||
| 31 | 32 | ||
| 32 | __author__ = 'Simeon Simeonov' | 33 | __author__ = 'Simeon Simeonov' |
| 33 | __version__ = '3.0' | 34 | __version__ = '4.0' |
| 34 | __license__ = 'GPL3' | 35 | __license__ = 'GPL3' |
| 35 | 36 | ||
| 36 | 37 | ||
| 37 | BEINC_SSL_METHODS = {'TLSv1': ssl.PROTOCOL_TLSv1} | 38 | def eprint(*arg, **kwargs): |
| 38 | try: | 39 | """stdderr print wrapper""" |
| 39 | BEINC_SSL_METHODS.update({'TLSv1_1': ssl.PROTOCOL_TLSv1_1}) | 40 | print(*arg, file=sys.stderr, flush=True, **kwargs) |
| 40 | BEINC_SSL_METHODS.update({'TLSv1_2': ssl.PROTOCOL_TLSv1_2}) | ||
| 41 | except: | ||
| 42 | pass | ||
| 43 | 41 | ||
| 44 | 42 | ||
| 45 | class BEINCCustomHTTPSConnection(httplib.HTTPConnection): | 43 | def fetch_password(args_password): |
| 46 | """ | 44 | """ |
| 47 | This class allows communication via SSL. | 45 | Fetches the password from the provided `args_password` |
| 46 | |||
| 47 | :param args_password: The password coming from argparse | ||
| 48 | :type args_password: str | ||
| 48 | 49 | ||
| 49 | It is a reimplementation of httplib.HTTPSConnection and | 50 | :return: The password string |
| 50 | allows the server certificate to be validated against CA | 51 | :rtype: str |
| 51 | This functionality lacks in Python < 2.7.9 | ||
| 52 | """ | 52 | """ |
| 53 | default_port = httplib.HTTPS_PORT | 53 | if not args_password: |
| 54 | try: | ||
| 55 | return getpass.getpass() | ||
| 56 | except KeyboardInterrupt: | ||
| 57 | eprint(os.linesep + 'Prompt terminated') | ||
| 58 | sys.exit(errno.EACCES) | ||
| 59 | elif os.path.isfile(args_password): | ||
| 60 | try: | ||
| 61 | with io.open(args_password, 'r') as fp: | ||
| 62 | passwd = fp.readline() | ||
| 63 | if passwd.strip(): | ||
| 64 | return passwd.strip() | ||
| 65 | except Exception as e: | ||
| 66 | eprint(f'Unable to open password file: {e}') | ||
| 67 | sys.exit(1) | ||
| 68 | return args_password | ||
| 54 | 69 | ||
| 55 | def __init__(self, host, port=None, key_file=None, cert_file=None, | ||
| 56 | strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, | ||
| 57 | source_address=None, custom_ssl_options={}): | ||
| 58 | httplib.HTTPConnection.__init__(self, host, port, strict, timeout, | ||
| 59 | source_address) | ||
| 60 | self.key_file = key_file | ||
| 61 | self.cert_file = cert_file | ||
| 62 | self.custom_ssl_options = custom_ssl_options | ||
| 63 | 70 | ||
| 64 | def connect(self): | 71 | def pull_notifications(ssl_context, args): |
| 65 | "Connect to a host on a given (SSL) port." | 72 | """ |
| 66 | sock = socket.create_connection((self.host, self.port), | 73 | Pulls the notifications from the BEINC server. |
| 67 | self.timeout, self.source_address) | ||
| 68 | if self._tunnel_host: | ||
| 69 | self.sock = sock | ||
| 70 | self._tunnel() | ||
| 71 | self.sock = ssl.wrap_socket(sock, | ||
| 72 | self.key_file, | ||
| 73 | self.cert_file, | ||
| 74 | **self.custom_ssl_options) | ||
| 75 | 74 | ||
| 75 | :param ssl_context: The SSL context object | ||
| 76 | :type ssl_context: ssl.SSLContext | ||
| 76 | 77 | ||
| 77 | class BEINCCustomSafeTransport(xmlrpclib.Transport): | 78 | :param args: The arguments assigned from argparse |
| 78 | """ | 79 | :type args: argparse.Namespace |
| 79 | Provides an alternative HTTPS transport for clients running on Python<2.7.9 | 80 | |
| 81 | :return: The notifications (if any) | ||
| 82 | :rtype: list | ||
| 80 | """ | 83 | """ |
| 84 | response = urllib.request.urlopen( | ||
| 85 | args.url, | ||
| 86 | data=urllib.parse.urlencode( | ||
| 87 | ( | ||
| 88 | ('resource_name', args.rname), | ||
| 89 | ('password', args.password) | ||
| 90 | )).encode('utf-8'), | ||
| 91 | timeout=args.socket_timeout, | ||
| 92 | context=ssl_context) | ||
| 93 | response_dict = json.loads(response.read().decode('utf-8')) | ||
| 94 | if response.code != 200: | ||
| 95 | raise socket.error(response_dict.get('message', '')) | ||
| 96 | return response_dict['data']['messages'] | ||
| 81 | 97 | ||
| 82 | def __init__(self, use_datetime=0, custom_ssl_options={}): | ||
| 83 | xmlrpclib.Transport.__init__(self, use_datetime=use_datetime) | ||
| 84 | self.custom_ssl_options = custom_ssl_options | ||
| 85 | 98 | ||
| 86 | def make_connection(self, host): | 99 | def push_notification(ssl_context, args): |
| 87 | if self._connection and host == self._connection[0]: | 100 | """ |
| 88 | return self._connection[1] | 101 | Pushes a single notification to the BEINC server. |
| 89 | try: | ||
| 90 | HTTPS = BEINCCustomHTTPSConnection | ||
| 91 | except AttributeError: | ||
| 92 | raise NotImplementedError( | ||
| 93 | "your version of httplib doesn't support HTTPS" | ||
| 94 | ) | ||
| 95 | else: | ||
| 96 | chost, self._extra_headers, x509 = self.get_host_info(host) | ||
| 97 | self._connection = host, HTTPS( | ||
| 98 | chost, | ||
| 99 | None, | ||
| 100 | custom_ssl_options=self.custom_ssl_options, | ||
| 101 | **(x509 or {})) | ||
| 102 | return self._connection[1] | ||
| 103 | 102 | ||
| 103 | :param ssl_context: The SSL context object | ||
| 104 | :type ssl_context: ssl.SSLContext | ||
| 104 | 105 | ||
| 105 | def action_execute(args): | 106 | :param args: The arguments assigned from argparse |
| 106 | """ | 107 | :type args: argparse.Namespace |
| 107 | """ | 108 | """ |
| 108 | try: | 109 | response = urllib.request.urlopen( |
| 109 | ssl_version = BEINC_SSL_METHODS.get(args.ssl_version, | 110 | args.url, |
| 110 | ssl.PROTOCOL_SSLv23) | 111 | data=urllib.parse.urlencode( |
| 111 | if sys.hexversion >= 0x20709f0: | 112 | ( |
| 112 | # Python >= 2.7.9 | 113 | ('resource_name', args.rname), |
| 113 | context = ssl.SSLContext(ssl_version) | 114 | ('password', args.password), |
| 114 | context.verify_mode = ssl.CERT_NONE | 115 | ('title', args.title), |
| 115 | if args.cert: | 116 | ('message', args.message) |
| 116 | context.verify_mode = ssl.CERT_REQUIRED | 117 | )).encode('utf-8'), |
| 117 | context.load_verify_locations(os.path.expanduser(args.cert)) | 118 | timeout=args.socket_timeout, |
| 118 | context.check_hostname = bool(not args.disable_hostname_check) | 119 | context=ssl_context) |
| 119 | if args.ciphers: | 120 | response_dict = json.loads(response.read().decode('utf-8')) |
| 120 | context.set_ciphers(args.ciphers) | 121 | if response.code != 200: |
| 121 | transport = xmlrpclib.SafeTransport(context=context) | 122 | raise socket.error(response_dict.get('message', '')) |
| 122 | else: | ||
| 123 | # Python < 2.7.9 | ||
| 124 | ssl_options = {} | ||
| 125 | ssl_options['ssl_version'] = ssl_version | ||
| 126 | if args.cert: | ||
| 127 | ssl_options['ca_certs'] = os.path.expanduser(args.cert) | ||
| 128 | ssl_options['cert_reqs'] = ssl.CERT_REQUIRED | ||
| 129 | if args.ciphers: | ||
| 130 | ssl_options['ciphers'] = args.ciphers | ||
| 131 | transport = BEINCCustomSafeTransport( | ||
| 132 | custom_ssl_options=ssl_options) | ||
| 133 | server = xmlrpclib.ServerProxy(args.url, | ||
| 134 | transport=transport) | ||
| 135 | if args.pull: | ||
| 136 | print(server.pull(args.rname, args.password)) | ||
| 137 | else: | ||
| 138 | print(server.push(args.rname, | ||
| 139 | args.password, | ||
| 140 | args.title, | ||
| 141 | args.message)) | ||
| 142 | except xmlrpclib.Fault as fault: | ||
| 143 | sys.stderr.write( | ||
| 144 | 'BEINC server answered with errorCode={0}: {1}\n'.format( | ||
| 145 | fault.faultCode, | ||
| 146 | fault.faultString)) | ||
| 147 | except ssl.SSLError as e: | ||
| 148 | sys.stderr.write('BEINC SSL/TLS error: {0}\n'.format(e)) | ||
| 149 | sys.exit(errno.EPERM) | ||
| 150 | except socket.error as e: | ||
| 151 | sys.stderr.write('BEINC connection error: {0}\n'.format(e)) | ||
| 152 | sys.exit(errno.EPERM) | ||
| 153 | except Exception as e: | ||
| 154 | sys.stderr.write('BEINC generic client error: {0}\n'.format(e)) | ||
| 155 | sys.exit(errno.EPERM) | ||
| 156 | 123 | ||
| 157 | 124 | ||
| 158 | def main(): | 125 | def main(inargs=None): |
| 126 | """main entry""" | ||
| 159 | parser = argparse.ArgumentParser( | 127 | parser = argparse.ArgumentParser( |
| 160 | description='The following options are available') | 128 | description='The following options are available') |
| 161 | parser.add_argument( | 129 | parser.add_argument( |
| @@ -178,13 +146,12 @@ def main(): | |||
| 178 | dest='ciphers', | 146 | dest='ciphers', |
| 179 | default='', | 147 | default='', |
| 180 | help='Preferred ciphers list (default: auto)') | 148 | help='Preferred ciphers list (default: auto)') |
| 181 | if sys.hexversion >= 0x20709f0: | 149 | parser.add_argument( |
| 182 | parser.add_argument( | 150 | '--disable-hostname-check', |
| 183 | '--disable-hostname-check', | 151 | action='store_true', |
| 184 | action='store_true', | 152 | dest='disable_hostname_check', |
| 185 | dest='disable_hostname_check', | 153 | default=False, |
| 186 | default=False, | 154 | help='Do not check whether server cert matches server hostname') |
| 187 | help='Do not check whether server cert matches server hostname') | ||
| 188 | parser.add_argument( | 155 | parser.add_argument( |
| 189 | '-m', '--message', | 156 | '-m', '--message', |
| 190 | metavar='MESSAGE', | 157 | metavar='MESSAGE', |
| @@ -213,23 +180,6 @@ def main(): | |||
| 213 | dest='pull', | 180 | dest='pull', |
| 214 | default=False, | 181 | default=False, |
| 215 | help='Perform a pull operation (default: push)') | 182 | help='Perform a pull operation (default: push)') |
| 216 | if sys.hexversion >= 0x20709f0: | ||
| 217 | parser.add_argument( | ||
| 218 | '-s', '--ssl-version', | ||
| 219 | metavar='VERSION', | ||
| 220 | type=str, | ||
| 221 | dest='ssl_version', | ||
| 222 | default='auto', | ||
| 223 | help='Use SSL version: "auto" (default), ' | ||
| 224 | '"SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2"') | ||
| 225 | else: | ||
| 226 | parser.add_argument( | ||
| 227 | '-s', '--ssl-version', | ||
| 228 | metavar='VERSION', | ||
| 229 | type=str, | ||
| 230 | dest='ssl_version', | ||
| 231 | default='auto', | ||
| 232 | help='Use SSL version: "auto" (default), "SSLv3", "TLSv1"') | ||
| 233 | parser.add_argument( | 183 | parser.add_argument( |
| 234 | '-T', '--socket-timeout', | 184 | '-T', '--socket-timeout', |
| 235 | metavar='SECONDS', | 185 | metavar='SECONDS', |
| @@ -247,29 +197,36 @@ def main(): | |||
| 247 | parser.add_argument( | 197 | parser.add_argument( |
| 248 | '-v', '--version', | 198 | '-v', '--version', |
| 249 | action='version', | 199 | action='version', |
| 250 | version='%(prog)s {0}'.format(__version__), | 200 | version=f'%(prog)s {__version__}', |
| 251 | help='display program-version and exit') | 201 | help='display program-version and exit') |
| 252 | args = parser.parse_args() | 202 | args = parser.parse_args(inargs) |
| 253 | |||
| 254 | if not args.password: | ||
| 255 | try: | ||
| 256 | args.password = getpass.getpass() | ||
| 257 | except Exception as e: | ||
| 258 | sys.stderr.write('Prompt terminated\n') | ||
| 259 | sys.exit(errno.EACCES) | ||
| 260 | elif os.path.isfile(args.password): | ||
| 261 | try: | ||
| 262 | with open(args.password, 'r') as fp: | ||
| 263 | passwd = fp.readline() | ||
| 264 | if passwd.strip(): | ||
| 265 | args.password = passwd.strip() | ||
| 266 | except Exception as e: | ||
| 267 | sys.stderr.write('Unable to open password file: {0}'.format(e)) | ||
| 268 | sys.exit(1) | ||
| 269 | if args.socket_timeout: | 203 | if args.socket_timeout: |
| 270 | socket.setdefaulttimeout(args.socket_timeout) | 204 | socket.setdefaulttimeout(args.socket_timeout) |
| 271 | action_execute(args) | 205 | args.password = fetch_password(args.password) |
| 272 | sys.exit(0) | 206 | try: |
| 207 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) | ||
| 208 | context.verify_mode = ssl.CERT_NONE | ||
| 209 | if args.cert: | ||
| 210 | context.verify_mode = ssl.CERT_REQUIRED | ||
| 211 | context.load_verify_locations(cafile=os.path.expanduser(args.cert)) | ||
| 212 | context.check_hostname = bool(not args.disable_hostname_check) | ||
| 213 | if args.ciphers: | ||
| 214 | context.set_ciphers(args.ciphers) | ||
| 215 | if args.pull: | ||
| 216 | print(json.dumps(pull_notifications(context, args), indent=4)) | ||
| 217 | else: | ||
| 218 | push_notification(context, args) | ||
| 219 | print('OK') | ||
| 220 | sys.exit(0) | ||
| 221 | except ssl.SSLError as e: | ||
| 222 | eprint(f'BEINC SSL/TLS error: {e}') | ||
| 223 | sys.exit(errno.EPERM) | ||
| 224 | except socket.error as e: | ||
| 225 | eprint(f'BEINC connection error: {e}') | ||
| 226 | sys.exit(errno.EPERM) | ||
| 227 | except Exception as e: | ||
| 228 | eprint(f'BEINC generic client error: {e}') | ||
| 229 | sys.exit(errno.EPERM) | ||
| 273 | 230 | ||
| 274 | 231 | ||
| 275 | if __name__ == '__main__': | 232 | if __name__ == '__main__': |
diff --git a/beinc_poller.py b/beinc_pull.py index 624cd24..9c44c00 100755 --- a/beinc_poller.py +++ b/beinc_pull.py | |||
| @@ -1,8 +1,8 @@ | |||
| 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
| 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
| 3 | 3 | ||
| 4 | # Blackmore's Enhanced IRC-Notification Collection (BEINC) v3.0 | 4 | # Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 |
| 5 | # Copyright (C) 2013-2018 Simeon Simeonov | 5 | # Copyright (C) 2013-2020 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 |
| @@ -16,11 +16,11 @@ | |||
| 16 | 16 | ||
| 17 | # You should have received a copy of the GNU General Public License | 17 | # 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/>. | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 19 | 19 | """A simple client that pulls notifications from a BEINC server""" | |
| 20 | |||
| 21 | import argparse | 20 | import argparse |
| 22 | import errno | 21 | import errno |
| 23 | import getpass | 22 | import getpass |
| 23 | import io | ||
| 24 | import json | 24 | import json |
| 25 | import os | 25 | import os |
| 26 | import sched | 26 | import sched |
| @@ -28,69 +28,92 @@ import socket | |||
| 28 | import ssl | 28 | import ssl |
| 29 | import sys | 29 | import sys |
| 30 | import time | 30 | import time |
| 31 | 31 | import urllib.parse | |
| 32 | PY2 = sys.version_info[0] == 2 | 32 | import urllib.request |
| 33 | PY3 = sys.version_info[0] == 3 | ||
| 34 | |||
| 35 | if PY3: | ||
| 36 | from urllib.parse import urlencode | ||
| 37 | from urllib.request import urlopen | ||
| 38 | else: | ||
| 39 | from urllib import urlencode | ||
| 40 | from urllib2 import urlopen | ||
| 41 | 33 | ||
| 42 | try: | 34 | try: |
| 43 | if PY3: | 35 | import notify2 as pynotify |
| 44 | import notify2 as pynotify | 36 | except ImportError: |
| 45 | else: | ||
| 46 | import pynotify | ||
| 47 | except ImportError as e: | ||
| 48 | pynotify = None | 37 | pynotify = None |
| 49 | 38 | ||
| 50 | 39 | ||
| 51 | __author__ = 'Simeon Simeonov' | 40 | __author__ = 'Simeon Simeonov' |
| 52 | __version__ = '3.0' | 41 | __version__ = '4.0' |
| 53 | __license__ = 'GPL3' | 42 | __license__ = 'GPL3' |
| 54 | 43 | ||
| 55 | 44 | ||
| 45 | def eprint(*arg, **kwargs): | ||
| 46 | """stdderr print wrapper""" | ||
| 47 | print(*arg, file=sys.stderr, flush=True, **kwargs) | ||
| 48 | |||
| 49 | |||
| 50 | def fetch_password(args_password): | ||
| 51 | """ | ||
| 52 | Fetches the password from the provided `args_password` | ||
| 53 | |||
| 54 | :param args_password: The password coming from argparse | ||
| 55 | :type args_password: str | ||
| 56 | |||
| 57 | :return: The password string | ||
| 58 | :rtype: str | ||
| 59 | """ | ||
| 60 | if not args_password: | ||
| 61 | try: | ||
| 62 | return getpass.getpass() | ||
| 63 | except KeyboardInterrupt: | ||
| 64 | eprint(os.linesep + 'Prompt terminated') | ||
| 65 | sys.exit(errno.EACCES) | ||
| 66 | elif os.path.isfile(args_password): | ||
| 67 | try: | ||
| 68 | with io.open(args_password, 'r') as fp: | ||
| 69 | passwd = fp.readline() | ||
| 70 | if passwd.strip(): | ||
| 71 | return passwd.strip() | ||
| 72 | except Exception as e: | ||
| 73 | eprint(f'Unable to open password file: {e}') | ||
| 74 | sys.exit(1) | ||
| 75 | return args_password | ||
| 76 | |||
| 77 | |||
| 56 | def display_notification(args, title, message): | 78 | def display_notification(args, title, message): |
| 57 | """ | 79 | """ |
| 58 | A wrapper function for displaying a single notification | 80 | A wrapper function for displaying a single notification |
| 59 | 81 | ||
| 60 | args: the argparse processed command-line arguments | 82 | :param args: The arguments assigned from argparse |
| 61 | title: notification title | 83 | :type args: argparse.Namespace |
| 62 | message: notification message | 84 | |
| 85 | :param title: The title | ||
| 86 | :type title: str | ||
| 87 | |||
| 88 | :param message: The message | ||
| 89 | :type message: str | ||
| 63 | """ | 90 | """ |
| 64 | if args.osd_sys == 'pynotify': | 91 | if args.osd_sys == 'pynotify': |
| 65 | if not pynotify: | 92 | if not pynotify: |
| 66 | raise Exception( | 93 | raise Exception( |
| 67 | 'Could not load "pynotify".\n' | 94 | 'Could not load "pynotify".' |
| 68 | 'Please install "pynotify" or use a different osd-system!\n' | 95 | 'Please install "pynotify" or use a different osd-system!' |
| 69 | 'Terminating...\n') | 96 | 'Terminating...') |
| 70 | if not pynotify.init('BEINC Notify'): | 97 | if not pynotify.init('BEINC Notify'): |
| 71 | raise Exception('There was a problem with libnotify\n') | 98 | raise Exception('There was a problem with libnotify') |
| 72 | notification_obj = pynotify.Notification(summary=title, | 99 | notification_obj = pynotify.Notification(summary=title, |
| 73 | message=message) | 100 | message=message) |
| 74 | if PY3: | 101 | notification_obj.timeout = 1000 * args.osd_timeout |
| 75 | notification_obj.timeout = 1000 * args.osd_timeout | 102 | notification_obj.set_category('im.received') |
| 76 | notification_obj.set_category('im.received') | ||
| 77 | else: | ||
| 78 | notification_obj.set_timeout(1000 * args.osd_timeout) | ||
| 79 | notification_obj.set_property( | ||
| 80 | 'app_name', | ||
| 81 | '{0} {1}'.format(sys.argv[0], __version__)) | ||
| 82 | notification_obj.show() | 103 | notification_obj.show() |
| 83 | else: | 104 | else: |
| 84 | raise Exception( | 105 | raise Exception(f'Unsupported osd-system: {args.osd_sys}') |
| 85 | 'Unsupported osd-system: {0}\n'.format(args.osd_sys)) | ||
| 86 | 106 | ||
| 87 | 107 | ||
| 88 | def poll_notifications(scheduler, args): | 108 | def pull_notifications(scheduler, args): |
| 89 | """ | 109 | """ |
| 90 | the core function initiated by the scheduler performing a single poll | 110 | The core function initiated by the scheduler performing a single pull |
| 91 | 111 | ||
| 92 | scheduler: scheduler object | 112 | :param scheduler: Scheduler object |
| 93 | args: the argparse processed command-line arguments | 113 | :type scheduler: sched.scheduler |
| 114 | |||
| 115 | :param args: The arguments assigned from argparse | ||
| 116 | :type args: argparse.Namespace | ||
| 94 | """ | 117 | """ |
| 95 | try: | 118 | try: |
| 96 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) | 119 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) |
| @@ -101,9 +124,9 @@ def poll_notifications(scheduler, args): | |||
| 101 | context.check_hostname = bool(not args.disable_hostname_check) | 124 | context.check_hostname = bool(not args.disable_hostname_check) |
| 102 | if args.ciphers: | 125 | if args.ciphers: |
| 103 | context.set_ciphers(args.ciphers) | 126 | context.set_ciphers(args.ciphers) |
| 104 | response = urlopen( | 127 | response = urllib.request.urlopen( |
| 105 | args.url, | 128 | args.url, |
| 106 | data=urlencode( | 129 | data=urllib.parse.urlencode( |
| 107 | ( | 130 | ( |
| 108 | ('resource_name', args.rname), | 131 | ('resource_name', args.rname), |
| 109 | ('password', args.password) | 132 | ('password', args.password) |
| @@ -120,20 +143,21 @@ def poll_notifications(scheduler, args): | |||
| 120 | response.close() | 143 | response.close() |
| 121 | scheduler.enter(args.frequency, | 144 | scheduler.enter(args.frequency, |
| 122 | 1, | 145 | 1, |
| 123 | poll_notifications, | 146 | pull_notifications, |
| 124 | (scheduler, args)) | 147 | (scheduler, args)) |
| 125 | except ssl.SSLError as e: | 148 | except ssl.SSLError as e: |
| 126 | sys.stderr.write('BEINC SSL/TLS error: {0}\n'.format(e)) | 149 | eprint(f'BEINC SSL/TLS error: {e}') |
| 127 | sys.exit(errno.EPERM) | 150 | sys.exit(errno.EPERM) |
| 128 | except socket.error as e: | 151 | except socket.error as e: |
| 129 | sys.stderr.write('BEINC connection error: {0}\n'.format(e)) | 152 | eprint(f'BEINC connection error: {e}') |
| 130 | sys.exit(errno.EPERM) | 153 | sys.exit(errno.EPERM) |
| 131 | except Exception as e: | 154 | except Exception as e: |
| 132 | sys.stderr.write('BEINC generic client error: {0}\n'.format(e)) | 155 | eprint(f'BEINC generic client error: {e}') |
| 133 | sys.exit(errno.EPERM) | 156 | sys.exit(errno.EPERM) |
| 134 | 157 | ||
| 135 | 158 | ||
| 136 | def main(): | 159 | def main(inargs=None): |
| 160 | """main entry""" | ||
| 137 | parser = argparse.ArgumentParser( | 161 | parser = argparse.ArgumentParser( |
| 138 | description='The following options are available') | 162 | description='The following options are available') |
| 139 | parser.add_argument( | 163 | parser.add_argument( |
| @@ -168,7 +192,7 @@ def main(): | |||
| 168 | type=int, | 192 | type=int, |
| 169 | dest='frequency', | 193 | dest='frequency', |
| 170 | default=10, | 194 | default=10, |
| 171 | help='Polling frequency in seconds (default: 10)') | 195 | help='Pulling frequency in seconds (default: 10)') |
| 172 | parser.add_argument( | 196 | parser.add_argument( |
| 173 | '-n', '--resource-name', | 197 | '-n', '--resource-name', |
| 174 | metavar='NAME', | 198 | metavar='NAME', |
| @@ -208,28 +232,14 @@ def main(): | |||
| 208 | parser.add_argument( | 232 | parser.add_argument( |
| 209 | '-v', '--version', | 233 | '-v', '--version', |
| 210 | action='version', | 234 | action='version', |
| 211 | version='%(prog)s {0}'.format(__version__), | 235 | version=f'%(prog)s {__version__}', |
| 212 | help='display program-version and exit') | 236 | help='display program-version and exit') |
| 213 | args = parser.parse_args() | 237 | args = parser.parse_args(inargs) |
| 214 | if not args.password: | 238 | args.password = fetch_password(args.password) |
| 215 | try: | ||
| 216 | args.password = getpass.getpass() | ||
| 217 | except Exception as e: | ||
| 218 | sys.stderr.write('Prompt terminated\n') | ||
| 219 | sys.exit(errno.EACCES) | ||
| 220 | elif os.path.isfile(args.password): | ||
| 221 | try: | ||
| 222 | with open(args.password, 'r') as fp: | ||
| 223 | passwd = fp.readline() | ||
| 224 | if passwd.strip(): | ||
| 225 | args.password = passwd.strip() | ||
| 226 | except Exception as e: | ||
| 227 | sys.stderr.write('Unable to open password file: {0}'.format(e)) | ||
| 228 | sys.exit(1) | ||
| 229 | scheduler = sched.scheduler(time.time, time.sleep) | 239 | scheduler = sched.scheduler(time.time, time.sleep) |
| 230 | scheduler.enter(args.frequency, | 240 | scheduler.enter(args.frequency, |
| 231 | 1, | 241 | 1, |
| 232 | poll_notifications, | 242 | pull_notifications, |
| 233 | (scheduler, args)) | 243 | (scheduler, args)) |
| 234 | scheduler.run() | 244 | scheduler.run() |
| 235 | 245 | ||
diff --git a/beinc_server.py b/beinc_server.py index ad81ce9..0f6c248 100755 --- a/beinc_server.py +++ b/beinc_server.py | |||
| @@ -1,8 +1,8 @@ | |||
| 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
| 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
| 3 | 3 | ||
| 4 | # Blackmore's Enhanced IRC-Notification Collection (BEINC) v3.0 | 4 | # Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 |
| 5 | # Copyright (C) 2013-2018 Simeon Simeonov | 5 | # Copyright (C) 2013-2020 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,26 +29,16 @@ import sys | |||
| 29 | 29 | ||
| 30 | from functools import wraps | 30 | from functools import wraps |
| 31 | from logging.config import fileConfig | 31 | from logging.config import fileConfig |
| 32 | 32 | from http.server import BaseHTTPRequestHandler, HTTPServer | |
| 33 | PY2 = sys.version_info[0] == 2 | ||
| 34 | PY3 = sys.version_info[0] == 3 | ||
| 35 | |||
| 36 | if PY3: | ||
| 37 | from http.server import BaseHTTPRequestHandler, HTTPServer | ||
| 38 | else: | ||
| 39 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer | ||
| 40 | 33 | ||
| 41 | try: | 34 | try: |
| 42 | if PY3: | 35 | import notify2 as pynotify |
| 43 | import notify2 as pynotify | 36 | except ImportError: |
| 44 | else: | ||
| 45 | import pynotify | ||
| 46 | except ImportError as e: | ||
| 47 | pynotify = None | 37 | pynotify = None |
| 48 | 38 | ||
| 49 | 39 | ||
| 50 | __author__ = 'Simeon Simeonov' | 40 | __author__ = 'Simeon Simeonov' |
| 51 | __version__ = '3.0' | 41 | __version__ = '4.0' |
| 52 | __license__ = 'GPL3' | 42 | __license__ = 'GPL3' |
| 53 | 43 | ||
| 54 | 44 | ||
| @@ -59,28 +49,30 @@ BEINC_CURRENT_CONFIG_VERSION = 3 | |||
| 59 | 49 | ||
| 60 | 50 | ||
| 61 | class BEINCError401(Exception): | 51 | class BEINCError401(Exception): |
| 62 | pass | 52 | """BEINCError401""" |
| 63 | 53 | ||
| 64 | 54 | ||
| 65 | class BEINCError403(Exception): | 55 | class BEINCError403(Exception): |
| 66 | pass | 56 | """BEINCError403""" |
| 67 | 57 | ||
| 68 | 58 | ||
| 69 | class BEINCError404(Exception): | 59 | class BEINCError404(Exception): |
| 70 | pass | 60 | """BEINCError404""" |
| 71 | 61 | ||
| 72 | 62 | ||
| 73 | class BEINCError405(Exception): | 63 | class BEINCError405(Exception): |
| 74 | pass | 64 | """BEINCError405""" |
| 75 | 65 | ||
| 76 | 66 | ||
| 77 | def beinc_login_required(method): | 67 | def eprint(*arg, **kwargs): |
| 78 | """ | 68 | """stdderr print wrapper""" |
| 79 | Decorator for checking login credentials | 69 | print(*arg, file=sys.stderr, flush=True, **kwargs) |
| 80 | """ | 70 | |
| 81 | 71 | ||
| 72 | def beinc_login_required(method): | ||
| 73 | """Decorator for checking login credentials""" | ||
| 82 | @wraps(method) | 74 | @wraps(method) |
| 83 | def wrapper(self, data, *args, **kwargs): | 75 | def wrapper(self, data, *arg, **kwargs): |
| 84 | if data.get('resource_name') is None: | 76 | if data.get('resource_name') is None: |
| 85 | raise BEINCError403('Resource-name missing') | 77 | raise BEINCError403('Resource-name missing') |
| 86 | if data.get('password') is None: | 78 | if data.get('password') is None: |
| @@ -91,122 +83,122 @@ def beinc_login_required(method): | |||
| 91 | raise BEINCError401('Wrong instance or password') | 83 | raise BEINCError401('Wrong instance or password') |
| 92 | if not instance.password_match(data.get('password')): | 84 | if not instance.password_match(data.get('password')): |
| 93 | raise BEINCError401('Wrong instance or password') | 85 | raise BEINCError401('Wrong instance or password') |
| 94 | return method(self, data, *args, **kwargs) | 86 | return method(self, data, *arg, **kwargs) |
| 95 | return wrapper | 87 | return wrapper |
| 96 | 88 | ||
| 97 | 89 | ||
| 98 | class BEINCInstance(object): | 90 | class BEINCInstance: |
| 99 | """ | 91 | """Represents a single server-instance""" |
| 100 | Represents a single server-instance | ||
| 101 | """ | ||
| 102 | 92 | ||
| 103 | def __init__(self, instance_dict): | 93 | def __init__(self, instance_dict): |
| 104 | """ | 94 | """ |
| 105 | instance_dict: the config-dictionary node that represents this instance | 95 | instance_dict: the config-dictionary node that represents this instance |
| 106 | """ | 96 | """ |
| 107 | self.__message_queue = list() | 97 | self._message_queue = [] |
| 108 | self.__osd_type = BEINC_OSD_TYPE_NONE | 98 | self._osd_type = BEINC_OSD_TYPE_NONE |
| 109 | self.__osd_notification = None | 99 | self._osd_notification = None |
| 110 | 100 | ||
| 111 | self.__name = instance_dict.get('name') | 101 | self._name = instance_dict.get('name') |
| 112 | self.__password = instance_dict.get('password', '') | 102 | self._password = instance_dict.get('password', '') |
| 113 | self.__queue_size = int(instance_dict.get('queue_size', 3)) | 103 | self._queue_size = int(instance_dict.get('queue_size', 3)) |
| 114 | if instance_dict['osd_system'].lower() == 'pynotify': | 104 | if instance_dict['osd_system'].lower() == 'pynotify': |
| 115 | self.__queue_size = 0 # disable queueing | 105 | self._queue_size = 0 # disable queueing |
| 116 | if pynotify is None: | 106 | if pynotify is None: |
| 117 | sys.stderr.write( | 107 | eprint('This server does not possess pynotify capability') |
| 118 | 'This server does not possess pynotify capability\n') | 108 | eprint(f'Remove the instance {self._name} or define it with ' |
| 119 | sys.stderr.write( | 109 | f'"osd_system": "none" or other ' |
| 120 | 'Remove the instance {0}'.format(self.__name)) | 110 | f'available backend') |
| 121 | sys.stderr.write( | ||
| 122 | 'or define it with "osd_system": "none" ' | ||
| 123 | 'or other available backend\n') | ||
| 124 | sys.exit(errno.EPERM) | 111 | sys.exit(errno.EPERM) |
| 125 | try: | 112 | try: |
| 126 | self.__osd_notification = pynotify.Notification(' ') | 113 | self._osd_notification = pynotify.Notification(' ') |
| 127 | if PY3: | 114 | self._osd_notification.timeout = 1000 * int( |
| 128 | self.__osd_notification.timeout = 1000 * int( | 115 | instance_dict.get('osd_timeout', 5)) |
| 129 | instance_dict.get('osd_timeout', 5)) | 116 | self._osd_notification.set_category('im.received') |
| 130 | self.__osd_notification.set_category('im.received') | 117 | self._osd_type = BEINC_OSD_TYPE_PYNOTIFY |
| 131 | else: | ||
| 132 | self.__osd_notification.set_timeout( | ||
| 133 | 1000 * int(instance_dict.get('osd_timeout', 5))) | ||
| 134 | self.__osd_notification.set_property( | ||
| 135 | 'app_name', | ||
| 136 | '{0} {1}'.format(sys.argv[0], __version__)) | ||
| 137 | self.__osd_type = BEINC_OSD_TYPE_PYNOTIFY | ||
| 138 | except Exception as e: | 118 | except Exception as e: |
| 139 | sys.stderr.write( | 119 | eprint(f'Unable to set up a pynotify notification object ' |
| 140 | 'Unable to set up a ' | 120 | f'for "{self._name}" ({e})') |
| 141 | 'pynotify notification object for "{0}" ({1})\n'.format( | ||
| 142 | self.__name, | ||
| 143 | e)) | ||
| 144 | sys.exit(errno.EPERM) | 121 | sys.exit(errno.EPERM) |
| 145 | 122 | ||
| 146 | @property | 123 | @property |
| 147 | def name(self): | 124 | def name(self): |
| 148 | """ | 125 | """name-property for the server instance (read-only)""" |
| 149 | name-property for the server instance (read-only) | 126 | return self._name |
| 150 | """ | ||
| 151 | return self.__name | ||
| 152 | 127 | ||
| 153 | @property | 128 | @property |
| 154 | def queueable(self): | 129 | def queueable(self): |
| 155 | """ | 130 | """True if this instance has a queueing capability (read-only)""" |
| 156 | True if this instance has a queueing capability (read-only) | 131 | return bool(self._queue_size) |
| 157 | """ | ||
| 158 | return bool(self.__queue_size) | ||
| 159 | 132 | ||
| 160 | def password_match(self, password): | 133 | def password_match(self, password): |
| 161 | """ | 134 | """ |
| 162 | Returns True if 'passowrd' matches the instance-password, | 135 | Returns True if 'passowrd' matches the instance-password, |
| 163 | otherwise - False | 136 | otherwise - False |
| 137 | |||
| 138 | :param password: The password to compare | ||
| 139 | :type password: str | ||
| 140 | |||
| 141 | :return: True if the password matches, False otherwise | ||
| 142 | :rtype: bool | ||
| 164 | """ | 143 | """ |
| 165 | return True if self.__password == password else False | 144 | return self._password == password |
| 166 | 145 | ||
| 167 | def send_message(self, title, message): | 146 | def send_message(self, title, message): |
| 168 | """ | 147 | """ |
| 169 | Displays or enqueues the message, | 148 | Displays or enqueues the message, |
| 170 | depending on the instance's type in regard to the osd_system | 149 | depending on the instance's type in regard to the osd_system |
| 150 | |||
| 151 | :param title: The title | ||
| 152 | :type title: str | ||
| 153 | |||
| 154 | :param message: The message | ||
| 155 | :type message: str | ||
| 171 | """ | 156 | """ |
| 172 | if self.__osd_type == BEINC_OSD_TYPE_PYNOTIFY: | 157 | if self._osd_type == BEINC_OSD_TYPE_PYNOTIFY: |
| 173 | self.__send_pynotify_messaage(title, message) | 158 | self._send_pynotify_messaage(title, message) |
| 174 | else: | 159 | else: |
| 175 | self.__send_message_to_queue(title, message) | 160 | self._send_message_to_queue(title, message) |
| 176 | 161 | ||
| 177 | def get_queue(self): | 162 | def get_queue(self): |
| 178 | """ | 163 | """Returns a list of dict representation of the message queue""" |
| 179 | Returns a list of dict representation of the message queue | 164 | r_value = self._message_queue |
| 180 | """ | 165 | self._message_queue = [] |
| 181 | r_value = self.__message_queue | ||
| 182 | self.__message_queue = list() | ||
| 183 | return r_value | 166 | return r_value |
| 184 | 167 | ||
| 185 | def __send_pynotify_messaage(self, title, message): | 168 | def _send_pynotify_messaage(self, title, message): |
| 186 | """ | 169 | """ |
| 187 | Displays pynotify message | 170 | Displays pynotify message |
| 171 | |||
| 172 | :param title: The title | ||
| 173 | :type title: str | ||
| 174 | |||
| 175 | :param message: The message | ||
| 176 | :type message: str | ||
| 188 | """ | 177 | """ |
| 189 | self.__osd_notification.update(summary=title, message=message) | 178 | self._osd_notification.update(summary=title, message=message) |
| 190 | self.__osd_notification.show() | 179 | self._osd_notification.show() |
| 191 | 180 | ||
| 192 | def __send_message_to_queue(self, title, message): | 181 | def _send_message_to_queue(self, title, message): |
| 193 | """ | 182 | """ |
| 194 | Enqueues the message | 183 | Enqueues the message |
| 184 | |||
| 185 | :param title: The title | ||
| 186 | :type title: str | ||
| 187 | |||
| 188 | :param message: The message | ||
| 189 | :type message: str | ||
| 195 | """ | 190 | """ |
| 196 | if len(self.__message_queue) >= self.__queue_size: | 191 | if len(self._message_queue) >= self._queue_size: |
| 197 | self.__message_queue.pop(0) | 192 | self._message_queue.pop(0) |
| 198 | self.__message_queue.append({'title': title, 'message': message}) | 193 | self._message_queue.append({'title': title, 'message': message}) |
| 199 | 194 | ||
| 200 | 195 | ||
| 201 | class BEINCCustomHandler(BaseHTTPRequestHandler): | 196 | class BEINCCustomHandler(BaseHTTPRequestHandler): |
| 202 | """ | 197 | """Custom handler""" |
| 203 | """ | ||
| 204 | def do_POST(self): | 198 | def do_POST(self): |
| 205 | """ | 199 | """Handle POST requests""" |
| 206 | Handle POST requests | ||
| 207 | """ | ||
| 208 | if self.path.strip('/') not in ('beinc/push', 'beinc/pull'): | 200 | if self.path.strip('/') not in ('beinc/push', 'beinc/pull'): |
| 209 | self.__generate_json_error(404, 'Invalid resource path') | 201 | self._generate_json_error(404, 'Invalid resource path') |
| 210 | return | 202 | return |
| 211 | form = cgi.FieldStorage( | 203 | form = cgi.FieldStorage( |
| 212 | fp=self.rfile, | 204 | fp=self.rfile, |
| @@ -222,43 +214,38 @@ class BEINCCustomHandler(BaseHTTPRequestHandler): | |||
| 222 | try: | 214 | try: |
| 223 | result = {} | 215 | result = {} |
| 224 | if self.path.strip('/') == 'beinc/push': | 216 | if self.path.strip('/') == 'beinc/push': |
| 225 | result = self.__handle_push(POST_data) | 217 | result = self._handle_push(POST_data) |
| 226 | elif self.path.strip('/') == 'beinc/pull': | 218 | elif self.path.strip('/') == 'beinc/pull': |
| 227 | result = self.__handle_pull(POST_data) | 219 | result = self._handle_pull(POST_data) |
| 228 | self.__render_to_JSON_response(result) | 220 | self._render_to_JSON_response(result) |
| 229 | except BEINCError401 as e: | 221 | except BEINCError401 as e: |
| 230 | self.__generate_json_error(401, str(e)) | 222 | self._generate_json_error(401, str(e)) |
| 231 | except BEINCError403 as e: | 223 | except BEINCError403 as e: |
| 232 | self.__generate_json_error(403, str(e)) | 224 | self._generate_json_error(403, str(e)) |
| 233 | except BEINCError404 as e: | 225 | except BEINCError404 as e: |
| 234 | self.__generate_json_error(404, str(e)) | 226 | self._generate_json_error(404, str(e)) |
| 235 | except BEINCError405 as e: | 227 | except BEINCError405 as e: |
| 236 | self.__generate_json_error(405, str(e)) | 228 | self._generate_json_error(405, str(e)) |
| 237 | except Exception as e: | 229 | except Exception as e: |
| 238 | self.__generate_json_error(500, | 230 | self._generate_json_error(500, f'Unexpected error: {e}') |
| 239 | 'Unexpected error: {}'.format(str(e))) | ||
| 240 | 231 | ||
| 241 | def do_GET(self): | 232 | def do_GET(self): |
| 242 | """ | 233 | """Handle GET Requests""" |
| 243 | Handle GET Requests | 234 | self._generate_json_error(405, 'Unsupported method') |
| 244 | """ | ||
| 245 | self.__generate_json_error(405, 'Unsupported method') | ||
| 246 | 235 | ||
| 247 | @beinc_login_required | 236 | @beinc_login_required |
| 248 | def __handle_push(self, data): | 237 | def _handle_push(self, data): |
| 249 | """ | 238 | """Handle push""" |
| 250 | """ | ||
| 251 | instance = self.server.instances[data.get('resource_name')] | 239 | instance = self.server.instances[data.get('resource_name')] |
| 252 | try: | 240 | try: |
| 253 | instance.send_message(data.get('title'), data.get('message')) | 241 | instance.send_message(data.get('title'), data.get('message')) |
| 254 | return {'message': 'OK. Sent.'} | 242 | return {'message': 'OK. Sent.'} |
| 255 | except Exception as e: | 243 | except Exception as e: |
| 256 | self.__generate_json_error(500, str(e)) | 244 | self._generate_json_error(500, str(e)) |
| 257 | 245 | ||
| 258 | @beinc_login_required | 246 | @beinc_login_required |
| 259 | def __handle_pull(self, data): | 247 | def _handle_pull(self, data): |
| 260 | """ | 248 | """Handle pull""" |
| 261 | """ | ||
| 262 | instance = self.server.instances[data.get('resource_name')] | 249 | instance = self.server.instances[data.get('resource_name')] |
| 263 | try: | 250 | try: |
| 264 | if not instance.queueable: | 251 | if not instance.queueable: |
| @@ -267,13 +254,12 @@ class BEINCCustomHandler(BaseHTTPRequestHandler): | |||
| 267 | return {'message': 'OK. Fetched.', | 254 | return {'message': 'OK. Fetched.', |
| 268 | 'data': {'messages': instance.get_queue()}} | 255 | 'data': {'messages': instance.get_queue()}} |
| 269 | except Exception as e: | 256 | except Exception as e: |
| 270 | self.__generate_json_error(500, str(e)) | 257 | self._generate_json_error(500, str(e)) |
| 271 | 258 | ||
| 272 | def __generate_json_error(self, code, message): | 259 | def _generate_json_error(self, code, message): |
| 273 | """ | 260 | """ |
| 274 | Generates response header and json content for errors | 261 | Generates response header and json content for errors |
| 275 | 262 | ||
| 276 | Keyword Arguments: | ||
| 277 | :param code: the HTTP code | 263 | :param code: the HTTP code |
| 278 | :type code: int | 264 | :type code: int |
| 279 | 265 | ||
| @@ -288,9 +274,8 @@ class BEINCCustomHandler(BaseHTTPRequestHandler): | |||
| 288 | sort_keys=True, | 274 | sort_keys=True, |
| 289 | indent=4).encode('utf-8')) | 275 | indent=4).encode('utf-8')) |
| 290 | 276 | ||
| 291 | def __render_to_JSON_response(self, context): | 277 | def _render_to_JSON_response(self, context): |
| 292 | """ | 278 | """ |
| 293 | Keyword Arguments: | ||
| 294 | :param context: the context-dict to be converted to json | 279 | :param context: the context-dict to be converted to json |
| 295 | :type context: dict | 280 | :type context: dict |
| 296 | """ | 281 | """ |
| @@ -305,31 +290,36 @@ class BEINCCustomHandler(BaseHTTPRequestHandler): | |||
| 305 | 290 | ||
| 306 | 291 | ||
| 307 | class BEINCNotifyServer(HTTPServer): | 292 | class BEINCNotifyServer(HTTPServer): |
| 308 | """ | 293 | """BEINCNotifyServer class""" |
| 309 | """ | 294 | def __init__(self, *arg, **kwargs): |
| 295 | """Default constructor""" | ||
| 296 | super().__init__(*arg, **kwargs) | ||
| 297 | self._config = None | ||
| 298 | self._instances = {} | ||
| 299 | |||
| 310 | def set_config(self, config): | 300 | def set_config(self, config): |
| 311 | """ | 301 | """ |
| 312 | Sets the configuration dict for the server, instantiates | 302 | Sets the configuration dict for the server, instantiates |
| 313 | the BEINC instances and initiates the defined OSD backends | 303 | the BEINC instances and initiates the defined OSD backends |
| 314 | """ | 304 | """ |
| 315 | self.__config = config | 305 | self._config = config |
| 316 | self.__instances = dict() | ||
| 317 | # initialize pynotify if the module exists and if needed | 306 | # initialize pynotify if the module exists and if needed |
| 318 | if pynotify: | 307 | if pynotify: |
| 319 | for instance in self.__config['server']['instances']: | 308 | for instance in self._config['server']['instances']: |
| 320 | # check if we have at least one instance that uses pynotify | 309 | # check if we have at least one instance that uses pynotify |
| 321 | # before initializing it | 310 | # before initializing it |
| 322 | if instance.get('osd_system', '').lower() == 'pynotify': | 311 | if instance.get('osd_system', '').lower() == 'pynotify': |
| 323 | if not pynotify.init('BEINC Notify'): | 312 | if not pynotify.init('BEINC Notify'): |
| 324 | sys.stderr.write('pynotify.init failed! Exiting...\n') | 313 | eprint('pynotify.init failed! Exiting...') |
| 325 | sys.exit(1) | 314 | sys.exit(1) |
| 326 | break | 315 | break |
| 316 | instance = {'name': 'Invalid'} | ||
| 327 | try: | 317 | try: |
| 328 | for instance in self.__config['server']['instances']: | 318 | for instance in self._config['server']['instances']: |
| 329 | self.__instances[instance['name']] = BEINCInstance(instance) | 319 | self._instances[instance['name']] = BEINCInstance(instance) |
| 330 | logger.info('Instance "{0}" added'.format(instance['name'])) | 320 | logger.info('Instance %s added', instance['name']) |
| 331 | except Exception as e: | 321 | except Exception as e: |
| 332 | sys.stderr.write('Unable to create instance "{0}": {1}\n'.format( | 322 | eprint('Unable to create instance "{0}": {1}'.format( |
| 333 | instance['name'], | 323 | instance['name'], |
| 334 | e)) | 324 | e)) |
| 335 | sys.exit(1) | 325 | sys.exit(1) |
| @@ -339,7 +329,7 @@ class BEINCNotifyServer(HTTPServer): | |||
| 339 | """ | 329 | """ |
| 340 | a property that returns the instance list (read-only) | 330 | a property that returns the instance list (read-only) |
| 341 | """ | 331 | """ |
| 342 | return self.__instances | 332 | return self._instances |
| 343 | 333 | ||
| 344 | 334 | ||
| 345 | if __name__ == '__main__': | 335 | if __name__ == '__main__': |
| @@ -390,15 +380,14 @@ if __name__ == '__main__': | |||
| 390 | parser.add_argument( | 380 | parser.add_argument( |
| 391 | '-v', '--version', | 381 | '-v', '--version', |
| 392 | action='version', | 382 | action='version', |
| 393 | version='%(prog)s {0}'.format(__version__), | 383 | version=f'%(prog)s {__version__}', |
| 394 | help='Display program-version and exit') | 384 | help='Display program-version and exit') |
| 395 | args = parser.parse_args() | 385 | args = parser.parse_args() |
| 396 | try: | 386 | try: |
| 397 | with open(args.config_file, 'r') as fp: | 387 | with open(args.config_file, 'r') as fp: |
| 398 | config_dict = json.load(fp) | 388 | config_dict = json.load(fp) |
| 399 | except Exception as e: | 389 | except Exception as e: |
| 400 | sys.stderr.write('Unable to parse {0}: {1}\n'.format(args.config_file, | 390 | eprint(f'Unable to parse {args.config_file}: {e}') |
| 401 | e)) | ||
| 402 | sys.exit(errno.EIO) | 391 | sys.exit(errno.EIO) |
| 403 | try: | 392 | try: |
| 404 | if os.path.isfile(args.logger_config): | 393 | if os.path.isfile(args.logger_config): |
| @@ -411,11 +400,11 @@ if __name__ == '__main__': | |||
| 411 | logger = logging.getLogger('beinc') | 400 | logger = logging.getLogger('beinc') |
| 412 | logger.info('BEINC starting. Loading config...') | 401 | logger.info('BEINC starting. Loading config...') |
| 413 | if config_dict.get('config_version') != BEINC_CURRENT_CONFIG_VERSION: | 402 | if config_dict.get('config_version') != BEINC_CURRENT_CONFIG_VERSION: |
| 414 | sys.stderr.write( | 403 | eprint( |
| 415 | 'WARNING: The version of the config-file: {0} ({1}) ' | 404 | 'WARNING: The version of the config-file: {0} ({1}) ' |
| 416 | 'does not correspond to the latest version supported ' | 405 | 'does not correspond to the latest version supported ' |
| 417 | 'by this program ({2})\nCheck beinc_config_sample.json ' | 406 | 'by this program ({2})\nCheck beinc_config_sample.json ' |
| 418 | 'for the newest features!\n'.format( | 407 | 'for the newest features!'.format( |
| 419 | args.config_file, | 408 | args.config_file, |
| 420 | config_dict.get('config_version', 'Not set'), | 409 | config_dict.get('config_version', 'Not set'), |
| 421 | BEINC_CURRENT_CONFIG_VERSION)) | 410 | BEINC_CURRENT_CONFIG_VERSION)) |
| @@ -441,6 +430,6 @@ if __name__ == '__main__': | |||
| 441 | except KeyboardInterrupt: | 430 | except KeyboardInterrupt: |
| 442 | print('\n\nTerminating...') | 431 | print('\n\nTerminating...') |
| 443 | except Exception as e: | 432 | except Exception as e: |
| 444 | sys.stderr.write('BEINCServer critical error: {0}\n'.format(e)) | 433 | eprint(f'BEINCServer critical error: {e}') |
| 445 | sys.exit(1) | 434 | sys.exit(1) |
| 446 | sys.exit(0) | 435 | sys.exit(0) |
diff --git a/beinc_weechat.py b/beinc_weechat.py index 132a22a..c1a1e1e 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) v3.0 | 3 | # Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0 |
| 4 | # Copyright (C) 2013-2019 Simeon Simeonov | 4 | # Copyright (C) 2013-2020 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 |
| @@ -15,35 +15,25 @@ | |||
| 15 | 15 | ||
| 16 | # 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 |
| 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 | 18 | """BEINC client for Weechat""" | |
| 19 | |||
| 20 | import datetime | 19 | import datetime |
| 21 | import json | 20 | import json |
| 22 | import os | 21 | import os |
| 23 | import socket | 22 | import socket |
| 24 | import ssl | 23 | import ssl |
| 25 | import sys | 24 | import urllib.parse |
| 25 | import urllib.request | ||
| 26 | 26 | ||
| 27 | import weechat | 27 | import weechat |
| 28 | 28 | ||
| 29 | PY2 = sys.version_info[0] == 2 | ||
| 30 | PY3 = sys.version_info[0] == 3 | ||
| 31 | |||
| 32 | if PY3: | ||
| 33 | from urllib.parse import urlencode | ||
| 34 | from urllib.request import urlopen | ||
| 35 | else: | ||
| 36 | from urllib import urlencode | ||
| 37 | from urllib2 import urlopen | ||
| 38 | |||
| 39 | 29 | ||
| 40 | __author__ = 'Simeon Simeonov' | 30 | __author__ = 'Simeon Simeonov' |
| 41 | __version__ = '3.0' | 31 | __version__ = '4.0' |
| 42 | __license__ = 'GPL3' | 32 | __license__ = 'GPL3' |
| 43 | 33 | ||
| 44 | 34 | ||
| 45 | enabled = True | 35 | enabled = True |
| 46 | global_values = dict() | 36 | global_values = {} |
| 47 | 37 | ||
| 48 | # few constants # | 38 | # few constants # |
| 49 | BEINC_POLICY_NONE = 0 | 39 | BEINC_POLICY_NONE = 0 |
| @@ -52,322 +42,317 @@ BEINC_POLICY_LIST_ONLY = 2 | |||
| 52 | BEINC_CURRENT_CONFIG_VERSION = 2 | 42 | BEINC_CURRENT_CONFIG_VERSION = 2 |
| 53 | 43 | ||
| 54 | 44 | ||
| 55 | class WeechatTarget(object): | 45 | class WeechatTarget: |
| 56 | """ | 46 | """ |
| 57 | The target (destination) class | 47 | The target (destination) class |
| 48 | |||
| 58 | Each remote destination is represented as a WeechatTarget object | 49 | Each remote destination is represented as a WeechatTarget object |
| 59 | """ | 50 | """ |
| 60 | 51 | ||
| 61 | def __init__(self, target_dict): | 52 | def __init__(self, target_dict): |
| 62 | """ | 53 | """ |
| 63 | target_dict: the config-dictionary node that represents this instance | 54 | :param target_dict: The config-dict node that represents this instance |
| 55 | :type target_dict: dict | ||
| 64 | """ | 56 | """ |
| 65 | self.__name = target_dict.get('name', '') | 57 | self._name = target_dict.get('name', '') |
| 66 | if self.__name == '': | 58 | if self._name == '': |
| 67 | raise Exception('"name" not defined for target') | 59 | raise Exception('"name" not defined for target') |
| 68 | self.__url = target_dict.get('target_url', '') | 60 | self._url = target_dict.get('target_url', '') |
| 69 | if self.__url == '': | 61 | if self._url == '': |
| 70 | raise Exception('"target_url" not defined for target') | 62 | raise Exception('"target_url" not defined for target') |
| 71 | self.__password = target_dict.get('target_password', '') | 63 | self._password = target_dict.get('target_password', '') |
| 72 | self.__pm_title_template = target_dict.get('pm_title_template', | 64 | self._pm_title_template = target_dict.get('pm_title_template', |
| 73 | '%s @ %S') | 65 | '%s @ %S') |
| 74 | self.__pm_message_template = target_dict.get('pm_message_template', | 66 | self._pm_message_template = target_dict.get('pm_message_template', |
| 75 | '%m') | 67 | '%m') |
| 76 | self.__cm_title_template = target_dict.get('cm_title_template', | 68 | self._cm_title_template = target_dict.get('cm_title_template', |
| 77 | '%c @ %S') | 69 | '%c @ %S') |
| 78 | self.__cm_message_template = target_dict.get('cm_message_template', | 70 | self._cm_message_template = target_dict.get('cm_message_template', |
| 79 | '%s -> %m') | 71 | '%s -> %m') |
| 80 | self.__nm_title_template = target_dict.get('nm_title_template', | 72 | self._nm_title_template = target_dict.get('nm_title_template', |
| 81 | '%c @ %S') | 73 | '%c @ %S') |
| 82 | self.__nm_message_template = target_dict.get('nm_message_template', | 74 | self._nm_message_template = target_dict.get('nm_message_template', |
| 83 | '%s -> %m') | 75 | '%s -> %m') |
| 84 | self.__chans = set(target_dict.get('channel_list', list())) | 76 | self._chans = set(target_dict.get('channel_list', [])) |
| 85 | self.__nicks = set(target_dict.get('nick_list', list())) | 77 | self._nicks = set(target_dict.get('nick_list', [])) |
| 86 | self.__chan_messages_policy = int(target_dict.get( | 78 | self._chan_messages_policy = int(target_dict.get( |
| 87 | 'channel_messages_policy', | 79 | 'channel_messages_policy', |
| 88 | BEINC_POLICY_LIST_ONLY)) | 80 | BEINC_POLICY_LIST_ONLY)) |
| 89 | self.__priv_messages_policy = int(target_dict.get( | 81 | self._priv_messages_policy = int(target_dict.get( |
| 90 | 'private_messages_policy', | 82 | 'private_messages_policy', |
| 91 | BEINC_POLICY_ALL)) | 83 | BEINC_POLICY_ALL)) |
| 92 | self.__notifications_policy = int(target_dict.get( | 84 | self._notifications_policy = int(target_dict.get( |
| 93 | 'notifications_policy', | 85 | 'notifications_policy', |
| 94 | BEINC_POLICY_ALL)) | 86 | BEINC_POLICY_ALL)) |
| 95 | self.__cert_file = target_dict.get('target_cert_file') | 87 | self._cert_file = target_dict.get('target_cert_file') |
| 96 | self.__timestamp_format = target_dict.get('target_timestamp_format', | 88 | self._timestamp_format = target_dict.get('target_timestamp_format', |
| 97 | '%H:%M:%S') | 89 | '%H:%M:%S') |
| 98 | self.__debug = bool(target_dict.get('debug', False)) | 90 | self._debug = bool(target_dict.get('debug', False)) |
| 99 | self.__enabled = bool(target_dict.get('enabled', True)) | 91 | self._enabled = bool(target_dict.get('enabled', True)) |
| 100 | self.__socket_timeout = int(target_dict.get('socket_timeout', 3)) | 92 | self._socket_timeout = int(target_dict.get('socket_timeout', 3)) |
| 101 | self.__ssl_ciphers = target_dict.get('ssl_ciphers', '') | 93 | self._ssl_ciphers = target_dict.get('ssl_ciphers', '') |
| 102 | self.__disable_hostname_check = bool( | 94 | self._disable_hostname_check = bool( |
| 103 | target_dict.get('disable-hostname-check', False)) | 95 | target_dict.get('disable-hostname-check', False)) |
| 104 | self.__ssl_version = target_dict.get('ssl_version', 'auto') | 96 | self._ssl_version = target_dict.get('ssl_version', 'auto') |
| 105 | self.__last_message = None # datetime.datetime instance | 97 | self._last_message = None # datetime.datetime instance |
| 106 | self.__context = None | 98 | self._context = None |
| 107 | self.__context_setup() | 99 | self._context_setup() |
| 108 | 100 | ||
| 109 | @property | 101 | @property |
| 110 | def name(self): | 102 | def name(self): |
| 111 | """ | 103 | """Target name (read-only property)""" |
| 112 | Target name (read-only property) | 104 | return self._name |
| 113 | """ | ||
| 114 | return self.__name | ||
| 115 | 105 | ||
| 116 | @property | 106 | @property |
| 117 | def chans(self): | 107 | def chans(self): |
| 118 | """ | 108 | """Target channel list (read-only property)""" |
| 119 | Target channel list (read-only property) | 109 | return self._chans |
| 120 | """ | ||
| 121 | return self.__chans | ||
| 122 | 110 | ||
| 123 | @property | 111 | @property |
| 124 | def nicks(self): | 112 | def nicks(self): |
| 125 | """ | 113 | """Target nick list (read-only property)""" |
| 126 | Target nick list (read-only property) | 114 | return self._nicks |
| 127 | """ | ||
| 128 | return self.__nicks | ||
| 129 | 115 | ||
| 130 | @property | 116 | @property |
| 131 | def channel_messages_policy(self): | 117 | def channel_messages_policy(self): |
| 132 | """ | 118 | """The target's channel messages policy (read-only property)""" |
| 133 | The target's channel messages policy (read-only property) | 119 | return self._chan_messages_policy |
| 134 | """ | ||
| 135 | return self.__chan_messages_policy | ||
| 136 | 120 | ||
| 137 | @property | 121 | @property |
| 138 | def private_messages_policy(self): | 122 | def private_messages_policy(self): |
| 139 | """ | 123 | """The target's private messages policy (read-only property)""" |
| 140 | The target's private messages policy (read-only property) | 124 | return self._priv_messages_policy |
| 141 | """ | ||
| 142 | return self.__priv_messages_policy | ||
| 143 | 125 | ||
| 144 | @property | 126 | @property |
| 145 | def notifications_policy(self): | 127 | def notifications_policy(self): |
| 146 | """ | 128 | """The target's notifications policy (read-only property)""" |
| 147 | The target's notifications policy (read-only property) | 129 | return self._notifications_policy |
| 148 | """ | ||
| 149 | return self.__notifications_policy | ||
| 150 | 130 | ||
| 151 | @property | 131 | @property |
| 152 | def enabled(self): | 132 | def enabled(self): |
| 153 | """ | 133 | """The target's enabled status (bool property)""" |
| 154 | The target's enabled status (bool property) | 134 | return self._enabled |
| 155 | """ | ||
| 156 | return self.__enabled | ||
| 157 | 135 | ||
| 158 | @enabled.setter | 136 | @enabled.setter |
| 159 | def enabled(self, value): | 137 | def enabled(self, value): |
| 160 | """ | 138 | """The target's enabled status (bool property)""" |
| 161 | The target's enabled status (bool property) | 139 | self._enabled = value |
| 162 | """ | ||
| 163 | self.__enabled = value | ||
| 164 | 140 | ||
| 165 | def __repr__(self): | 141 | def __repr__(self): |
| 166 | """ | 142 | """repr() implementation""" |
| 167 | """ | ||
| 168 | last_message = 'never' | 143 | last_message = 'never' |
| 169 | if self.__last_message is not None: | 144 | if self._last_message is not None: |
| 170 | last_message = self.__last_message.strftime('%Y-%m-%d %H:%M:%S') | 145 | last_message = self._last_message.strftime('%Y-%m-%d %H:%M:%S') |
| 171 | return ('name: {0}\nurl: {1}\nenabled: {2}\nchannel_list: {3}\n' | 146 | return ('name: {0}\nurl: {1}\nenabled: {2}\nchannel_list: {3}\n' |
| 172 | 'nick_list: {4}\nchannel_messages_policy: {5}\n' | 147 | 'nick_list: {4}\nchannel_messages_policy: {5}\n' |
| 173 | 'private_messages_policy: {6}\nnotifications_policy: {7}\n' | 148 | 'private_messages_policy: {6}\nnotifications_policy: {7}\n' |
| 174 | 'last message: {8}\nsocket timeout: {9}\nssl-version: {10}\n' | 149 | 'last message: {8}\nsocket timeout: {9}\nssl-version: {10}\n' |
| 175 | 'ciphers: {11}\ndisable hostname check: {12}\n' | 150 | 'ciphers: {11}\ndisable hostname check: {12}\n' |
| 176 | 'debug: {13}\n\n'.format( | 151 | 'debug: {13}\n\n'.format( |
| 177 | self.__name, | 152 | self._name, |
| 178 | self.__url, | 153 | self._url, |
| 179 | 'yes' if self.__enabled else 'no', | 154 | 'yes' if self._enabled else 'no', |
| 180 | ', '.join(self.__chans), | 155 | ', '.join(self._chans), |
| 181 | ', '.join(self.__nicks), | 156 | ', '.join(self._nicks), |
| 182 | self.__chan_messages_policy, | 157 | self._chan_messages_policy, |
| 183 | self.__priv_messages_policy, | 158 | self._priv_messages_policy, |
| 184 | self.__notifications_policy, | 159 | self._notifications_policy, |
| 185 | last_message, | 160 | last_message, |
| 186 | self.__socket_timeout, | 161 | self._socket_timeout, |
| 187 | self.__ssl_version, | 162 | self._ssl_version, |
| 188 | self.__ssl_ciphers or 'auto', | 163 | self._ssl_ciphers or 'auto', |
| 189 | 'yes' if self.__disable_hostname_check else 'no', | 164 | 'yes' if self._disable_hostname_check else 'no', |
| 190 | 'yes' if self.__debug else 'no')) | 165 | 'yes' if self._debug else 'no')) |
| 191 | 166 | ||
| 192 | def send_private_message_notification(self, values): | 167 | def send_private_message_notification(self, values): |
| 193 | """ | 168 | """ |
| 194 | sends a private message notification to the represented target | 169 | Sends a private message notification to the represented target |
| 195 | 170 | ||
| 196 | values: dict pupulated by the irc msg-handler | 171 | :param value: Dict pupulated by the irc msg-handler |
| 172 | :type value: dict | ||
| 197 | """ | 173 | """ |
| 198 | try: | 174 | try: |
| 199 | title = self.__fetch_formatted_str(self.__pm_title_template, | 175 | title = self._fetch_formatted_str(self._pm_title_template, |
| 200 | values) | 176 | values) |
| 201 | message = self.__fetch_formatted_str( | 177 | message = self._fetch_formatted_str( |
| 202 | self.__pm_message_template, | 178 | self._pm_message_template, |
| 203 | values) | 179 | values) |
| 204 | if not self.__send_beinc_message(title, message) and self.__debug: | 180 | if not self._send_beinc_message(title, message) and self._debug: |
| 205 | beinc_prnt( | 181 | beinc_prnt( |
| 206 | 'BEINC DEBUG: send_private_message_notification-ERROR ' | 182 | f'BEINC DEBUG: send_private_message_notification-ERROR ' |
| 207 | 'for "{0}": __send_beinc_message -> False'.format( | 183 | f'for "{self._name}": _send_beinc_message -> False') |
| 208 | self.__name)) | ||
| 209 | except Exception as e: | 184 | except Exception as e: |
| 210 | if self.__debug: | 185 | if self._debug: |
| 211 | beinc_prnt( | 186 | beinc_prnt( |
| 212 | 'BEINC DEBUG: send_private_message_notification-ERROR ' | 187 | f'BEINC DEBUG: send_private_message_notification-ERROR ' |
| 213 | 'for "{0}": {1}'.format(self.__name, e)) | 188 | f'for "{self._name}": {e}') |
| 214 | 189 | ||
| 215 | def send_channel_message_notification(self, values): | 190 | def send_channel_message_notification(self, values): |
| 216 | """ | 191 | """ |
| 217 | sends a channel message notification to the represented target | 192 | Sends a channel message notification to the represented target |
| 218 | 193 | ||
| 219 | values: dict pupulated by the irc msg-handler | 194 | :param value: Dict pupulated by the irc msg-handler |
| 195 | :type value: dict | ||
| 220 | """ | 196 | """ |
| 221 | try: | 197 | try: |
| 222 | title = self.__fetch_formatted_str(self.__cm_title_template, | 198 | title = self._fetch_formatted_str(self._cm_title_template, |
| 223 | values) | 199 | values) |
| 224 | message = self.__fetch_formatted_str(self.__cm_message_template, | 200 | message = self._fetch_formatted_str(self._cm_message_template, |
| 225 | values) | 201 | values) |
| 226 | if not self.__send_beinc_message(title, message) and self.__debug: | 202 | if not self._send_beinc_message(title, message) and self._debug: |
| 227 | beinc_prnt( | 203 | beinc_prnt( |
| 228 | 'BEINC DEBUG: send_channel_message_notification-ERROR ' | 204 | f'BEINC DEBUG: send_channel_message_notification-ERROR ' |
| 229 | 'for "{0}": __send_beinc_message -> False'.format( | 205 | f'for "{self._name}": _send_beinc_message -> False') |
| 230 | self.__name)) | ||
| 231 | except Exception as e: | 206 | except Exception as e: |
| 232 | if self.__debug: | 207 | if self._debug: |
| 233 | beinc_prnt( | 208 | beinc_prnt( |
| 234 | 'BEINC DEBUG: send_channel_message_notification-ERROR ' | 209 | f'BEINC DEBUG: send_channel_message_notification-ERROR ' |
| 235 | 'for "{0}": {1}'.format(self.__name, e)) | 210 | f'for "{self._name}": {e}') |
| 236 | 211 | ||
| 237 | def send_notify_message_notification(self, values): | 212 | def send_notify_message_notification(self, values): |
| 238 | """ | 213 | """ |
| 239 | sends a notify message notification to the represented target | 214 | Sends a notify message notification to the represented target |
| 240 | 215 | ||
| 241 | values: dict pupulated by the irc msg-handler | 216 | :param value: Dict pupulated by the irc msg-handler |
| 217 | :type value: dict | ||
| 242 | """ | 218 | """ |
| 243 | try: | 219 | try: |
| 244 | title = self.__fetch_formatted_str(self.__nm_title_template, | 220 | title = self._fetch_formatted_str(self._nm_title_template, |
| 245 | values) | 221 | values) |
| 246 | message = self.__fetch_formatted_str(self.__nm_message_template, | 222 | message = self._fetch_formatted_str(self._nm_message_template, |
| 247 | values) | 223 | values) |
| 248 | if not self.__send_beinc_message(title, message) and self.__debug: | 224 | if not self._send_beinc_message(title, message) and self._debug: |
| 249 | beinc_prnt( | 225 | beinc_prnt( |
| 250 | 'BEINC DEBUG: send_notify_message_notification-ERROR ' | 226 | f'BEINC DEBUG: send_notify_message_notification-ERROR ' |
| 251 | 'for "{0}": __send_beinc_message -> False'.format( | 227 | f'for "{self._name}": _send_beinc_message -> False') |
| 252 | self.__name)) | ||
| 253 | except Exception as e: | 228 | except Exception as e: |
| 254 | if self.__debug: | 229 | if self._debug: |
| 255 | beinc_prnt( | 230 | beinc_prnt( |
| 256 | 'BEINC DEBUG: send_notify_message_notification-ERROR ' | 231 | f'BEINC DEBUG: send_notify_message_notification-ERROR ' |
| 257 | 'for "{0}": {1}'.format(self.__name, e)) | 232 | f'for "{self._name}": {e}') |
| 258 | 233 | ||
| 259 | def send_broadcast_notification(self, message): | 234 | def send_broadcast_notification(self, message): |
| 260 | """ | 235 | """ |
| 261 | sends a 'pure' broadcast / test message notification | 236 | Sends a 'pure' broadcast / test message notification |
| 262 | to the represented target | 237 | to the represented target |
| 263 | 238 | ||
| 264 | message: a single message string | 239 | :param message: A single message string |
| 240 | :type message: str | ||
| 265 | """ | 241 | """ |
| 266 | try: | 242 | try: |
| 267 | title = 'BEINC broadcast' | 243 | title = 'BEINC broadcast' |
| 268 | if not self.__send_beinc_message(title, message) and self.__debug: | 244 | if not self._send_beinc_message(title, message) and self._debug: |
| 269 | beinc_prnt( | 245 | beinc_prnt( |
| 270 | 'BEINC DEBUG: send_broadcast_notification-ERROR ' | 246 | f'BEINC DEBUG: send_broadcast_notification-ERROR ' |
| 271 | 'for "{0}": __send_beinc_message -> False'.format( | 247 | f'for "{self._name}": _send_beinc_message -> False') |
| 272 | self.__name)) | ||
| 273 | except Exception as e: | 248 | except Exception as e: |
| 274 | if self.__debug: | 249 | if self._debug: |
| 275 | beinc_prnt( | 250 | beinc_prnt( |
| 276 | 'BEINC DEBUG: send_broadcast_notification-ERROR ' | 251 | f'BEINC DEBUG: send_broadcast_notification-ERROR ' |
| 277 | 'for "{0}": {1}'.format(self.__name, e)) | 252 | f'for "{self._name}": {e}') |
| 278 | 253 | ||
| 279 | def __context_setup(self): | 254 | def _context_setup(self): |
| 280 | """ | 255 | """Sets up the SSL context""" |
| 281 | """ | 256 | if self._context is not None: |
| 282 | if self.__context is not None: | ||
| 283 | return True | 257 | return True |
| 284 | try: | 258 | try: |
| 285 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) | 259 | context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) |
| 286 | context.verify_mode = ssl.CERT_NONE | 260 | context.verify_mode = ssl.CERT_NONE |
| 287 | if self.__cert_file: | 261 | if self._cert_file: |
| 288 | context.verify_mode = ssl.CERT_REQUIRED | 262 | context.verify_mode = ssl.CERT_REQUIRED |
| 289 | context.load_verify_locations(cafile=os.path.expanduser( | 263 | context.load_verify_locations(cafile=os.path.expanduser( |
| 290 | self.__cert_file)) | 264 | self._cert_file)) |
| 291 | context.check_hostname = bool( | 265 | context.check_hostname = bool( |
| 292 | not self.__disable_hostname_check) | 266 | not self._disable_hostname_check) |
| 293 | if self.__ssl_ciphers and self.__ssl_ciphers != 'auto': | 267 | if self._ssl_ciphers and self._ssl_ciphers != 'auto': |
| 294 | context.set_ciphers(self.__ssl_ciphers) | 268 | context.set_ciphers(self._ssl_ciphers) |
| 295 | self.__context = context | 269 | self._context = context |
| 296 | return True | 270 | return True |
| 297 | except ssl.SSLError as e: | 271 | except ssl.SSLError as e: |
| 298 | if self.__debug: | 272 | if self._debug: |
| 299 | beinc_prnt('BEINC DEBUG: SSL/TLS error: {0}\n'.format(e)) | 273 | beinc_prnt(f'BEINC DEBUG: SSL/TLS error: {e}\n') |
| 300 | except Exception as e: | 274 | except Exception as e: |
| 301 | if self.__debug: | 275 | if self._debug: |
| 302 | beinc_prnt('BEINC DEBUG: Generic context error: {0}\n'.format( | 276 | beinc_prnt(f'BEINC DEBUG: Generic context error: {e}\n') |
| 303 | e)) | 277 | self._context = None |
| 304 | self.__context = None | ||
| 305 | return False | 278 | return False |
| 306 | 279 | ||
| 307 | def __fetch_formatted_str(self, template, values): | 280 | def _fetch_formatted_str(self, template, values): |
| 308 | """ | 281 | """ |
| 309 | returns a formatted string by replacing the defined | 282 | Returns a formatted string by replacing the defined |
| 310 | macros in 'template' the the corresponding values from 'values' | 283 | macros in 'template' the the corresponding values from 'values' |
| 311 | 284 | ||
| 312 | values: dict | 285 | :param template: The template to use |
| 313 | template: str | 286 | :type template: str |
| 287 | |||
| 288 | :param values: The values dict | ||
| 289 | :type values: dict | ||
| 290 | |||
| 291 | :return: The formatted string | ||
| 292 | :rtype: str | ||
| 314 | """ | 293 | """ |
| 315 | timestamp = datetime.datetime.now().strftime(self.__timestamp_format) | 294 | timestamp = datetime.datetime.now().strftime(self._timestamp_format) |
| 316 | replacements = {'%S': values['server'], | 295 | replacements = {'%S': values['server'], |
| 317 | '%s': values['source_nick'], | 296 | '%s': values['source_nick'], |
| 318 | '%c': values['channel'], | 297 | '%c': values['channel'], |
| 319 | '%m': values['message'], | 298 | '%m': values['message'], |
| 320 | '%t': timestamp, | 299 | '%t': timestamp, |
| 321 | '%p': u'BEINC', | 300 | '%p': 'BEINC', |
| 322 | '%n': values['own_nick']} | 301 | '%n': values['own_nick']} |
| 323 | for key, value in replacements.items(): | 302 | for key, value in replacements.items(): |
| 324 | template = template.replace(key, value) | 303 | template = template.replace(key, value) |
| 325 | return template.encode('utf-8') | 304 | return template |
| 326 | 305 | ||
| 327 | def __send_beinc_message(self, title, message): | 306 | def _send_beinc_message(self, title, message): |
| 328 | """ | 307 | """ |
| 329 | the function implements the BEINC "protocol" by generating a simple | 308 | The method implements the BEINC "protocol" by generating a simple |
| 330 | POST request | 309 | POST request |
| 310 | |||
| 311 | :param title: The title | ||
| 312 | :type title: str | ||
| 313 | |||
| 314 | :param message: The message | ||
| 315 | :type message: str | ||
| 316 | |||
| 317 | :return: The status | ||
| 318 | :rtype: bool | ||
| 331 | """ | 319 | """ |
| 332 | try: | 320 | try: |
| 333 | if self.__context is None and not self.__context_setup(): | 321 | if self._context is None and not self._context_setup(): |
| 334 | return False | 322 | return False |
| 335 | response = urlopen( | 323 | response = urllib.request.urlopen( |
| 336 | self.__url, | 324 | self._url, |
| 337 | data=urlencode( | 325 | data=urllib.parse.urlencode( |
| 338 | ( | 326 | ( |
| 339 | ('resource_name', self.__name), | 327 | ('resource_name', self._name), |
| 340 | ('password', self.__password), | 328 | ('password', self._password), |
| 341 | ('title', title), | 329 | ('title', title), |
| 342 | ('message', message) | 330 | ('message', message) |
| 343 | )).encode('utf-8'), | 331 | )).encode('utf-8'), |
| 344 | timeout=self.__socket_timeout, | 332 | timeout=self._socket_timeout, |
| 345 | context=self.__context) | 333 | context=self._context) |
| 346 | response_dict = json.loads(response.read().decode('utf-8')) | 334 | response_dict = json.loads(response.read().decode('utf-8')) |
| 347 | if response.code != 200: | 335 | if response.code != 200: |
| 348 | raise socket.error(response_dict.get('message', '')) | 336 | raise socket.error(response_dict.get('message', '')) |
| 349 | if self.__debug: | 337 | if self._debug: |
| 350 | beinc_prnt('BEINC DEBUG: Server responded: {0}'.format( | 338 | beinc_prnt('BEINC DEBUG: Server responded: {0}'.format( |
| 351 | response_dict.get('message'))) | 339 | response_dict.get('message'))) |
| 352 | self.__last_message = datetime.datetime.now() | 340 | self._last_message = datetime.datetime.now() |
| 353 | return True | 341 | return True |
| 354 | except ssl.SSLError as e: | 342 | except ssl.SSLError as e: |
| 355 | if self.__debug: | 343 | if self._debug: |
| 356 | beinc_prnt('BEINC DEBUG: SSL/TLS error: {0}\n'.format(e)) | 344 | beinc_prnt(f'BEINC DEBUG: SSL/TLS error: {e}\n') |
| 357 | except socket.error as e: | 345 | except socket.error as e: |
| 358 | if self.__debug: | 346 | if self._debug: |
| 359 | beinc_prnt('BEINC DEBUG: Connection error: {0}\n'.format(e)) | 347 | beinc_prnt(f'BEINC DEBUG: Connection error: {e}\n') |
| 360 | except Exception as e: | 348 | except Exception as e: |
| 361 | if self.__debug: | 349 | if self._debug: |
| 362 | beinc_prnt('BEINC DEBUG: Unable to send message: {0}\n'.format( | 350 | beinc_prnt(f'BEINC DEBUG: Unable to send message: {e}\n') |
| 363 | e)) | ||
| 364 | return False | 351 | return False |
| 365 | 352 | ||
| 366 | 353 | ||
| 367 | def beinc_prnt(message_str): | 354 | def beinc_prnt(message_str): |
| 368 | """ | 355 | """wrapper around weechat.prnt""" |
| 369 | wrapper around weechat.prnt | ||
| 370 | """ | ||
| 371 | if global_values['use_current_buffer']: | 356 | if global_values['use_current_buffer']: |
| 372 | weechat.prnt(weechat.current_buffer(), message_str) | 357 | weechat.prnt(weechat.current_buffer(), message_str) |
| 373 | else: | 358 | else: |
| @@ -375,9 +360,7 @@ def beinc_prnt(message_str): | |||
| 375 | 360 | ||
| 376 | 361 | ||
| 377 | def beinc_cmd_broadcast_handler(cmd_tokens): | 362 | def beinc_cmd_broadcast_handler(cmd_tokens): |
| 378 | """ | 363 | """handles: '/beinc broadcast' command actions""" |
| 379 | handles: '/beinc broadcast' command actions | ||
| 380 | """ | ||
| 381 | if not cmd_tokens: | 364 | if not cmd_tokens: |
| 382 | beinc_prnt('beinc broadcast <message>') | 365 | beinc_prnt('beinc broadcast <message>') |
| 383 | return weechat.WEECHAT_RC_OK | 366 | return weechat.WEECHAT_RC_OK |
| @@ -388,9 +371,7 @@ def beinc_cmd_broadcast_handler(cmd_tokens): | |||
| 388 | 371 | ||
| 389 | 372 | ||
| 390 | def beinc_cmd_target_handler(cmd_tokens): | 373 | def beinc_cmd_target_handler(cmd_tokens): |
| 391 | """ | 374 | """handles: '/beinc target' command actions""" |
| 392 | handles: '/beinc target' command actions | ||
| 393 | """ | ||
| 394 | if not cmd_tokens or cmd_tokens[0] not in ['list', 'enable', 'disable']: | 375 | if not cmd_tokens or cmd_tokens[0] not in ['list', 'enable', 'disable']: |
| 395 | beinc_prnt('beinc target [ list | enable <name> | disable <name> ]') | 376 | beinc_prnt('beinc target [ list | enable <name> | disable <name> ]') |
| 396 | return weechat.WEECHAT_RC_OK | 377 | return weechat.WEECHAT_RC_OK |
| @@ -410,10 +391,10 @@ def beinc_cmd_target_handler(cmd_tokens): | |||
| 410 | for target in target_list: | 391 | for target in target_list: |
| 411 | if target.name == name: | 392 | if target.name == name: |
| 412 | target.enabled = True | 393 | target.enabled = True |
| 413 | beinc_prnt('target "{0}" enabled'.format(name)) | 394 | beinc_prnt(f'target "{name}" enabled') |
| 414 | break | 395 | break |
| 415 | else: | 396 | else: |
| 416 | beinc_prnt('no matching target for "{0}"'.format(name)) | 397 | beinc_prnt(f'no matching target for "{name}"') |
| 417 | elif cmd_tokens[0] == 'disable': | 398 | elif cmd_tokens[0] == 'disable': |
| 418 | if not cmd_tokens[1:]: | 399 | if not cmd_tokens[1:]: |
| 419 | beinc_prnt('missing a name-argument') | 400 | beinc_prnt('missing a name-argument') |
| @@ -422,17 +403,15 @@ def beinc_cmd_target_handler(cmd_tokens): | |||
| 422 | for target in target_list: | 403 | for target in target_list: |
| 423 | if target.name == name: | 404 | if target.name == name: |
| 424 | target.enabled = False | 405 | target.enabled = False |
| 425 | beinc_prnt('target "{0}" disabled'.format(name)) | 406 | beinc_prnt(f'target "{name}" disabled') |
| 426 | break | 407 | break |
| 427 | else: | 408 | else: |
| 428 | beinc_prnt('no matching target for "{0}"'.format(name)) | 409 | beinc_prnt(f'no matching target for "{name}"') |
| 429 | return weechat.WEECHAT_RC_OK | 410 | return weechat.WEECHAT_RC_OK |
| 430 | 411 | ||
| 431 | 412 | ||
| 432 | def beinc_command(data, buffer_obj, args): | 413 | def beinc_command(data, buffer_obj, args): |
| 433 | """ | 414 | """Callback function handling the Weechat's /beinc command""" |
| 434 | Callback function handling the Weechat's /beinc command | ||
| 435 | """ | ||
| 436 | global enabled | 415 | global enabled |
| 437 | cmd_tokens = args.split() | 416 | cmd_tokens = args.split() |
| 438 | if not cmd_tokens: | 417 | if not cmd_tokens: |
| @@ -457,15 +436,13 @@ def beinc_command(data, buffer_obj, args): | |||
| 457 | 436 | ||
| 458 | 437 | ||
| 459 | def beinc_privmsg_handler(data, signal, signal_data): | 438 | def beinc_privmsg_handler(data, signal, signal_data): |
| 460 | """ | 439 | """Callback function the *PRIVMSG* IRC messages hooked by Weechat""" |
| 461 | Callback function the *PRIVMSG* IRC messages hooked by Weechat | ||
| 462 | """ | ||
| 463 | if not enabled: | 440 | if not enabled: |
| 464 | return weechat.WEECHAT_RC_OK | 441 | return weechat.WEECHAT_RC_OK |
| 465 | prvmsg_dict = weechat.info_get_hashtable('irc_message_parse', | 442 | prvmsg_dict = weechat.info_get_hashtable('irc_message_parse', |
| 466 | {'message': signal_data}) | 443 | {'message': signal_data}) |
| 467 | # packing the privmsg handler values | 444 | # packing the privmsg handler values |
| 468 | ph_values = dict() | 445 | ph_values = {} |
| 469 | ph_values['server'] = signal.split(',')[0] | 446 | ph_values['server'] = signal.split(',')[0] |
| 470 | ph_values['own_nick'] = weechat.info_get('irc_nick', ph_values['server']) | 447 | ph_values['own_nick'] = weechat.info_get('irc_nick', ph_values['server']) |
| 471 | ph_values['channel'] = prvmsg_dict['arguments'].split(':')[0].strip() | 448 | ph_values['channel'] = prvmsg_dict['arguments'].split(':')[0].strip() |
| @@ -529,8 +506,8 @@ def beinc_init(): | |||
| 529 | global global_values | 506 | global global_values |
| 530 | 507 | ||
| 531 | # global chans/nicks sets are used to speed up the filtering | 508 | # global chans/nicks sets are used to speed up the filtering |
| 532 | global_values = dict() | 509 | global_values = {} |
| 533 | target_list = list() | 510 | target_list = [] |
| 534 | custom_error = '' | 511 | custom_error = '' |
| 535 | global_values['global_channel_messages_policy'] = False | 512 | global_values['global_channel_messages_policy'] = False |
| 536 | global_values['global_private_messages_policy'] = False | 513 | global_values['global_private_messages_policy'] = False |
| @@ -541,7 +518,7 @@ def beinc_init(): | |||
| 541 | beinc_config_file_str = os.path.join( | 518 | beinc_config_file_str = os.path.join( |
| 542 | weechat.info_get('weechat_dir', ''), | 519 | weechat.info_get('weechat_dir', ''), |
| 543 | 'beinc_weechat.json') | 520 | 'beinc_weechat.json') |
| 544 | beinc_prnt('Parsing {0}...'.format(beinc_config_file_str)) | 521 | beinc_prnt(f'Parsing {beinc_config_file_str}...') |
| 545 | custom_error = 'load error' | 522 | custom_error = 'load error' |
| 546 | with open(beinc_config_file_str, 'r') as fp: | 523 | with open(beinc_config_file_str, 'r') as fp: |
| 547 | config_dict = json.load(fp, encoding='utf-8') | 524 | config_dict = json.load(fp, encoding='utf-8') |
| @@ -562,7 +539,7 @@ def beinc_init(): | |||
| 562 | try: | 539 | try: |
| 563 | new_target = WeechatTarget(target) | 540 | new_target = WeechatTarget(target) |
| 564 | except Exception as e: | 541 | except Exception as e: |
| 565 | beinc_prnt('Unable to add target: {0}'.format(e)) | 542 | beinc_prnt(f'Unable to add target: {e}') |
| 566 | continue | 543 | continue |
| 567 | if new_target.channel_messages_policy: | 544 | if new_target.channel_messages_policy: |
| 568 | global_values['global_channel_messages_policy'] = True | 545 | global_values['global_channel_messages_policy'] = True |
| @@ -571,12 +548,11 @@ def beinc_init(): | |||
| 571 | if new_target.notifications_policy: | 548 | if new_target.notifications_policy: |
| 572 | global_values['global_notifications_policy'] = True | 549 | global_values['global_notifications_policy'] = True |
| 573 | target_list.append(new_target) | 550 | target_list.append(new_target) |
| 574 | beinc_prnt('BEINC target "{0}" added'.format(new_target.name)) | 551 | beinc_prnt(f'BEINC target "{new_target.name}" added') |
| 575 | beinc_prnt('Done!') | 552 | beinc_prnt('Done!') |
| 576 | except Exception as e: | 553 | except Exception as e: |
| 577 | beinc_prnt('ERROR: unable to parse {0}: {1} - {2}\n' | 554 | beinc_prnt(f'ERROR: unable to parse {beinc_config_file_str}: ' |
| 578 | 'BEINC is now disabled'.format( | 555 | f'{custom_error} - {e}\nBEINC is now disabled') |
| 579 | beinc_config_file_str, custom_error, e)) | ||
| 580 | enabled = False | 556 | enabled = False |
| 581 | # do not return error / exit the script | 557 | # do not return error / exit the script |
| 582 | # in order to give a smoother opportunity to fix a 'broken' config | 558 | # in order to give a smoother opportunity to fix a 'broken' config |
