summaryrefslogtreecommitdiff
path: root/beinc_pull.py
diff options
context:
space:
mode:
Diffstat (limited to 'beinc_pull.py')
-rwxr-xr-xbeinc_pull.py248
1 files changed, 248 insertions, 0 deletions
diff --git a/beinc_pull.py b/beinc_pull.py
new file mode 100755
index 0000000..9c44c00
--- /dev/null
+++ b/beinc_pull.py
@@ -0,0 +1,248 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v4.0
5# Copyright (C) 2013-2020 Simeon Simeonov
6
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
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16
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/>.
19"""A simple client that pulls notifications from a BEINC server"""
20import argparse
21import errno
22import getpass
23import io
24import json
25import os
26import sched
27import socket
28import ssl
29import sys
30import time
31import urllib.parse
32import urllib.request
33
34try:
35 import notify2 as pynotify
36except ImportError:
37 pynotify = None
38
39
40__author__ = 'Simeon Simeonov'
41__version__ = '4.0'
42__license__ = 'GPL3'
43
44
45def eprint(*arg, **kwargs):
46 """stdderr print wrapper"""
47 print(*arg, file=sys.stderr, flush=True, **kwargs)
48
49
50def 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
78def display_notification(args, title, message):
79 """
80 A wrapper function for displaying a single notification
81
82 :param args: The arguments assigned from argparse
83 :type args: argparse.Namespace
84
85 :param title: The title
86 :type title: str
87
88 :param message: The message
89 :type message: str
90 """
91 if args.osd_sys == 'pynotify':
92 if not pynotify:
93 raise Exception(
94 'Could not load "pynotify".'
95 'Please install "pynotify" or use a different osd-system!'
96 'Terminating...')
97 if not pynotify.init('BEINC Notify'):
98 raise Exception('There was a problem with libnotify')
99 notification_obj = pynotify.Notification(summary=title,
100 message=message)
101 notification_obj.timeout = 1000 * args.osd_timeout
102 notification_obj.set_category('im.received')
103 notification_obj.show()
104 else:
105 raise Exception(f'Unsupported osd-system: {args.osd_sys}')
106
107
108def pull_notifications(scheduler, args):
109 """
110 The core function initiated by the scheduler performing a single pull
111
112 :param scheduler: Scheduler object
113 :type scheduler: sched.scheduler
114
115 :param args: The arguments assigned from argparse
116 :type args: argparse.Namespace
117 """
118 try:
119 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
120 context.verify_mode = ssl.CERT_NONE
121 if args.cert:
122 context.verify_mode = ssl.CERT_REQUIRED
123 context.load_verify_locations(cafile=os.path.expanduser(args.cert))
124 context.check_hostname = bool(not args.disable_hostname_check)
125 if args.ciphers:
126 context.set_ciphers(args.ciphers)
127 response = urllib.request.urlopen(
128 args.url,
129 data=urllib.parse.urlencode(
130 (
131 ('resource_name', args.rname),
132 ('password', args.password)
133 )).encode('utf-8'),
134 timeout=args.socket_timeout,
135 context=context)
136 response_dict = json.loads(response.read().decode('utf-8'))
137 if response.code != 200:
138 raise socket.error(response_dict.get('message', ''))
139 for entry in response_dict['data']['messages']:
140 display_notification(args,
141 entry.get('title', ''),
142 entry.get('message', ''))
143 response.close()
144 scheduler.enter(args.frequency,
145 1,
146 pull_notifications,
147 (scheduler, args))
148 except ssl.SSLError as e:
149 eprint(f'BEINC SSL/TLS error: {e}')
150 sys.exit(errno.EPERM)
151 except socket.error as e:
152 eprint(f'BEINC connection error: {e}')
153 sys.exit(errno.EPERM)
154 except Exception as e:
155 eprint(f'BEINC generic client error: {e}')
156 sys.exit(errno.EPERM)
157
158
159def main(inargs=None):
160 """main entry"""
161 parser = argparse.ArgumentParser(
162 description='The following options are available')
163 parser.add_argument(
164 'url',
165 metavar='URL',
166 type=str,
167 help='BEINC server destination URL')
168 parser.add_argument(
169 '-c', '--cert-file',
170 metavar='FILE',
171 type=str,
172 dest='cert',
173 default='',
174 help='CA-cert to check the server-cert against '
175 '(default: Check disabled)')
176 parser.add_argument(
177 '--ciphers',
178 metavar='CIPHERS',
179 type=str,
180 dest='ciphers',
181 default='',
182 help='Preferred ciphers list (default: auto)')
183 parser.add_argument(
184 '--disable-hostname-check',
185 action='store_true',
186 dest='disable_hostname_check',
187 default=False,
188 help='Do not check whether server cert matches server hostname')
189 parser.add_argument(
190 '-f', '--frequency',
191 metavar='SECONDS',
192 type=int,
193 dest='frequency',
194 default=10,
195 help='Pulling frequency in seconds (default: 10)')
196 parser.add_argument(
197 '-n', '--resource-name',
198 metavar='NAME',
199 type=str,
200 dest='rname',
201 required=True,
202 help='The name of the BEINC-resource on the remote server')
203 parser.add_argument(
204 '-o', '--osd-system',
205 metavar='SYSTEM',
206 type=str,
207 dest='osd_sys',
208 default='pynotify',
209 help='BEINC osd-system: "pynotify" (default)')
210 parser.add_argument(
211 '-p', '--password',
212 metavar='PASSWORD[FILE]',
213 type=str,
214 dest='password',
215 default='',
216 help='BEINC taget-password / text-file containing the target password'
217 ' (default & recommended: prompt for passwd)')
218 parser.add_argument(
219 '-T', '--socket-timeout',
220 metavar='SECONDS',
221 type=int,
222 dest='socket_timeout',
223 default=3,
224 help='Socket timeout in seconds (0=Python default) (default: 3)')
225 parser.add_argument(
226 '-t', '--osd-timeout',
227 metavar='SECONDS',
228 type=int,
229 dest='osd_timeout',
230 default=5,
231 help='OSD timeout (default: 5)')
232 parser.add_argument(
233 '-v', '--version',
234 action='version',
235 version=f'%(prog)s {__version__}',
236 help='display program-version and exit')
237 args = parser.parse_args(inargs)
238 args.password = fetch_password(args.password)
239 scheduler = sched.scheduler(time.time, time.sleep)
240 scheduler.enter(args.frequency,
241 1,
242 pull_notifications,
243 (scheduler, args))
244 scheduler.run()
245
246
247if __name__ == '__main__':
248 main()