5
""" signature of downloaded plugins is verified by signature files """
7
__copyright__ = 'this file is in the public domain'
9
# wrapper for the GNU Privacy Guard
10
# (c) Wijnand 'tehmaze' Modderman - http://tehmaze.com
14
sys.path.insert(0, os.getcwd())
16
from gozerbot.config import config
17
from gozerbot.datadir import datadir
18
from gozerbot.utils.popen import gozerpopen
19
from gozerbot.utils.log import rlog, enable_logging
25
class PgpNoPubkeyException(Exception):
28
class PgpNoFingerprintException(Exception):
31
class NoGPGException(Exception):
35
''' Wrapper for the GNU Privacy Guard. '''
37
re_verify = re.compile('^\[GNUPG:\] VALIDSIG ([0-9A-F]+)', re.I | re.M)
38
re_import = re.compile('^\[GNUPG:\] IMPORT_OK \d ([0-9A-F]+)', re.I | re.M)
39
re_pubkey = re.compile('^\[GNUPG:\] NO_PUBKEY ([0-9A-F]+)', re.I | re.M)
42
self.homedir = os.path.join(datadir, 'pgp')
43
if not os.path.exists(self.homedir):
44
rlog(5, 'pgp', 'creating directory %s' % self.homedir)
45
os.mkdir(self.homedir)
46
if hasattr(config, 'pgpkeyserver'):
47
self.keyserver = config.pgpkeyserver
49
self.keyserver = 'pgp.mit.edu'
50
# make sure the pgp dir is safe
51
os.chmod(self.homedir, 0700)
52
rlog(5, 'pgp', 'will be using public key server %s' % self.keyserver)
54
def exists(self, keyid):
55
(data, returncode) = self._gpg('--fingerprint', keyid)
56
return returncode == 0
58
def imports(self, data):
60
(data, returncode) = self._gpg('--import', tmp)
62
test_import = self.re_import.search(data)
64
fingerprint = test_import.group(1)
68
def imports_keyserver(self, fingerprint):
69
(data, returncode) = self._gpg('--keyserver ', self.keyserver, \
70
'--recv-keys', fingerprint)
72
raise NoGPGException()
73
return returncode == 0
75
def remove(self, data):
78
(_, returncode) = self._gpg('--yes', '--delete-keys', data)
80
raise NoGPGException()
81
return returncode == 0
83
def verify(self, data):
85
(data, returncode) = self._gpg('--verify', tmp)
88
raise NoGPGException()
89
if returncode not in (None, 0, 512):
91
test_pubkey = self.re_pubkey.search(data)
93
raise PgpNoPubkeyException(test_pubkey.group(1))
94
test_verify = self.re_verify.search(data)
96
return test_verify.group(1)
98
def verify_signature(self, data, signature):
99
tmp1 = self._tmp(data)
100
tmp2 = self._tmp(signature)
101
(data, returncode) = self._gpg('--verify', tmp2, tmp1)
104
if returncode == 256:
105
raise NoGPGException()
106
if returncode not in (None, 0, 512):
108
test_pubkey = self.re_pubkey.search(data)
110
raise PgpNoPubkeyException(test_pubkey.group(1))
111
test_verify = self.re_verify.search(data)
113
return test_verify.group(1)
116
def _gpg(self, *extra):
117
cmnd = ['gpg', '--homedir', self.homedir, '--batch', '--status-fd', '1']
119
rlog(0, 'pgp', 'executing: %s' % ' '.join(cmnd))
121
proces = gozerpopen(cmnd, [])
124
except Exception, ex:
125
rlog(0, 'pgp', 'error running "%s": %s' % (cmnd, str(ex)))
127
data = proces.fromchild.read()
128
returncode = proces.close()
129
rlog(0, 'pgp', 'return code: %s, data: %s' % (returncode, data))
130
return (data, returncode)
132
def _tmp(self, data):
133
fdid, tmp = tempfile.mkstemp()
134
fd = os.fdopen(fdid, 'w')
139
# these are the gozerbot.org plugin repositories
140
gozerbot_pgp_keys = {
141
# gozerbot.org/plugs install key
142
'5290D844CF600B6CA6AFF8952D6D437CC5C9C2B2': '''
143
-----BEGIN PGP PUBLIC KEY BLOCK-----
145
mQGiBEZM0ZURBADomT07whLs4n/ml84aIl8Ch6ShngfaaOS12ZrBQVQ/VSh7zPOx
146
IzfhSmwDJAWLZOnnM5zAqWPuNSJa3hQzY/M+X7vv3/p7kkB54/5U6LW+8ODENnMe
147
yPJhbI7phlfeE+STK9hytC3W5+OSrQknXkwYm1bGOur0iiU4Nr16ePE19wCg4KIc
148
ecqxm1U2CRtVC6G3qrZpscMD/0loPcv6Yw7Sx+3UwgAFaOJNE73P+h87wz29WkKs
149
h8Sx/l+Zf2NW/cMUwR0OOQQpzXKtZ8mF/CuPY4YITThKEGh680dUecuGBk5k3LeE
150
ZuLCFagEwZDWqXq/rR7+v8KCaxHpsaoU9P9p3Z7mruvaTYFp+yNfIYAklYoVI2iO
151
pB9PA/94xF3Vsdm3rqT6MUCVbBLKRRNHh7RDiFhQb0GfoWQiae0ZdJO2zWKCprKc
152
cboswKVs0SEUAD30izAaMlfR/p/LrFDHYwr5bBm38xhRMgFxrqdV+5o1+bdYNGA6
153
dJc6XgDWhDHpErCDibGAugNfVCDPhTE1eKbQQXST1KHnhyOcWbQeQmFydCBUaGF0
154
ZSA8YmFydEBnb3plcmJvdC5vcmc+iGAEExECACAFAkZM0ZUCGwMGCwkIBwMCBBUC
155
CAMEFgIDAQIeAQIXgAAKCRAtbUN8xcnCslzoAKCq7QqTlT10I6WcDZddY9GqcKxp
156
VQCaAgX1r9e39X79HR3NxTU6EHgm1hm5Ag0ERkzRnxAIALEuEASs9rpQNcpn6+UJ
157
M43J6vu9qYPl6T4ljsTdsi11Lr7r2ltFmvu/RqA9R4M4QwS/JZjm71R3Ci0eJSnR
158
AlkhejYHn5TKJ5rzRO6EUL0Jd/Rwgk/9qivIyfKAW3A1To2JgRUP9WI19MKYu6e9
159
K5h3KNi/1FfpeOFptB9mHyXBO2rHR9gaN6Wu9uz+eOGlZCyhNRx1jlmVcKAudT2g
160
qg/8NsuiEmv9Wf/CMgYawyenqx8S7cYC5EvHEscIOI/8zma0/1JqD8vllbSh64Ih
161
fsaW3twx/2gAIgtxRSIW7RQuYhs+zBT2C+Sg91rEgGcXjaw+OW2OZJ4kq1Qba82V
162
UOcAAwUH/21DRtgKgAX6xk97l+6ItP19HrcmydxNh299BboSKJDh5RcO1H4xOYyx
163
8NCDLhluGNzqeKPbBeW60F3qpkoQMGD7XsVpfKFu4Umn2qtrZAlg6qucgF0476IO
164
rhvKskAbNMBlocKzxiT+Ruomkt0Bwa3S1owXlMFwImA8coiWCQ8QnbpvjsYW9OH4
165
AgznK5uXKWgMxlZ9SffU0IHXipl3TENXiIdSwUUc2gV5emV/ROFhASNl7YYxZhY6
166
ng8YRku3MXM2jN1S/gNX1hvcq20oFjucKx7YkkV1k23FKF6XK776bmD0hYiSH1e0
167
uSAlBPAgC5HPqMpFhfyeFnDW0N90DR6ISQQYEQIACQUCRkzRnwIbDAAKCRAtbUN8
168
xcnCsoVMAJ472BdMgh+eUJp0+rGCeQhLELJVlQCcCV1zrNw9yoxIGJYnMZZSn4V5
171
-----END PGP PUBLIC KEY BLOCK-----
173
# tehmaze.com/plugs install key
174
'D561C5D7CD86CB3CCA1AB5512A22EC17F9EBC3D8': '''
175
-----BEGIN PGP PUBLIC KEY BLOCK-----
177
mQGiBEZMjOARBACYqwLsv7AMOEHL4wspu0DVXwzVm3U3AU2LivHaJjj66zTd16K4
178
1QQ8CjdWVjMLOSH9tvj0Z7wbdyeQEjAxykgIgIUvQ0zuHgzqVsbpW//W/Noc1Z3K
179
MIEPBQIDdWM7Ln9+1jZAWIKU6oPU6F9Qt2/8o2NDc0w8O73NKgn/NGWtqwCglitS
180
SvUvpP3HZZ8aYwqjk51j0V8D/ilgVNkb/7FumD2yF1R48bJmbRaFu58Hu2IplRr0
181
cGHZ1ijCR8fWeMPGY3CakEOjxQa9IkNtoce/PGTgbIl+TLwXSdiiyu5xkEIt2HHm
182
F3lzLw2o6T59g2w22KpLeXMO02+LcNdsV/BOrhLiO7E/cDB7wBfd/BG3zNC9C2ln
183
cmL4BACL5SEaOE4BCRrOfWdDDUF9iiXGxpCErdxlwId9eFM5OMe48ZmB7oGY52dY
184
rzGK54bqkVxRf5MOcUy3IjulZcy/LcOmm+agNnbQPVTuWNfty8+pSs6cPnF2bBUN
185
xf+UEZnQRlUHokHBy20DMjV3v8jXMBtZxlB24EmMDE21nAofZrQxdGVobWF6ZS5j
186
b20gKGdvemVycGx1Z3MpIDxnb3plcnBsdWdzQHRlaG1hemUuY29tPohgBBMRAgAg
187
BQJGTIzgAhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQKiLsF/nrw9gngQCf
188
esACa4M8ZjvMul8xad4oIUHkMTYAn0+bZBN8ip10/5qIR/CdDvwSL56b
190
-----END PGP PUBLIC KEY BLOCK-----
193
'B2FF7E64254524AC21EF9B76FF52B757C5181C13': '''
194
-----BEGIN PGP PUBLIC KEY BLOCK-----
195
Version: GnuPG v1.4.6 (GNU/Linux)
197
mQGiBEjE3rURBACHzP3dPU2E8t62zZ+nzksHsmaV4ooJPoysEWWpG9SdC2MGuwQ7
198
cnhCWmhxee6VbZ+KbC+pIEpEXeFPK3yaOkO09A1/CKuFTwndDR6Xzy4c/7WauYxq
199
hmn5Q/eODoKYseRoGY65u/rpK1/8OlNxjQltr1Ysgw+GHfjkJK2pWNz3wwCgnVZK
200
p6TYgb9EHLSjqmXekaOMV5kD/iH10PJXj3V+ToZKrsumP3dxha2TICtWMAo6LHa0
201
Qwwkjag93Ji+5yyVM/9UrYzJlqLUapTDit4gmeRm+3Abz6zcOcemowIbNhl/AK8F
202
04JaLTr3uOkkBaVRaIYWWPU8pmq9qT2vcPXxYVE87IBURp9XyvEZrhI/DPCtvXsJ
203
ZB+3A/0UVOo5anIiejqxk99VB61wYD2jN5SUD/VctrhIJYs0PnNBbD3rKcFwURLy
204
WB2cBRXPHoFBOm8h1nWxG/vCMvJoLSI9oU7/AV7j0v6uLSrO0EP8AreQONJi0J29
205
mIjIvdCn/f7pmYXJIL+xo+B1H2yd8uCUT66R+byqTlaISOZGe7Q6QmFzIHZhbiBP
206
b3N0dmVlbiAoR296ZXJib3QgUGx1Z2lucykgPHYub29zdHZlZW5AZ21haWwuY29t
207
PohgBBMRAgAgBQJIxN61AhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQ/1K3
208
V8UYHBOjywCfUZUZL034A5FL5Dv6jW+ul5FVee8An29ig3yylumzZclKGqnnRn0S
209
eb72uQENBEjE3rcQBAC22r4DBZNX6F9LDwtEXLnRpDwP1lf9e5gvD9NMD++zvEfq
210
Ezu/1WCg2hgPSJEDlX0sCnCeqyIonl5/3UjPfug9ZtZDOPjZAl2BDIAptwhZoNSd
211
ypuC4xOyLog7CXpTO0xHQAG7RhBncU9ieenZQQzTV81nFuWe5Icy1espbAi8xwAD
212
BQP6AhoaTKoNv6/JbHrDAigC0mcS7KabeumkRnOCHcZVdPYumto3E63SHZtAXFxI
213
ldkoCOhYfuj/3nosydJcSX0EF2rZMr9p0Mc/N+I341DVagi2OlLRYsqqrBbPm37j
214
BQNEDl/OUHI17ckd45OqiZ2RYiZlbXBrInFyFNhxElTBeQeISQQYEQIACQUCSMTe
215
twIbDAAKCRD/UrdXxRgcE7s4AJ9UzkvCP/tiwzoqe5nQSBqMZAwfUACfS+KCdWq0
218
-----END PGP PUBLIC KEY BLOCK-----
221
'5121C252FF91E0BD':"""
222
-----BEGIN PGP PUBLIC KEY BLOCK-----
225
mQGiBERp0bERBAD3MPp/WCvXGP2wMp3TvifsnuJi8gRm4XYodXTVCEViZ7nCHzrN7Szg6y8q
226
K1IWP5h6FUXZWV33irZ99FYObDYbRAfLc+MY521JN61tJvcwRP7MMu5N5WNBIwp4xW5qP+z4
227
vpO9PodoTBScw6QDBkYuE9j9USB2BMaudlfX1jAk2wCgnwH3Y8Kwe5MZZysoL3KU5/8NIisD
228
/1jgMlGefoCSfplc37tKMH0nVOo94HW4Y4XA0NXZZkCVuHZKIscrS0hwW/ss0WjMzGu7FtmU
229
X/F2tllKDgKcPEHFXsLhT6dgLxIuzuGYZgr9l42cCcfpgWQCAIcvGiuO7YmTt5cnaJLjQswM
230
BcuU78tTa61tWxaAdH+eNNHV/+qgA/0Yj6mOnwpi1Md22Q9L18lS+n6oSJTzv57VyJfjZPGV
231
dxGligaBL4uq6zdqb2X8x8tTSUHGCQ8Gl6v+FL1sWn+K3aoPtVcpxU1RYmJ7Zf83Sgdx8qaJ
232
kdcfMBosbgEll+znl+vTAswcMGoVr/CkGyfJAjtVplId648+hjtpdoqvK7QiU3RlbiBTcGFu
233
cyA8c3RlbkBibGlua2VubGlnaHRzLm5sPohGBBARAgAGBQJEadkEAAoJECbTIkEcy1BHmBwA
234
n0NERJ74tkxsP+TF1nZx8iZ+fQ/4AKCM2hHWi71sBC/eU4hezK0sErn6lYhGBBARAgAGBQJE
235
aj2gAAoJEJsNdpdhxXCtNSoAoIIljClzClz6pFK1GGpYIaJTbSnYAJ9Q1wbn+K6bYcULYpPp
236
okyqMom0OYhMBBMRAgAMBQJEa3OXBYMPB/oaAAoJEOyRFOLTMLr8JIoAnRF1ZsEzTROjG9FP
237
0kcnTYxRTp+rAJ9SIZTDED/C0JTX2FeVf/bK9cW7BYhmBBMRAgAmBQJEadGxAhsDBQkPCZwA
238
BgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQUSHCUv+R4L120ACeLhN7vpj4Tc0JoVhEhC2J
239
fXydwGUAni0P3WEcB2+I4XEAYvp7/RF6aJSTuQINBERp0vwQCADFtCUtwwwhCLvyp/4a/hGh
240
SOpnug41CdG6eULrCi/+mm88r1ANoz9MXRFu5wqxKSIAInsy+WmDc9b6+PBmAiiUsR8qCgot
241
AWkzWo8a2BAXFSH9QhZTlhdY6chVzUZ9v4r1tJ+aBcd6+baLefTUGqsqZYwnoEXPK+sGsJNP
242
m7dK2/9DmQrpE/+vzS4hhWLRfYBbYRyMpZrbksEQ32p4wH2Vv5aWYudPmXYSlH77wYl4++qg
243
LEXIs5NoMaKsQJG7rre7vjAtltfQIw9guRc8UgA3gfaoI4IAUTk6g6J6jKvgUFsGvVA2CC5q
244
IYkUZz/ffe12uExg7PxbKZ7GxtkjKInDAAMGCADBA1y1VUAw1wmcMFFRw/Tp5xAd1dy9oH3h
245
sdZHBOWH0OSTcRLe014h4pn82HPu4rJQKyvpP/0DjTs8QfmkPn8E5Pgr6MuvcyTvwaCXfwtU
246
gI5s95x4nMTtSgrOcOuDulyUuzSLjk+aDqkk78U/N1ck4ifr6PZhrB3CvOPt4YqAWVr+3Bcd
247
grNFNqH332fQlLoNNekva0W4ogTv2vYVUNa1LNlOjIldmwlITDPqdLJxQ3kRub/e3KJ1Li1g
248
hxYBTzLQYqoJ+WvF6J2IhmH5xo5lu/Gh7aIh50Keyt3ZjE7gK5oQUBozOprOIWx7whVjAu2/
249
ZKWRt0IWuWEOLrioyflKiE8EGBECAA8FAkRp0vwCGwwFCQ8JnAAACgkQUSHCUv+R4L2bYwCf
250
bqSE/brg8NBAjUB2Bly/GE15KOUAniH5CnF11sfqL3aUUVkW58X+OQLM
252
-----END PGP PUBLIC KEY BLOCK-----
256
# initiate a Pgp instance and import the distkeys, if needed
258
for keyid, keydata in gozerbot_pgp_keys.iteritems():
260
if not pgp.exists(keyid):
262
except NoGPGException:
263
rlog(10, 'pgp', 'no pgp installed')
266
from gozerbot.utils.url import geturl, geturl2
267
from gozerbot.utils.generic import waitforuser, touch
268
from gozerbot.utils.locking import lockdec
269
from gozerbot.utils.log import rlog
270
from gozerbot.utils.exception import exceptionmsg, handle_exception
271
from gozerbot.utils.generic import uniqlist
272
from gozerbot.commands import cmnds
273
from gozerbot.examples import examples
274
from gozerbot.plugins import plugins
275
from gozerbot.plughelp import plughelp
276
from gozerbot.aliases import aliasdel, aliasset
277
from gozerbot.utils.dol import Dol
278
from gozerbot.datadir import datadir
279
from gozerbot.utils.fileutils import tarextract
282
import re, urllib2, urlparse, os, thread, shutil
286
""" contains plugin version data """
290
def add(self, plugin, version):
291
""" add plugin version """
292
self.data[plugin] = version
296
""" list plugin versions """
300
installlock = thread.allocate_lock()
301
locked = lockdec(installlock)
303
plughelp.add('install', 'install plugin from remote site')
305
installsites = ['http://gozerbot.org/gozerplugs',
306
'http://tehmaze.com/plugs/gozerbot-0.9', 'http://plugs.trbs.net/0.9', \
307
'http://gozerplugs.blinkenlights.nl', 'http://gozerbot.org/catchall']
309
class InstallerException(Exception):
312
class Installer(object):
314
We're taking care of installing and verifying plugins.
317
BACKUP_PATTERN = '%s~'
318
SUPPORTED_EXT = ['tar', 'py'] # supported extensions, in this order
320
def install(self, plugname, site=''):
322
Install `plugname` from `site`. If no `site` is specified, we scan the list of
323
pre-configured `installsites`.
325
# check if logs dir exists if not create it
326
if not os.path.isdir('myplugs'):
328
touch('myplugs/__init__.py')
330
if plugname.endswith('.py'):
331
plugname = plugname[:3]
332
plugname = plugname.split(os.sep)[-1]
335
for site in installsites:
337
plugdata, plugsig, plugtype = self.fetchplug(site, plugname)
338
break # no exception? found it!
339
except InstallerException, e:
340
errors.append(str(e))
343
plugdata, plugsig, plugtype = self.fetchplug(site, plugname)
344
except InstallerException, e:
345
errors.append(str(e))
348
raise InstallerException(', '.join(str(e) for e in errors))
350
raise InstallerException('nothing to do')
352
if self.validate(plugdata, plugsig):
354
self.save_py(plugname, plugdata)
355
elif plugtype == 'tar':
356
self.save_tar(plugname, plugdata)
357
if hasattr(plugdata, 'info') and plugdata.info.has_key('last-modified'):
358
cfg.add(plugname, ['%s/%s.%s' % (site, plugname, plugtype), plugdata.info['last-modified']])
359
return '%s/%s.%s' % (site, plugname, plugtype)
361
raise InstallerException('%s.%s signature validation failed' % (plugname, plugtype))
363
def fetchplug(self, site, plugname):
365
Fetch the plugin with `plugname` from `site`, we try any extension available in
366
`Installer.SUPPORTED_EXT`. We also fetch the signature file.
369
if not site.startswith('http://'):
370
site = 'http://%s' % site
371
base = '%s/%s' % (site, plugname)
372
for ext in self.SUPPORTED_EXT:
374
plugdata, plugsig = self.fetchplugdata(base, ext)
375
return plugdata, plugsig, ext
376
except InstallerException, e:
377
errors.append(str(e))
378
raise InstallerException(errors)
380
def fetchplugdata(self, base, ext='py'):
382
Here the actual HTTP request is being made.
384
url = '%s.%s' % (base, ext)
385
plug = base.split('/')[-1]
387
plugdata = geturl2(url)
388
except urllib2.HTTPError:
389
raise InstallerException("no %s" % os.path.basename(url))
392
plugsig = geturl2('%s.asc' % url)
393
except urllib2.HTTPError:
394
raise InstallerException('%s plugin has no signature' % plug)
396
return plugdata, plugsig
398
def backup(self, plugname):
400
Backup a plugin with name `plugname` and return if we backed up a file or
401
directory, and what its original name was.
404
oldname = os.path.join('myplugs', plugname)
405
newname = self.BACKUP_PATTERN % oldname
406
if os.path.isfile('%s.py' % oldname):
407
newname = self.BACKUP_PATTERN % '%s.py' % oldname
408
oldname = '%s.py' % oldname
410
if os.path.isfile(newname):
412
os.rename(oldname, newname)
413
elif os.path.isdir(oldname):
415
if os.path.isdir(newname):
416
shutil.rmtree(newname)
417
os.rename(oldname, newname)
418
return oldtype, newname
420
def restore(self, plugname, oldtype, oldname):
422
Restore a plugin with `plugname` from `oldname` with type `oldtype`.
424
newname = os.path.join('myplugs', plugname)
426
# remove partial 'new' data
427
if os.path.isdir(newname):
428
shutils.rmtree(newname)
429
os.rename(oldname, newname)
430
elif oldtype == 'file':
431
newname = '%s.py' % newname
432
if os.path.isfile(newname):
434
os.rename(oldname, newname)
437
def save_tar(self, plugname, plugdata):
438
oldtype, oldname = self.backup(plugname)
440
if not tarextract(plugname, str(plugdata), 'myplugs'): # because it is an istr
441
raise InstallerException('%s.tar had no (valid) files to extract' % (plugname))
442
except InstallerException:
445
self.restore(plugname, oldtype, oldname)
446
raise InstallerException('restored backup, error while extracting %s: %s' % (plugname, str(e)))
448
def save_py(self, plugname, plugdata):
449
oldtype, oldname = self.backup(plugname)
451
plugfile = open(os.path.join('myplugs', '%s.py' % plugname), 'w')
452
plugfile.write(plugdata)
454
except InstallerException:
457
self.restore(plugname, oldtype, oldname)
458
raise InstallerException('restored backup, error while writing %s: %s' % (plugfile, str(e)))
460
def validate(self, plugdata, plugsig):
461
fingerprint = pgp.verify_signature(plugdata, plugsig)
469
for i in installsites:
471
pluglist = geturl2(i)
472
except Exception, ex:
475
result += re.findall('<a href="(.*?)\.py">', pluglist)
477
result.remove('__init__')
482
print 'available plugins: %s' % ' .. '.join(uniqlist(result))
484
print "couldn't extract plugin list from: %s " % ' .. '.join(errors)
486
def installplug(plugs):
487
""" install a remote plugin """
495
installer = Installer()
499
plug = plug.split(os.sep)[-1]
500
for site in installsites:
502
url = installer.install(plug, site)
504
print 'installed %s' % url
505
installed.append(plug)
507
break # stop iterating sites
508
except NoGPGException, ex:
509
print "couldn't run gpg .. please install gnupg if you \
510
want to install remote plugins"
512
except Exception, ex:
513
errors[site] = str(ex)
517
for i, j in errors.iteritems():
519
for error, sites in errordict.iteritems():
520
errorlist.append("%s => %s" % (' , '.join(sites), error))
521
print "errors: %s" % ' .. '.join(errors)
523
from optparse import OptionParser
524
parser = OptionParser(usage='usage: %prog [options]', version='%prog ' + gozerbot.__version__)
525
parser.add_option('-d', '--directory', type='string', default=".", dest='botdir',
527
help="bot directory to install plugins in")
528
parser.add_option('-l', action='store_true', dest='showlist',
529
help="show list of available plugins")
530
opts, args = parser.parse_args()
534
showlist = opts.showlist
535
except AttributeError:
544
except AttributeError:
548
print "usage: gozerbot-install -l | [-d <botdir>] plug1 plug2"
552
installplug(opts.args)