forked from abertschi/postcards
/
postcards.py
executable file
·242 lines (202 loc) · 9.81 KB
/
postcards.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
from postcard_creator import postcard_creator
import sys
import base64
import json
import os
import argparse, textwrap
from argparse import RawTextHelpFormatter
import urllib
class Postcards:
def main(self, argv):
args = self.get_argparser(argv)
self.validate_cli_args(args=args)
postcard_creator.Debug.debug = True # always true?
if args.trace:
postcard_creator.Debug.debug = True
postcard_creator.Debug.trace = True
if args.debug:
postcard_creator.Debug.debug = True
if args.encrypt:
print(self._encrypt(args.encrypt[0], args.encrypt[1]))
exit(0)
elif args.decrypt:
print(self._decrypt(args.decrypt[0], args.decrypt[1]))
exit(0)
config = self._read_config(args.config[0])
accounts = self._get_accounts_from_config(config)
self._validate_config(config, accounts)
self.send(accounts=accounts,
recipient=config.get('recipient'),
sender=config.get('sender') if config.get('sender') else config.get('recipient'),
mock=bool(args.mock),
plugin_payload=config.get('payload'),
picture_stream=self._read_picture(args.picture) if args.picture else None,
message=args.message,
cli_args=args)
def send(self, accounts, recipient, sender, mock=False, plugin_payload={},
message=None, picture_stream=None, cli_args=None):
pcc_wrapper = None
for account in accounts:
token = postcard_creator.Token()
if token.has_valid_credentials(account.get('username'), account.get('password')):
pcc = postcard_creator.PostcardCreator(token)
if pcc.has_free_postcard():
pcc_wrapper = pcc
break
if not pcc_wrapper:
print('error: No valid account given. Run later again or check accounts.')
exit(1)
if self._is_plugin():
img_and_text = self.get_img_and_text(plugin_payload, cli_args=cli_args)
if not message:
message = img_and_text['text']
if not picture_stream:
picture_stream = img_and_text['img']
card = postcard_creator.Postcard(message=message,
recipient=self._create_recipient(recipient),
sender=self._create_sender(sender),
picture_stream=picture_stream)
# Never send postcard, because postcard_wrapper is not yet working correctly
pcc_wrapper.send_free_card(card, mock_send=True)
if not mock:
print('Postcard sent!')
else:
print('Postcard not sent because of mock=True')
def _create_recipient(self, recipient):
return postcard_creator.Recipient(prename=recipient.get('firstname'),
lastname=recipient.get('lastname'),
street=recipient.get('street'),
zip_code=recipient.get('zipcode'),
place=recipient.get('city'))
def _create_sender(self, sender):
return postcard_creator.Sender(prename=sender.get('firstname'),
lastname=sender.get('lastname'),
street=sender.get('street'),
zip_code=sender.get('zipcode'),
place=sender.get('city'))
def _get_accounts_from_config(self, config, key=None):
accounts = []
for account in config.get('accounts'):
accounts.append({
'username': account.get('username'),
'password': account.get('password') if not key else self._decrypt(key, account.get('password'))
})
return accounts
def validate_cli_args(self, args):
if not any([args.config, args.encrypt, args.decrypt]):
print('error: The following arguments are required: --config, --encrypt, or --decrypt')
exit(1)
if not self._is_plugin():
if not args.picture and args.config:
print('error: No picture set. Run a plugin or set --picture')
exit(1)
def _validate_config(self, config, accounts):
if not accounts:
print('error: No account set in config/accounts file')
exit(1)
if not config.get('recipient'):
print('error: No recipient sent in config file')
exit(1)
def _read_config(self, location):
location = self._make_absolute_path(location)
if not os.path.isfile(location):
print('error: Config file not found at ' + location)
exit(1)
with open(location) as f:
return json.load(f)
def _read_picture(self, location):
if location.startswith('http'):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) ' +
'Chrome/23.0.1271.64 Safari/537.11',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
'Accept-Encoding': 'none',
'Accept-Language': 'en-US,en;q=0.8',
'Connection': 'keep-alive'}
request = urllib.request.Request(location, None, headers)
return urllib.request.urlopen(request)
else:
location = self._make_absolute_path(location)
if not os.path.isfile(location):
print('error: Picture not found at ' + location)
exit(1)
return open(location, 'rb')
def _make_absolute_path(self, path):
if os.path.isabs(path):
return path
else:
return str(os.path.join(os.getcwd(), path))
def _encrypt(self, key, msg):
return self._encode(key.encode('utf-8'), msg.encode('utf-8')).decode('utf-8')
def _decrypt(self, key, msg):
return self._decode(key.encode('utf-8'), msg.encode('utf-8')).decode('utf-8')
def _encode(self, key, clear):
# https://stackoverflow.com/questions/2490334/simple-way-to-encode-a-string-according-to-a-password
enc = []
for i in range(len(clear)):
key_c = key[i % len(key)]
enc_c = (clear[i] + key_c) % 256
enc.append(enc_c)
return base64.urlsafe_b64encode(bytes(enc))
def _decode(self, key, enc):
dec = []
enc = base64.urlsafe_b64decode(enc)
for i in range(len(enc)):
key_c = key[i % len(key)]
dec_c = (enc[i] - key_c) % 256
dec.append(dec_c)
return bytes(dec)
def _is_plugin(self):
return not type(self).__name__ == 'Postcards'
def get_img_and_text(self, plugin_payload, cli_args):
"""
To be overwritten by a plugin
:param plugin_payload: plugin config from config file
:param parser args added in Postcards.encrich_parser(). See docs of argparse
:return: an image and text
"""
raise Exception('Dont run this class directly. Use a plugin instead')
return {'img': None, 'text': None} # structure of object to return
def enrich_parser(self, parser):
"""
A plugin can add CLI options to the parser
:param parser:
:return: nothing
"""
pass
def get_argparser(self, argv):
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter,
description='Postcards is a CLI for the Swiss Postcard Creator')
parser.add_argument('--config', nargs=1, required=False, type=str,
help='location to the json config file')
parser.add_argument('--accounts-file', default=False,
help='location to a dedicated json file containing postcard creator accounts')
parser.add_argument('--picture', default=False,
help='postcard picture. path to an URL or image on disk')
parser.add_argument('--message', default='',
help='postcard message')
parser.add_argument('--key', nargs=1, metavar="PASSWORD", default=False,
help='a key to decrypt credentials stored in config files')
parser.add_argument('--username', default=False,
help='username credential. otherwise set in config or accounts file')
parser.add_argument('--password', default=False,
help='password credential. otherwise set in config or accounts file')
parser.add_argument('--encrypt', action="store", nargs=2, metavar=("KEY", "CREDENTIAL"), default=False,
help='encrypt credentials to store in config files')
parser.add_argument('--decrypt', action="store", nargs=2, metavar=("KEY", "ENCRYPTED_TEXT"), default=False,
help='decrypt credentials')
parser.add_argument('--mock', action='store_true',
help='do not submit postcard. useful for testing')
parser.add_argument('--trace', action='store_true',
help='enable tracing. useful for testing')
parser.add_argument('--debug', action='store_true',
help='enable debug logs. useful for testing')
parser.epilog = textwrap.dedent('''\
sourcecode: https://github.com/abertschi/postcards
''')
self.enrich_parser(parser)
return parser.parse_args()
if __name__ == '__main__':
p = Postcards()
p.main(sys.argv)