· 8 years ago · Jul 27, 2018, 09:42 PM
1# -*- coding: utf-8 -*-
2
3import calendar
4import requests
5import sqlite3
6import time
7import uuid
8import json
9import pendulum
10import dateutil
11
12from datetime import datetime, timedelta
13from random import choice
14
15from disco.bot import Plugin
16from disco.bot.command import CommandLevels
17from disco.util import snowflake
18from disco.types.message import MessageEmbed
19
20
21class UtilitiesPlugin(Plugin):
22 tags_to_remove = {}
23
24 @Plugin.command('joined', '<server:str> [user:str]')
25 def joined_command(self, event, server, user=None):
26 """= joined =
27 Displays date and time user joined discord or guild in EST
28 You can convert the result to your timezone with the !timezone command
29 usage :: !joined <guild | discord> [user]
30 aliases :: None
31 category :: Utilities
32 == Examples
33 !joined discord `No user supplied, defaults to you'
34 !joined discord linux
35 !joined guild `No user supplied, defaults to you'
36 !joined guild linux
37 """
38 if user is None:
39 user = event.guild.members[event.msg.author.id]
40 else:
41 # user = ' '.join(user)
42 for member in event.guild.members.values():
43 if user == member.name:
44 user = member
45 break
46 else:
47 event.msg.reply('No such user')
48 return
49
50 if server == 'guild':
51 joined = user.joined_at
52 joined = pendulum.datetime(joined.year, joined.month, joined.day, joined.hour, joined.minute, joined.second, tz='America/New_York')
53 elif server == 'discord':
54 joined = pendulum.from_timestamp(snowflake.to_unix(user.user.id), tz='America/New_York')
55 else:
56 return
57
58 event.msg.reply(joined.format('[{} joined {}] dddd MMMM Do YYYY [at] h:mmA zz'.format(user.name, server)))
59
60
61 @Plugin.command('strawpoll', aliases=['poll', 'sp'], parser=True)
62 @Plugin.add_argument('title', nargs='+')
63 @Plugin.add_argument('--options', '-o', nargs='+', required=True)
64 def strawpoll_command(self, event, args):
65 new_poll_url = 'https://www.strawpoll.me/api/v2/polls'
66 get_poll_url = 'https://www.strawpoll.me/api/v2/polls/{}'
67
68 title = ' '.join(args.title)
69 options = [option.strip() for option in ' '.join(args.options).split('\\')]
70
71 payload = {
72 'title': title,
73 'options': options,
74 'captcha': True
75 }
76
77 poll = requests.post(new_poll_url, json=payload)
78 new_poll_ID = poll.json()['id']
79 poll_url = 'https://www.strawpoll.me/{}'.format(new_poll_ID)
80
81 poll_data = requests.get(get_poll_url.format(new_poll_ID)).json()
82 vote_data = zip(poll_data['options'], poll_data['votes'])
83 display_votes = '\n'.join(['{}: {}'.format(option, vote) for option, vote in vote_data])
84
85 event.msg.reply('{}\n\n```{}\n\n{}```'.format(poll_url, title, display_votes))
86
87
88 @Plugin.command('ping')
89 def ping_command(self, event):
90 """= ping =
91 Latency of bot and discord (not very accurate)
92 usage :: !ping
93 aliases :: None
94 category :: Utilities
95 """
96 user_ping = event.msg.timestamp
97 bot = event.msg.reply('Pong!')
98 bot_ping = bot.timestamp
99
100 user_bot_latency = (bot_ping - user_ping).total_seconds() * 1000.0
101
102 bot.edit('Latency of you to bot: ~{:.2f}ms'.format(user_bot_latency))
103
104
105 @Plugin.command('choose', aliases=['pick'], parser=True)
106 @Plugin.add_argument('options', nargs='+')
107 def choose_command(self, event, args):
108 """= choose =
109 Chooses an option from a list of options given by the user
110 usage :: !choose <options ...>
111 aliases :: pick
112 category :: Utilities
113 == Note
114 Must have at least 2 options
115 Options are separated with backslashes
116 == Examples
117 !choose option1 \ option2 \ option3
118 !choose linux is the best admin \ ara is the best admin \ riku is the best admin \ noob is the best admin
119 """
120 options = [option.strip() for option in ' '.join(args.options).split('\\')]
121 if len(options) <= 1 or not all(options): # if not at least two options and no options are empty strings
122 return
123
124 chosen = choice(options)
125 event.msg.reply('I choose: {}'.format(chosen))
126
127
128 def add_tag(self, event, args, conn, user_level):
129 self.log.info('Adding tag {} with value {}'.format(args.name, args.value))
130 if args.server and user_level < CommandLevels.MOD:
131 r = event.msg.reply('Only privileged users can add server-wide tags')
132 time.sleep(5)
133 r.delete()
134 return
135
136 if not all((args.name, args.value)):
137 self.log.error('Missing arguments')
138 return
139
140 name = ' '.join(args.name)
141 value = ' '.join(args.value)
142
143 c = conn.cursor()
144 c.execute('select count(*) from tags where user_id is NULL and name = ?', (name,))
145 if c.fetchone()[0] > 0:
146 self.log.error('Server-wide tag by the name {} already exists'.format(name))
147 r = event.msg.reply('A server-wide tag already has the name: {}'.format(name))
148 time.sleep(5)
149 r.delete()
150 return
151
152 c.execute('select count(*) from tags where user_id = ? and name = ?', (event.author.id, name))
153 if c.fetchone()[0] > 0:
154 self.log.error('User {} already has tag under the name: {}'.format(event.author, name))
155 r = event.msg.reply('You already have a tag under the name: {}'.format(name))
156 time.sleep(5)
157 r.delete()
158 return
159
160 if args.server:
161 c.execute('select count(*) from tags where name = ?', (name,))
162 tags_count = c.fetchone()[0]
163 if tags_count > 0:
164 dm = event.author.open_dm()
165 dm.send_message('{} user(s) already have a tag with the name: {}\n\
166 You must either delete all of them before adding this tag server-wide, or use a different name\n\
167 You can do `!tag -i {}` to see all tags with this name and their ID\'s for deletion'.format(name).replace('\t', ''))
168 return
169 c.execute("insert into tags (username, name, value)\
170 values (?, ?, ?)", (str(event.author), name, value))
171 else:
172 c.execute("insert into tags (user_id, username, name, value)\
173 values (?, ?, ?, ?)", (event.author.id, str(event.author), name, value))
174
175 conn.commit()
176 r = event.msg.reply('Successfully added tag')
177 time.sleep(5)
178 r.delete()
179
180
181 def delete_tag(self, event, args, conn, user_level):
182 REMOVE = False
183
184 if not args.key:
185 self.log.error('{} tried using -d flag without a key'.format(event.author))
186 event.msg.reply('You must provide the ID of the tag')
187 return
188
189 key = args.key[0]
190 unique_id = key # save unique_id for when key is overwritten with tag ID
191
192 if self.tags_to_remove.get(key):
193 key = self.tags_to_remove[key]
194 REMOVE = True
195
196 user_level = self.bot.get_level(event.author)
197 c = conn.cursor()
198 if not c.execute('select exists(select 1 from tags where ID = ?)', (key,)).fetchone()[0]:
199 self.log.error('{} tried deleting non-existent tag'.format(event.author))
200 event.msg.reply('No tag with the ID `{}` exists'.format(key))
201 return
202
203 c.execute('select user_id from tags where ID = ?', (key,))
204 user_id = c.fetchone()[0]
205 if user_id is None and user_level < CommandLevels.MOD:
206 self.log.error('{} tried deleting a server-wide tag'.format(event.author))
207 event.msg.reply('Only staff can delete server-wide tags')
208 return
209
210 if user_id is not None and user_id != event.author.id and user_level < CommandLevels.MOD:
211 self.log.error("{} tried deleting someone else's tag")
212 event.msg.reply("You can't delete another user's tag")
213 return
214
215 if REMOVE:
216 self.log.info('REMOVE=True, deleting tag {}, requested by user {}'.format(key, event.author))
217 c.execute('delete from tags where ID = ?', (key,))
218 conn.commit()
219 self.log.info('deleted tag {}'.format(key))
220 r = event.msg.reply('Successfully deleted tag')
221 time.sleep(5)
222 r.delete()
223 del self.tags_to_remove[unique_id]
224 self.log.info('Removed tag {} with unique ID {} from self.tags_to_remove'.format(key, unique_id))
225 else: # not marked for deletion yet so ask for confirmation
226 self.log.info('{} marking tag {} for deletion'.format(event.author, key))
227 name, value = c.execute('select name, value from tags where ID = ?', (key,)).fetchone()
228 unique_id = uuid.uuid4().hex
229 self.tags_to_remove[unique_id] = key
230 self.log.info('Created unique ID {} for tag {}'.format(unique_id, key))
231 dm = event.author.open_dm()
232 from inspect import cleandoc
233 dm.send_message(cleandoc(
234 u'''Please confirm you want to delete this tag:
235 ID: {}
236 Name: {}
237 Value: {}{}
238
239 To confirm and delete **permanently**, please run the following command in the server you executed the first delete command:
240 `{}`
241
242 Your confirmation code will **expire** in 1 minute.
243 '''.format(key, name, value if len(value) <= 30 else value[:30], '' if len(value) <= 30 else '... (Truncated to fit in message)',
244 '`!tag -dk {}`'.format(unique_id))
245 ))
246 time.sleep(60)
247 try:
248 del self.tags_to_remove[unique_id]
249 self.log.info('Tag {} with unique ID {} expired from self.tags_to_remove'.format(key, unique_id))
250 except KeyError:
251 self.log.info('Tag {} with unique ID {} was removed before it could expire'.format(key, unique_id))
252
253
254 def update_tag(self, event, args, conn, user_level):
255 if not (args.name or args.value):
256 self.log.error('{} tried using -u flag without a name or value'.format(event.author))
257 event.msg.reply('You must provide the ID of the tag')
258 return
259
260 name = ' '.join(args.name) if args.name else None
261 value = ' '.join(args.value) if args.value else None
262
263 if not args.key:
264 self.log.error('{} tried using -u flag without a key'.format(event.author))
265 return
266
267 key = args.key[0]
268
269 user_level = self.bot.get_level(event.author)
270 c = conn.cursor()
271 if not c.execute('select exists(select 1 from tags where ID = ?)', (key,)).fetchone()[0]:
272 self.log.error('{} tried updating non-existent tag'.format(event.author))
273 event.msg.reply('No tag with the ID `{}` exists'.format(key))
274 return
275
276 c.execute('select user_id from tags where ID = ?', (key,))
277 user_id = c.fetchone()[0]
278 if user_id is None and user_level < CommandLevels.MOD:
279 self.log.error('{} tried updating a server-wide tag'.format(event.author))
280 event.msg.reply('Only staff can update server-wide tags')
281 return
282
283 if user_id is not None and user_id != event.author.id:
284 self.log.error("{} tried updating another user's tag".format(event.author))
285 event.msg.reply("You can't update another user's tag")
286 return
287
288 if name and value:
289 query = ('update tags set username = ?, name = ?, value = ? where ID = ?',
290 (str(event.author), name, value, key))
291 elif name:
292 query = ('update tags set username = ?, name = ? where ID = ?',
293 (str(event.author), name, key))
294 elif value:
295 query = ('update tags set username = ?, value = ? where ID = ?',
296 (str(event.author), value, key))
297
298 c.execute(*query)
299 conn.commit()
300 self.log.info('{} updated tag with ID: {}'.format(event.author, key))
301 r = event.msg.reply('Successfully updated tag ID {}'.format(key))
302 time.sleep(5)
303 r.delete()
304
305
306 def list_tags(self, event, args, conn, user_level):
307 if args.name or args.value:
308 self.log.error('{} tried using -l flag with too many args:\n{}'.format(event.author, args))
309 return
310
311 dm = event.author.open_dm()
312 c = conn.cursor()
313 c.execute('select count(*) from tags')
314 if c.fetchone()[0] == 0:
315 dm.send_message('No tags have been added yet')
316 return
317
318 c.execute('select max(length(name)) from tags where user_id is NULL or user_id = ?', (event.author.id,))
319 width = c.fetchone()[0]
320
321 msg = u'```asciidoc\n== Server-wide Tags ==\n'
322 c.execute('select name, value from tags where user_id is NULL')
323 for name, value in c:
324 msg += u'{:<{width}} :: {}\n'.format(name, value, width=width)
325
326 if not args.server:
327 msg += '\n== Your Tags ==\n'
328 c.execute('select name, value from tags where user_id = ?', (event.author.id,))
329 for name, value in c:
330 msg += u'{:<{width}} :: {}\n'.format(name, value, width=width)
331
332 msg += u'```'
333
334 dm.send_message(msg)
335
336
337 def list_all_tags(self, event, args, conn, user_level):
338 if user_level < CommandLevels.MOD:
339 self.log.warning('Unprivileged user "{}" tried executing --list-all flag'.format(event.author))
340 r = event.msg.reply('Only privileged users can use this command')
341 time.sleep(5)
342 r.delete()
343 return
344
345 if args.name or args.value:
346 self.log.error('{} tried using -L flag with too many args:\n{}'.format(event.author, args))
347 return
348
349 dm = event.author.open_dm()
350 c = conn.cursor()
351 c.execute('select count(*) from tags')
352 if c.fetchone()[0] == 0:
353 dm.send_message('No tags have been added yet')
354 return
355
356 c.execute('select max(length(name)) from tags')
357 width = c.fetchone()[0]
358
359 msg = u'```asciidoc\n== Server-wide Tags ==\n'
360 c.execute('select name, value from tags where user_id is NULL')
361 for name, value in c:
362 msg += u'{:<{width}} :: {}\n'.format(name, value, width=width)
363
364 c.execute('select distinct user_id from tags where user_id is not NULL')
365 users = {user_id[0] for user_id in c}
366 for user in users:
367 msg += '\n== {} ==\n'.format(event.guild.members.get(user).name)
368 c.execute('select name, value from tags where user_id = ?', (user,))
369 for name, value in c:
370 msg += u'{:<{width}} :: {}\n'.format(name, value, width=width)
371
372 msg += '```'
373
374 if len(msg) < 2000:
375 dm.send_message(msg)
376 else:
377 url = 'https://api.paste.ee/v1/pastes'
378 header = {'X-Auth-Token': 'aePM1mOrLrbtdgLkGpINjOjJxr9TAtUz4fKYfnLXW'}
379 payload = {'sections': [{'contents': msg.strip('```').strip('asciidoc')}]}
380 r = requests.post(url, headers=header, json=payload)
381 dm.send_message('Pasting to `https://paste.ee` because output is too long: {}'.format(r.json()['link']))
382
383
384
385
386 def info_tag(self, event, args, conn, user_level):
387 if args.value:
388 self.log.error('{} included value in info lookup'.format(event.author))
389 return
390
391 dm = event.author.open_dm()
392 search = ' '.join(args.name)
393 self.log.info('Fetching tag info on name: {}'.format(search))
394 c = conn.cursor()
395 if args.server:
396 query = ('select ID, username, name from tags where user_id is NULL and name like ?', ('%'+search+'%',))
397 elif user_level >= CommandLevels.MOD:
398 query = ('select ID, username, name from tags where name like ?', ('%'+search+'%',))
399 else:
400 query = ('select ID, username, name from tags where user_id = ? and name like ?', (event.author.id, '%'+search+'%'))
401
402 c.execute('select max(length(name)) from tags where name like ?', ('%'+search+'%',))
403 width = c.fetchone()[0]
404 if width is None:
405 self.log.error("{}'s search '{}' had no results".format(event.author, search))
406 return
407 msg = u'```asciidoc\n== Tags similar to {}\n'.format(search)
408 c.execute(*query)
409 msg += u'{:<10}{:<{width}}{}\n'.format('*ID*', '*Name*', '*Created by*', width=width+5)
410 for ID, username, name in c:
411 msg += u' {:<10}{:<{width}}{}\n'.format(ID, name, username, width=width+5)
412
413 msg += u'```'
414
415 if len(msg) < 2000:
416 dm.send_message(msg)
417 else:
418 url = 'https://api.paste.ee/v1/pastes'
419 header = {'X-Auth-Token': 'aePM1mOrLrbtdgLkGpINjOjJxr9TAtUz4fKYfnLXW'}
420 payload = {'sections': [{'contents': msg.strip('```').strip('asciidoc')}]}
421 r = requests.post(url, headers=header, json=payload)
422 dm.send_message('Pasting to `https://paste.ee` because output is too long: {}'.format(r.json()['link']))
423
424
425 def get_tag(self, event, args, conn, user_level):
426 self.log.info('Getting tag {}'.format(args.name))
427 if not args.name or args.value:
428 return
429
430 name = ' '.join(args.name)
431
432 c = conn.cursor()
433 c.execute('select value from tags where user_id is NULL and name = ?', (name,))
434 value = c.fetchone()
435 if value:
436 event.msg.reply(value[0])
437 return
438
439 c.execute('select value from tags where user_id = ? and name = ?', (event.author.id, name))
440 value = c.fetchone()
441 if value:
442 event.msg.reply(value[0])
443 return
444 else:
445 self.log.info('No such tag exists named {}'.format(name))
446 r = event.msg.reply('No such tag exists')
447 time.sleep(5)
448 r.delete()
449
450
451 def count_tags(self, event, args, conn):
452 c = conn.cursor()
453 c.execute('select count(*) from tags')
454 count = c.fetchone()[0]
455 c.execute('select count(*) from tags where user_id is NULL')
456 server_count = c.fetchone()[0]
457 c.execute('select count(*) from tags where user_id is not NULL')
458 user_count = c.fetchone()[0]
459 c.execute('select count(distinct user_id) from tags where user_id is not NULL')
460 users_withtags = c.fetchone()[0]
461 if count == 0:
462 event.msg.reply('There aren\'t any tags yet')
463 else:
464 event.msg.reply('There {} currently {} {}. (Server-wide: {} / User Tags: {})\n{} {} {} tags'.format(
465 'is' if count ==1 else 'are',
466 count,
467 'tag' if count == 1 else 'tags',
468 server_count,
469 user_count,
470 users_withtags,
471 'user' if users_withtags == 1 else 'users',
472 'has' if users_withtags == 1 else 'have'
473 ))
474
475
476
477 @Plugin.command('tag', parser=True)
478 @Plugin.add_argument('-a', '--add', action='store_true')
479 @Plugin.add_argument('-d', '--delete', action='store_true')
480 @Plugin.add_argument('-u', '--update', action='store_true')
481 @Plugin.add_argument('-l', '--list', action='store_true')
482 @Plugin.add_argument('-L', '--list-all', action='store_true')
483 @Plugin.add_argument('-s', '--server', action='store_true')
484 @Plugin.add_argument('-i', '--info', action='store_true')
485 @Plugin.add_argument('-k', '--key', nargs=1)
486 @Plugin.add_argument('name', nargs='*')
487 @Plugin.add_argument('-v', '--value', nargs='*')
488 def tag_command(self, event, args):
489 """= tag =
490 Create your own custom commands
491 usage :: !tag [-a] [-d] [-u] [-l] [-L] [-s] [-i] [-k] [name] [-v] [value]
492 aliases :: None
493 category :: Utilities
494 == Flags
495 -a/--add :: Add new tag
496 -d/--delete :: Delete tag based on tag ID
497 -u/--update :: Update tag with new value based on tag ID
498 -l/--list :: List all personal and server-wide tags
499 -L/--list-all :: List every tag in the database (STAFF ONLY)
500 -s/--server :: Apply action to only server-wide tags
501 -i/--info :: Get info on tags that are similar to <tagname> (user who created it, ID of tag, etc)
502 -k/--key :: Denote following text will be ID of tag
503 -v/--value :: Denote following text to be value of tag
504 == Constraints
505 name :: Must be <= 30 characters
506 value :: Must be <= 1992 characters
507 == Note
508 Staff are allowed to delete any tags that might be inappropriate or offensive in some way
509 == Add Examples
510 !tag -a foo -v bar `Add tag named foo with value bar'
511 !tag -a -s foo -v bar `Add server-wide tag named foo with value bar (STAFF ONLY)'
512 !tag -as foo -v bar `equivalent to example above'
513 == List Examples
514 !tag -l `List all your personal tags and server-wide tags'
515 !tag -ls `List only server-wide tags'
516 !tag -L `Lists every tag in the database (STAFF ONLY)'
517 == Info Examples
518 !tag -i o `Search all personal and server-wide tags with o in its name and return info'
519 !tag -si o `Search all server-wide tags with o in its name and return info'
520 == Update Examples (Be careful with these!)
521 !tag -u foo -v bar -k 42 `Updates name and value of tag with ID=42 to foo and bar respectively'
522 !tag -uk 42 foo -v bar `Equivalent to previous example'
523 !tag -u foo -k 42 `Only changes tag 42 name to foo (leaves value alone)'
524 !tag -u -v bar -k 42 `Only changes tag 42 value to bar (leaves name alone)'
525 == Delete Examples (Asks for confirmation first)
526 !tag -dk 42 `Marks tag 42 for deletion (You will be messaged a unique id to delete this tag)'
527 !tag -dk <unique id> `Deletes tag mapped to <unique id>'
528 """
529 if event.channel.is_dm:
530 event.msg.reply('This command only works in a server :/')
531 return
532
533 self.log.info('!tag command triggered with args: {}'.format(args))
534
535 server_id = event.guild.id
536 user_level = self.bot.get_level(event.author)
537
538 # key should be int in every case except when passing
539 # unique hex code to --delete
540 try:
541 args.key = [int(args.key[0])]
542 except:
543 pass
544
545 if args.name:
546 name_len = sum(map(len, args.name))
547 if name_len > 30:
548 self.log.error('{} tried to create tag with name len: {}'.format(event.author, name_len))
549 return
550 if args.value:
551 value_len = sum(map(len, args.value))
552 if value_len > 1992:
553 self.log.error('{} tried to create tag with value len: {}'.format(event.author, value_len))
554 return
555
556 try:
557 conn = sqlite3.connect('db/{}.db'.format(server_id), detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
558
559 # if sum(map(bool, vars(args).values())) == 0: # <-- test this
560 if not any(v for k,v in vars(args).iteritems()):
561 self.log.info('No args, responding with tag count')
562 self.count_tags(event, args, conn)
563 return
564
565 if sum((args.add, args.delete, args.update, args.list, args.info, args.list_all)) > 1:
566 return
567
568 if any((args.add, args.delete, args.update, args.list, args.info, args.list_all)):
569 event.msg.delete()
570
571 if args.add:
572 self.add_tag(event, args, conn, user_level)
573 elif args.delete:
574 self.delete_tag(event, args, conn, user_level)
575 elif args.update:
576 self.update_tag(event, args, conn, user_level)
577 elif args.list:
578 self.list_tags(event, args, conn, user_level)
579 elif args.list_all:
580 self.list_all_tags(event, args, conn, user_level)
581 elif args.info:
582 self.info_tag(event, args, conn, user_level)
583 else:
584 self.get_tag(event, args, conn, user_level)
585
586 except sqlite3.OperationalError:
587 self.log.info('First time running `!{}` command, creating new table in database'.format(event.name))
588 event.msg.reply('[INFO] - First time running `!{}` command, creating new table in database'.format(event.name))
589 c = conn.cursor()
590 c.execute('create table tags (ID integer primary key, user_id integer, username text, name text, value text)')
591 conn.commit()
592 event.msg.reply('[INFO] - Table created. Please run your command again.')
593
594 finally:
595 conn.close()
596
597
598 @Plugin.schedule(3600, init=False) # sync exchange rates every hour
599 def exchange_rate_sync(self):
600 """Get latest exchange rates from fixer.io and save to json file."""
601 self.log.info('Syncing latest exchange rates on fixer.io')
602 fixer_key = '' # secret lol
603 url = 'http://data.fixer.io/api/latest?access_key={}'.format(fixer_key)
604 r = requests.get(url)
605 if not r.json()['success']:
606 self.log.error('exchange_rate_sync ERROR: {}, code: {}\nDescription: {}'.format(r.json()['error']['type'], r.json()['error']['code'], r.json()['error']['info']))
607 return
608 with open('json/exch_rates.json', 'w') as f:
609 json.dump(r.json(), f)
610 return True
611
612
613 @Plugin.command('exchange', aliases=['ex'], parser=True)
614 @Plugin.add_argument('amount', type=float, nargs='?')
615 @Plugin.add_argument('base', nargs='?')
616 @Plugin.add_argument('target', nargs='?')
617 def exchange_command(self, event, args):
618 """= exchange =
619 Convert currency with current exchange rates (Exchange rates updated hourly)
620 usage :: !exchange AMOUNT FROM TO
621 aliases :: ex
622 category :: Utilities
623 == Arguments
624 AMOUNT :: The amount of money to convert
625 FROM :: The currency to convert *from*
626 TO :: The currency to convert *to*
627 == Constraints
628 AMOUNT :: Must be a positive number
629 == Examples
630 !exchange 1.50 USD JPY `Converts from freedom units to glorious nippon 円'
631 !exchange 10 EUR GBP `Converts Euros to British Pounds'
632 !exchange 3.50 usd aud `Lowercase works too (btw that is dollars to dollary-doos)'
633 == Supported Currencies
634 Run !exchange with no arguments to get a list
635 of all available currencies
636 """
637 try:
638 with open('json/exch_rates.json') as f:
639 rates = json.load(f)
640 except IOError:
641 if self.exchange_rate_sync('json/exch_rates.json'):
642 with open('json/exch_rates.json') as f:
643 rates = json.load(f)
644 else:
645 return
646
647 if not any(arg for arg in vars(args).values()):
648 event.msg.reply('Available currencies: ```{}```'.format(', '.join(sorted(rates['rates'].keys()))))
649 return
650 elif all(arg for arg in vars(args).values()):
651 pass
652 else:
653 self.log.error('{} ran !exchange with only partial arguments'.format(event.author))
654 return
655
656 if args.amount <= 0:
657 self.log.error('{} tried converting currency with non positive number: {}'.format(event.author, args.amount))
658 return
659
660 args.base, args.target = args.base.upper(), args.target.upper()
661
662 try:
663 eur_to_target = rates['rates'][args.target]
664 eur_to_base = rates['rates'][args.base]
665 result = eur_to_target/eur_to_base * args.amount
666 except KeyError:
667 if args.base == 'EUR':
668 result = rates['rates'][args.target] * args.amount
669 elif args.target == 'EUR':
670 result = (1.0/rates['rates'][args.base]) * args.amount
671 else:
672 self.log.error('{} used invalid currency. Args: {}'.format(event.author, args))
673 event.msg.reply('Invalid or unsupported currency')
674 return
675
676 if args.base not in rates['rates']:
677 self.log.error('{} used invalid `from` currency: {}'.format(event.author, args.base))
678 return
679
680 if args.target not in rates['rates']:
681 self.log.error('{} used invalid `to` currency: {}'.format(event.author, args.target))
682 return
683
684 event.msg.reply('{} {} = {:.2f} {}'.format(args.amount, args.base, result, args.target))
685
686
687 @Plugin.command('timezone', aliases=['tz', 'time'], parser=True)
688 @Plugin.add_argument('dt', nargs='*')
689 @Plugin.add_argument('-tz', '--timezone')
690 def timezone_command(self, event, args):
691 """= timezone =
692 Get current time of server or convert server time to a different timezone
693 usage :: !timezone [DATETIME] [-tz TIMEZONE]
694 aliases :: tz
695 category :: Utilities
696 == Datetime Syntax
697 Datetimes are pretty flexible, anything sane should work
698 You can see some examples below
699 == Examples
700 !timezone `Gets current Arabella time'
701 !timezone 8pm -tz Europe/London `Convert 8pm Arabella time to london timezone'
702 !timezone april 20th 1984 -tz UTC `Returns datetime in UTC timezone'
703 """
704 now = pendulum.now().in_tz('America/New_York')
705 if not args.dt and not args.timezone:
706 event.msg.reply('It is currently: {}'.format(now.format('LLLL zz')))
707 return
708
709 if not all(arg for arg in vars(args).values()):
710 self.log.error('{} used !timezone with partial args: {}'.format(event.author, args))
711 return
712
713 dt = dateutil.parser.parse(' '.join(args.dt))
714 dt = pendulum.instance(dt, 'America/New_York')
715 try:
716 new_dt = dt.in_tz(args.timezone)
717 event.msg.reply(new_dt.format('LLLL zz'))
718 except (pendulum.tz.zoneinfo.exceptions.InvalidTimezone, Exception), e:
719 self.log.error('{} got error using !timezone\n{}'.format(event.author, e))
720 self.log.info('Args for error above: {}'.format(args))
721
722
723 @Plugin.command('weather', aliases=['wg'], parser=True)
724 @Plugin.add_argument('state', nargs=1)
725 @Plugin.add_argument('city', nargs='+')
726 def weather_command(self, event, args):
727 """= weather =
728 Get current weather from somewhere
729 usage :: !weather STATE CITY
730 aliases :: wg
731 category :: Utilities
732 == Examples
733 !weather ny new york `Get current weather in New York City'
734 !weather ca los angeles `Get current weather in LA'
735 !weather japan tokyo `Get current weather in æ±äº¬'
736 """
737 self.log.info('{} executed !weather with args: {}'.format(event.author, args))
738 state = args.state[0]
739 city = ' '.join(args.city)
740
741 key = '' # secret lol
742 base_url = 'http://api.wunderground.com/api/{key}/conditions/q/{state}/{city}.json' # state can also be a country
743 self.log.info('weather request to {}'.format(base_url.format(state=state, city=city, key=key)))
744 r = requests.get(base_url.format(state=state, city=city, key=key))
745
746 try:
747 weather = r.json()['current_observation']
748 display_state = weather['display_location']['state_name']
749 display_city = weather['display_location']['city']
750 forecast_url = weather['forecast_url']
751 icon_url = weather['icon_url']
752 precip = weather['precip_today_string']
753 temp_c = weather['temp_c']
754 temp_f = weather['temp_f']
755 condition = weather['weather']
756 wind_dir = weather['wind_dir']
757 wind_mph = weather['wind_mph']
758 wind_kph = weather['wind_kph']
759 except KeyError, e:
760 self.log.error('Invalid query')
761 return
762
763 embed = MessageEmbed()
764 embed.set_author(name='Wunderground', url='http://www.wunderground.com', icon_url='http://icons.wxug.com/graphics/wu2/logo_130x80.png')
765 embed.title = '{}, {}'.format(display_state, display_city)
766 embed.url = forecast_url
767 embed.description = condition
768 embed.add_field(name='Temperature', value='{}° F ({}° C)'.format(temp_f, temp_c), inline=True)
769 embed.add_field(name='Precipitation', value=precip, inline=True)
770 embed.add_field(name='Wind', value='From the {} at {}mph ({}kph)'.format(wind_dir, wind_mph, wind_kph), inline=True)
771 embed.timestamp = pendulum.now().in_tz('America/New_York').isoformat()
772 embed.set_thumbnail(url=icon_url)
773 embed.set_footer(text='Powered by Weather Underground')
774 embed.color = '2189209' # dark blue (hex: #216799)
775
776 event.msg.reply(embed=embed)
777
778
779 @Plugin.listen('GuildMemberAdd')
780 def on_guild_join(self, guild_member):
781 if guild_member.guild_id != 414960415018057738:
782 return
783
784 self.log.info(u'{} joined {}'.format(guild_member.name, guild_member.guild.name))
785
786 if guild_member.id in self.secret_staff:
787 self.log.info('Adding to secret staff')
788 role_obj = guild_member.guild.roles.get(441703257107202050) # secret staff player role
789 else:
790 role_obj = guild_member.guild.roles.get(441659643408678932) # normal player role
791
792 guild_member.add_role(role_obj)
793 self.log.info(u'Added {} to {} role'.format(guild_member.name, role_obj.name))