Added a flag to enable 24bit color output in images.
[rainbowstream.git] / rainbowstream / draw.py
1 import random
2 import textwrap
3 import itertools
4 import requests
5 import locale
6 import arrow
7 import re
8 import os
9
10 from io import BytesIO
11 from twitter.util import printNicely
12 from functools import wraps
13 from pyfiglet import figlet_format
14 from dateutil import parser
15 from .c_image import *
16 from .colors import *
17 from .config import *
18 from .py3patch import *
19 from .emoji import *
20
21 # Draw global variables
22 dg = {}
23
24
25 def init_cycle():
26 """
27 Init the cycle
28 """
29 colors_shuffle = [globals()[i.encode('utf8')]
30 if not str(i).isdigit()
31 else term_color(int(i))
32 for i in c['CYCLE_COLOR']]
33 return itertools.cycle(colors_shuffle)
34
35
36 def start_cycle():
37 """
38 Notify from rainbow
39 """
40 dg['cyc'] = init_cycle()
41 dg['cache'] = {}
42 dg['humanize_unsupported'] = False
43
44
45 def order_rainbow(s):
46 """
47 Print a string with ordered color with each character
48 """
49 colors_shuffle = [globals()[i.encode('utf8')]
50 if not str(i).isdigit()
51 else term_color(int(i))
52 for i in c['CYCLE_COLOR']]
53 colored = [colors_shuffle[i % 7](s[i]) for i in xrange(len(s))]
54 return ''.join(colored)
55
56
57 def random_rainbow(s):
58 """
59 Print a string with random color with each character
60 """
61 colors_shuffle = [globals()[i.encode('utf8')]
62 if not str(i).isdigit()
63 else term_color(int(i))
64 for i in c['CYCLE_COLOR']]
65 colored = [random.choice(colors_shuffle)(i) for i in s]
66 return ''.join(colored)
67
68
69 def Memoize(func):
70 """
71 Memoize decorator
72 """
73 @wraps(func)
74 def wrapper(*args):
75 if args not in dg['cache']:
76 dg['cache'][args] = func(*args)
77 return dg['cache'][args]
78 return wrapper
79
80
81 @Memoize
82 def cycle_color(s):
83 """
84 Cycle the colors_shuffle
85 """
86 return next(dg['cyc'])(s)
87
88
89 def ascii_art(text):
90 """
91 Draw the Ascii Art
92 """
93 fi = figlet_format(text, font='doom')
94 print('\n'.join(
95 [next(dg['cyc'])(i) for i in fi.split('\n')]
96 ))
97
98
99 def check_config():
100 """
101 Check if config is changed
102 """
103 changed = False
104 data = get_all_config()
105 for key in c:
106 if key in data:
107 if data[key] != c[key]:
108 changed = True
109 if changed:
110 reload_config()
111
112
113 def validate_theme(theme):
114 """
115 Validate a theme exists or not
116 """
117 # Theme changed check
118 files = os.listdir(os.path.dirname(__file__) + '/colorset')
119 themes = [f.split('.')[0] for f in files if f.split('.')[-1] == 'json']
120 return theme in themes
121
122
123 def reload_theme(value, prev):
124 """
125 Check current theme and update if necessary
126 """
127 if value != prev:
128 config = os.path.dirname(
129 __file__) + '/colorset/' + value + '.json'
130 # Load new config
131 data = load_config(config)
132 if data:
133 for d in data:
134 c[d] = data[d]
135 # Restart color cycle and update config
136 start_cycle()
137 set_config('THEME', value)
138 return value
139 return prev
140
141
142 def color_func(func_name):
143 """
144 Call color function base on name
145 """
146 if str(func_name).isdigit():
147 return term_color(int(func_name))
148 return globals()[func_name]
149
150
151 def fallback_humanize(date, fallback_format=None, use_fallback=False):
152 """
153 Format date with arrow and a fallback format
154 """
155 # Convert to local timezone
156 date = arrow.get(date).to('local')
157 # Set default fallback format
158 if not fallback_format:
159 fallback_format = '%Y/%m/%d %H:%M:%S'
160 # Determine using fallback format or not by a variable
161 if use_fallback:
162 return date.datetime.strftime(fallback_format)
163 try:
164 # Use Arrow's humanize function
165 lang, encode = locale.getdefaultlocale()
166 clock = date.humanize(locale=lang)
167 except:
168 # Notice at the 1st time only
169 if not dg['humanize_unsupported']:
170 dg['humanize_unsupported'] = True
171 printNicely(
172 light_magenta('Humanized date display method does not support your $LC_ALL.'))
173 # Fallback when LC_ALL is not supported
174 clock = date.datetime.strftime(fallback_format)
175 return clock
176
177
178 def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
179 """
180 Draw the rainbow
181 """
182 # Check config
183 check_config()
184
185 # Retrieve tweet
186 tid = t['id']
187 text = t['text']
188 screen_name = t['user']['screen_name']
189 name = t['user']['name']
190 created_at = t['created_at']
191 favorited = t['favorited']
192 retweet_count = t['retweet_count']
193 favorite_count = t['favorite_count']
194 client = t['source']
195 date = parser.parse(created_at)
196 try:
197 clock_format = c['FORMAT']['TWEET']['CLOCK_FORMAT']
198 except:
199 clock_format = '%Y/%m/%d %H:%M:%S'
200 clock = fallback_humanize(date, clock_format, not humanize)
201
202 # Pull extended retweet text
203 try:
204 text = 'RT @' + t['retweeted_status']['user']['screen_name'] + ': ' +\
205 t['retweeted_status']['text']
206 # Display as a notification
207 target = t['retweeted_status']['user']['screen_name']
208 if all([target == c['original_name'], not noti]):
209 # Add to evens for 'notification' command
210 t['event'] = 'retweet'
211 c['events'].append(t)
212 notify_retweet(t)
213 return
214 except:
215 pass
216
217 # Unescape HTML character
218 text = unescape(text)
219
220 # Get client name
221 try:
222 client = client.split('>')[-2].split('<')[0]
223 except:
224 client = None
225
226 # Get expanded url
227 try:
228 expanded_url = []
229 url = []
230 urls = t['entities']['urls']
231 for u in urls:
232 expanded_url.append(u['expanded_url'])
233 url.append(u['url'])
234 except:
235 expanded_url = None
236 url = None
237
238 # Get media
239 try:
240 media_url = []
241 media = t['entities']['media']
242 for m in media:
243 media_url.append(m['media_url'])
244 except:
245 media_url = None
246
247 # Filter and ignore
248 mytweet = screen_name == c['original_name']
249 screen_name = '@' + screen_name
250 fil = list(set((fil or []) + c['ONLY_LIST']))
251 ig = list(set((ig or []) + c['IGNORE_LIST']))
252 if fil and screen_name not in fil:
253 return
254 if ig and screen_name in ig:
255 return
256
257 # Get rainbow id
258 if tid not in c['tweet_dict']:
259 c['tweet_dict'].append(tid)
260 rid = len(c['tweet_dict']) - 1
261 else:
262 rid = c['tweet_dict'].index(tid)
263
264 # Format info
265 name = cycle_color(name)
266 if mytweet:
267 nick = color_func(c['TWEET']['mynick'])(screen_name)
268 else:
269 nick = color_func(c['TWEET']['nick'])(screen_name)
270 clock = clock
271 id = str(rid)
272 fav = ''
273 if favorited:
274 fav = color_func(c['TWEET']['favorited'])(u'\u2605')
275
276 tweet = text.split()
277 # Replace url
278 if expanded_url:
279 for index in xrange(len(expanded_url)):
280 tweet = lmap(
281 lambda x: expanded_url[index]
282 if x == url[index]
283 else x,
284 tweet)
285 # Highlight RT
286 tweet = lmap(
287 lambda x: color_func(c['TWEET']['rt'])(x)
288 if x == 'RT'
289 else x,
290 tweet)
291 # Highlight screen_name
292 tweet = lmap(lambda x: cycle_color(x) if x[0] == '@' else x, tweet)
293 # Highlight link
294 tweet = lmap(
295 lambda x: color_func(c['TWEET']['link'])(x)
296 if x.startswith('http')
297 else x,
298 tweet)
299 # Highlight hashtag
300 tweet = lmap(
301 lambda x: color_func(c['TWEET']['hashtag'])(x)
302 if x.startswith('#')
303 else x,
304 tweet)
305 # Highlight my tweet
306 if mytweet:
307 tweet = [color_func(c['TWEET']['mytweet'])(x)
308 for x in tweet
309 if not any([
310 x == 'RT',
311 x.startswith('http'),
312 x.startswith('#')])
313 ]
314 # Highlight keyword
315 tweet = ' '.join(tweet)
316 if keyword:
317 roj = re.search(keyword, tweet, re.IGNORECASE)
318 if roj:
319 occur = roj.group()
320 ary = tweet.split(occur)
321 delimiter = color_func(c['TWEET']['keyword'])(occur)
322 tweet = delimiter.join(ary)
323
324 # Load config formater
325 formater = ''
326 try:
327 formater = c['FORMAT']['TWEET']['DISPLAY']
328 formater = name.join(formater.split('#name'))
329 formater = nick.join(formater.split('#nick'))
330 formater = fav.join(formater.split('#fav'))
331 formater = tweet.join(formater.split('#tweet'))
332 formater = emojize(formater)
333 # Change clock word
334 word = [wo for wo in formater.split() if '#clock' in wo][0]
335 delimiter = color_func(c['TWEET']['clock'])(
336 clock.join(word.split('#clock')))
337 formater = delimiter.join(formater.split(word))
338 # Change id word
339 word = [wo for wo in formater.split() if '#id' in wo][0]
340 delimiter = color_func(c['TWEET']['id'])(id.join(word.split('#id')))
341 formater = delimiter.join(formater.split(word))
342 # Change retweet count word
343 word = [wo for wo in formater.split() if '#rt_count' in wo][0]
344 delimiter = color_func(c['TWEET']['retweet_count'])(
345 str(retweet_count).join(word.split('#rt_count')))
346 formater = delimiter.join(formater.split(word))
347 # Change favorites count word
348 word = [wo for wo in formater.split() if '#fa_count' in wo][0]
349 delimiter = color_func(c['TWEET']['favorite_count'])(
350 str(favorite_count).join(word.split('#fa_count')))
351 formater = delimiter.join(formater.split(word))
352 # Change client word
353 word = [wo for wo in formater.split() if '#client' in wo][0]
354 delimiter = color_func(c['TWEET']['client'])(
355 client.join(word.split('#client')))
356 formater = delimiter.join(formater.split(word))
357 except:
358 pass
359
360 # Add spaces in begining of line if this is inside a notification
361 if noti:
362 formater = '\n '.join(formater.split('\n'))
363 # Reformat
364 if formater.startswith('\n'):
365 formater = formater[1:]
366
367 # Draw
368 printNicely(formater)
369
370 # Display Image
371 if c['IMAGE_ON_TERM'] and media_url:
372 for mu in media_url:
373 try:
374 response = requests.get(mu)
375 image_to_display(BytesIO(response.content))
376 except Exception:
377 printNicely(red('Sorry, image link is broken'))
378
379
380 def print_threads(d):
381 """
382 Print threads of messages
383 """
384 id = 1
385 rel = {}
386 for partner in d:
387 messages = d[partner]
388 count = len(messages)
389 screen_name = '@' + partner[0]
390 name = partner[1]
391 screen_name = color_func(c['MESSAGE']['partner'])(screen_name)
392 name = cycle_color(name)
393 thread_id = color_func(c['MESSAGE']['id'])('thread_id:' + str(id))
394 line = ' ' * 2 + name + ' ' + screen_name + \
395 ' (' + str(count) + ' message) ' + thread_id
396 printNicely(line)
397 rel[id] = partner
398 id += 1
399 dg['thread'] = d
400 return rel
401
402
403 def print_thread(partner, me_nick, me_name):
404 """
405 Print a thread of messages
406 """
407 # Sort messages by time
408 messages = dg['thread'][partner]
409 messages.sort(key=lambda x: parser.parse(x['created_at']))
410 # Use legacy display on non-ascii text message
411 ms = [m['text'] for m in messages]
412 ums = [m for m in ms if not all(ord(c) < 128 for c in m)]
413 if ums:
414 for m in messages:
415 print_message(m)
416 printNicely('')
417 return
418 # Print the first line
419 dg['frame_margin'] = margin = 2
420 partner_nick = partner[0]
421 partner_name = partner[1]
422 left_size = len(partner_nick) + len(partner_name) + 2
423 right_size = len(me_nick) + len(me_name) + 2
424 partner_nick = color_func(c['MESSAGE']['partner'])('@' + partner_nick)
425 partner_name = cycle_color(partner_name)
426 me_screen_name = color_func(c['MESSAGE']['me'])('@' + me_nick)
427 me_name = cycle_color(me_name)
428 left = ' ' * margin + partner_name + ' ' + partner_nick
429 right = me_name + ' ' + me_screen_name + ' ' * margin
430 h, w = os.popen('stty size', 'r').read().split()
431 w = int(w)
432 line = '{}{}{}'.format(
433 left, ' ' * (w - left_size - right_size - 2 * margin), right)
434 printNicely('')
435 printNicely(line)
436 printNicely('')
437 # Print messages
438 for m in messages:
439 if m['sender_screen_name'] == me_nick:
440 print_right_message(m)
441 elif m['recipient_screen_name'] == me_nick:
442 print_left_message(m)
443
444
445 def print_right_message(m):
446 """
447 Print a message on the right of screen
448 """
449 h, w = os.popen('stty size', 'r').read().split()
450 w = int(w)
451 frame_width = w // 3 - dg['frame_margin']
452 frame_width = max(c['THREAD_MIN_WIDTH'], frame_width)
453 step = frame_width - 2 * dg['frame_margin']
454 slicing = textwrap.wrap(m['text'], step)
455 spaces = w - frame_width - dg['frame_margin']
456 dotline = ' ' * spaces + '-' * frame_width
457 dotline = color_func(c['MESSAGE']['me_frame'])(dotline)
458 # Draw the frame
459 printNicely(dotline)
460 for line in slicing:
461 fill = step - len(line)
462 screen_line = ' ' * spaces + '| ' + line + ' ' * fill + ' '
463 if slicing[-1] == line:
464 screen_line = screen_line + ' >'
465 else:
466 screen_line = screen_line + '|'
467 screen_line = color_func(c['MESSAGE']['me_frame'])(screen_line)
468 printNicely(screen_line)
469 printNicely(dotline)
470 # Format clock
471 date = parser.parse(m['created_at'])
472 date = arrow.get(date).to('local').datetime
473 clock_format = '%Y/%m/%d %H:%M:%S'
474 try:
475 clock_format = c['FORMAT']['MESSAGE']['CLOCK_FORMAT']
476 except:
477 pass
478 clock = date.strftime(clock_format)
479 # Format id
480 if m['id'] not in c['message_dict']:
481 c['message_dict'].append(m['id'])
482 rid = len(c['message_dict']) - 1
483 else:
484 rid = c['message_dict'].index(m['id'])
485 id = str(rid)
486 # Print meta
487 formater = ''
488 try:
489 virtual_meta = formater = c['THREAD_META_RIGHT']
490 virtual_meta = clock.join(virtual_meta.split('#clock'))
491 virtual_meta = id.join(virtual_meta.split('#id'))
492 # Change clock word
493 word = [wo for wo in formater.split() if '#clock' in wo][0]
494 delimiter = color_func(c['MESSAGE']['clock'])(
495 clock.join(word.split('#clock')))
496 formater = delimiter.join(formater.split(word))
497 # Change id word
498 word = [wo for wo in formater.split() if '#id' in wo][0]
499 delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
500 formater = delimiter.join(formater.split(word))
501 formater = emojize(formater)
502 except Exception:
503 printNicely(red('Wrong format in config.'))
504 return
505 meta = formater
506 line = ' ' * (w - len(virtual_meta) - dg['frame_margin']) + meta
507 printNicely(line)
508
509
510 def print_left_message(m):
511 """
512 Print a message on the left of screen
513 """
514 h, w = os.popen('stty size', 'r').read().split()
515 w = int(w)
516 frame_width = w // 3 - dg['frame_margin']
517 frame_width = max(c['THREAD_MIN_WIDTH'], frame_width)
518 step = frame_width - 2 * dg['frame_margin']
519 slicing = textwrap.wrap(m['text'], step)
520 spaces = dg['frame_margin']
521 dotline = ' ' * spaces + '-' * frame_width
522 dotline = color_func(c['MESSAGE']['partner_frame'])(dotline)
523 # Draw the frame
524 printNicely(dotline)
525 for line in slicing:
526 fill = step - len(line)
527 screen_line = ' ' + line + ' ' * fill + ' |'
528 if slicing[-1] == line:
529 screen_line = ' ' * (spaces - 1) + '< ' + screen_line
530 else:
531 screen_line = ' ' * spaces + '|' + screen_line
532 screen_line = color_func(c['MESSAGE']['partner_frame'])(screen_line)
533 printNicely(screen_line)
534 printNicely(dotline)
535 # Format clock
536 date = parser.parse(m['created_at'])
537 date = arrow.get(date).to('local').datetime
538 clock_format = '%Y/%m/%d %H:%M:%S'
539 try:
540 clock_format = c['FORMAT']['MESSAGE']['CLOCK_FORMAT']
541 except:
542 pass
543 clock = date.strftime(clock_format)
544 # Format id
545 if m['id'] not in c['message_dict']:
546 c['message_dict'].append(m['id'])
547 rid = len(c['message_dict']) - 1
548 else:
549 rid = c['message_dict'].index(m['id'])
550 id = str(rid)
551 # Print meta
552 formater = ''
553 try:
554 virtual_meta = formater = c['THREAD_META_LEFT']
555 virtual_meta = clock.join(virtual_meta.split('#clock'))
556 virtual_meta = id.join(virtual_meta.split('#id'))
557 # Change clock word
558 word = [wo for wo in formater.split() if '#clock' in wo][0]
559 delimiter = color_func(c['MESSAGE']['clock'])(
560 clock.join(word.split('#clock')))
561 formater = delimiter.join(formater.split(word))
562 # Change id word
563 word = [wo for wo in formater.split() if '#id' in wo][0]
564 delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
565 formater = delimiter.join(formater.split(word))
566 formater = emojize(formater)
567 except Exception:
568 printNicely(red('Wrong format in config.'))
569 return
570 meta = formater
571 line = ' ' * dg['frame_margin'] + meta
572 printNicely(line)
573
574
575 def print_message(m):
576 """
577 Print direct message
578 """
579 # Retrieve message
580 sender_screen_name = '@' + m['sender_screen_name']
581 sender_name = m['sender']['name']
582 text = unescape(m['text'])
583 recipient_screen_name = '@' + m['recipient_screen_name']
584 recipient_name = m['recipient']['name']
585 mid = m['id']
586 date = parser.parse(m['created_at'])
587 date = arrow.get(date).to('local').datetime
588 clock_format = '%Y/%m/%d %H:%M:%S'
589 try:
590 clock_format = c['FORMAT']['MESSAGE']['CLOCK_FORMAT']
591 except:
592 pass
593 clock = date.strftime(clock_format)
594
595 # Get rainbow id
596 if mid not in c['message_dict']:
597 c['message_dict'].append(mid)
598 rid = len(c['message_dict']) - 1
599 else:
600 rid = c['message_dict'].index(mid)
601
602 # Draw
603 sender_name = cycle_color(sender_name)
604 sender_nick = color_func(c['MESSAGE']['sender'])(sender_screen_name)
605 recipient_name = cycle_color(recipient_name)
606 recipient_nick = color_func(
607 c['MESSAGE']['recipient'])(recipient_screen_name)
608 to = color_func(c['MESSAGE']['to'])('>>>')
609 clock = clock
610 id = str(rid)
611
612 text = ''.join(lmap(lambda x: x + ' ' if x == '\n' else x, text))
613
614 # Load config formater
615 try:
616 formater = c['FORMAT']['MESSAGE']['DISPLAY']
617 formater = sender_name.join(formater.split("#sender_name"))
618 formater = sender_nick.join(formater.split("#sender_nick"))
619 formater = to.join(formater.split("#to"))
620 formater = recipient_name.join(formater.split("#recipient_name"))
621 formater = recipient_nick.join(formater.split("#recipient_nick"))
622 formater = text.join(formater.split("#message"))
623 # Change clock word
624 word = [wo for wo in formater.split() if '#clock' in wo][0]
625 delimiter = color_func(c['MESSAGE']['clock'])(
626 clock.join(word.split('#clock')))
627 formater = delimiter.join(formater.split(word))
628 # Change id word
629 word = [wo for wo in formater.split() if '#id' in wo][0]
630 delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
631 formater = delimiter.join(formater.split(word))
632 formater = emojize(formater)
633 except:
634 printNicely(red('Wrong format in config.'))
635 return
636
637 # Draw
638 printNicely(formater)
639
640
641 def notify_retweet(t):
642 """
643 Notify a retweet
644 """
645 source = t['user']
646 created_at = t['created_at']
647 # Format
648 source_user = cycle_color(source['name']) + \
649 color_func(c['NOTIFICATION']['source_nick'])(
650 ' @' + source['screen_name'])
651 notify = color_func(c['NOTIFICATION']['notify'])(
652 'retweeted your tweet')
653 date = parser.parse(created_at)
654 clock = fallback_humanize(date)
655 clock = color_func(c['NOTIFICATION']['clock'])(clock)
656 meta = c['NOTIFY_FORMAT']
657 meta = source_user.join(meta.split('#source_user'))
658 meta = notify.join(meta.split('#notify'))
659 meta = clock.join(meta.split('#clock'))
660 meta = emojize(meta)
661 # Output
662 printNicely('')
663 printNicely(meta)
664 draw(t=t['retweeted_status'], noti=True)
665
666
667 def notify_favorite(e):
668 """
669 Notify a favorite event
670 """
671 # Retrieve info
672 target = e['target']
673 if target['screen_name'] != c['original_name']:
674 return
675 source = e['source']
676 target_object = e['target_object']
677 created_at = e['created_at']
678 # Format
679 source_user = cycle_color(source['name']) + \
680 color_func(c['NOTIFICATION']['source_nick'])(
681 ' @' + source['screen_name'])
682 notify = color_func(c['NOTIFICATION']['notify'])(
683 'favorited your tweet')
684 date = parser.parse(created_at)
685 clock = fallback_humanize(date)
686 clock = color_func(c['NOTIFICATION']['clock'])(clock)
687 meta = c['NOTIFY_FORMAT']
688 meta = source_user.join(meta.split('#source_user'))
689 meta = notify.join(meta.split('#notify'))
690 meta = clock.join(meta.split('#clock'))
691 meta = emojize(meta)
692 # Output
693 printNicely('')
694 printNicely(meta)
695 draw(t=target_object, noti=True)
696
697
698 def notify_unfavorite(e):
699 """
700 Notify a unfavorite event
701 """
702 # Retrieve info
703 target = e['target']
704 if target['screen_name'] != c['original_name']:
705 return
706 source = e['source']
707 target_object = e['target_object']
708 created_at = e['created_at']
709 # Format
710 source_user = cycle_color(source['name']) + \
711 color_func(c['NOTIFICATION']['source_nick'])(
712 ' @' + source['screen_name'])
713 notify = color_func(c['NOTIFICATION']['notify'])(
714 'unfavorited your tweet')
715 date = parser.parse(created_at)
716 clock = fallback_humanize(date)
717 clock = color_func(c['NOTIFICATION']['clock'])(clock)
718 meta = c['NOTIFY_FORMAT']
719 meta = source_user.join(meta.split('#source_user'))
720 meta = notify.join(meta.split('#notify'))
721 meta = clock.join(meta.split('#clock'))
722 meta = emojize(meta)
723 # Output
724 printNicely('')
725 printNicely(meta)
726 draw(t=target_object, noti=True)
727
728
729 def notify_follow(e):
730 """
731 Notify a follow event
732 """
733 # Retrieve info
734 target = e['target']
735 if target['screen_name'] != c['original_name']:
736 return
737 source = e['source']
738 created_at = e['created_at']
739 # Format
740 source_user = cycle_color(source['name']) + \
741 color_func(c['NOTIFICATION']['source_nick'])(
742 ' @' + source['screen_name'])
743 notify = color_func(c['NOTIFICATION']['notify'])(
744 'followed you')
745 date = parser.parse(created_at)
746 clock = fallback_humanize(date)
747 clock = color_func(c['NOTIFICATION']['clock'])(clock)
748 meta = c['NOTIFY_FORMAT']
749 meta = source_user.join(meta.split('#source_user'))
750 meta = notify.join(meta.split('#notify'))
751 meta = clock.join(meta.split('#clock'))
752 meta = emojize(meta)
753 # Output
754 printNicely('')
755 printNicely(meta)
756
757
758 def notify_list_member_added(e):
759 """
760 Notify a list_member_added event
761 """
762 # Retrieve info
763 target = e['target']
764 if target['screen_name'] != c['original_name']:
765 return
766 source = e['source']
767 target_object = [e['target_object']] # list of Twitter list
768 created_at = e['created_at']
769 # Format
770 source_user = cycle_color(source['name']) + \
771 color_func(c['NOTIFICATION']['source_nick'])(
772 ' @' + source['screen_name'])
773 notify = color_func(c['NOTIFICATION']['notify'])(
774 'added you to a list')
775 date = parser.parse(created_at)
776 clock = fallback_humanize(date)
777 clock = color_func(c['NOTIFICATION']['clock'])(clock)
778 meta = c['NOTIFY_FORMAT']
779 meta = source_user.join(meta.split('#source_user'))
780 meta = notify.join(meta.split('#notify'))
781 meta = clock.join(meta.split('#clock'))
782 meta = emojize(meta)
783 # Output
784 printNicely('')
785 printNicely(meta)
786 print_list(target_object, noti=True)
787
788
789 def notify_list_member_removed(e):
790 """
791 Notify a list_member_removed event
792 """
793 # Retrieve info
794 target = e['target']
795 if target['screen_name'] != c['original_name']:
796 return
797 source = e['source']
798 target_object = [e['target_object']] # list of Twitter list
799 created_at = e['created_at']
800 # Format
801 source_user = cycle_color(source['name']) + \
802 color_func(c['NOTIFICATION']['source_nick'])(
803 ' @' + source['screen_name'])
804 notify = color_func(c['NOTIFICATION']['notify'])(
805 'removed you from a list')
806 date = parser.parse(created_at)
807 clock = fallback_humanize(date)
808 clock = color_func(c['NOTIFICATION']['clock'])(clock)
809 meta = c['NOTIFY_FORMAT']
810 meta = source_user.join(meta.split('#source_user'))
811 meta = notify.join(meta.split('#notify'))
812 meta = clock.join(meta.split('#clock'))
813 meta = emojize(meta)
814 # Output
815 printNicely('')
816 printNicely(meta)
817 print_list(target_object, noti=True)
818
819
820 def notify_list_user_subscribed(e):
821 """
822 Notify a list_user_subscribed event
823 """
824 # Retrieve info
825 target = e['target']
826 if target['screen_name'] != c['original_name']:
827 return
828 source = e['source']
829 target_object = [e['target_object']] # list of Twitter list
830 created_at = e['created_at']
831 # Format
832 source_user = cycle_color(source['name']) + \
833 color_func(c['NOTIFICATION']['source_nick'])(
834 ' @' + source['screen_name'])
835 notify = color_func(c['NOTIFICATION']['notify'])(
836 'subscribed to your list')
837 date = parser.parse(created_at)
838 clock = fallback_humanize(date)
839 clock = color_func(c['NOTIFICATION']['clock'])(clock)
840 meta = c['NOTIFY_FORMAT']
841 meta = source_user.join(meta.split('#source_user'))
842 meta = notify.join(meta.split('#notify'))
843 meta = clock.join(meta.split('#clock'))
844 meta = emojize(meta)
845 # Output
846 printNicely('')
847 printNicely(meta)
848 print_list(target_object, noti=True)
849
850
851 def notify_list_user_unsubscribed(e):
852 """
853 Notify a list_user_unsubscribed event
854 """
855 # Retrieve info
856 target = e['target']
857 if target['screen_name'] != c['original_name']:
858 return
859 source = e['source']
860 target_object = [e['target_object']] # list of Twitter list
861 created_at = e['created_at']
862 # Format
863 source_user = cycle_color(source['name']) + \
864 color_func(c['NOTIFICATION']['source_nick'])(
865 ' @' + source['screen_name'])
866 notify = color_func(c['NOTIFICATION']['notify'])(
867 'unsubscribed from your list')
868 date = parser.parse(created_at)
869 clock = fallback_humanize(date)
870 clock = color_func(c['NOTIFICATION']['clock'])(clock)
871 meta = c['NOTIFY_FORMAT']
872 meta = source_user.join(meta.split('#source_user'))
873 meta = notify.join(meta.split('#notify'))
874 meta = clock.join(meta.split('#clock'))
875 meta = emojize(meta)
876 # Output
877 printNicely('')
878 printNicely(meta)
879 print_list(target_object, noti=True)
880
881
882 def print_event(e):
883 """
884 Notify an event
885 """
886 event_dict = {
887 'retweet': notify_retweet,
888 'favorite': notify_favorite,
889 'unfavorite': notify_unfavorite,
890 'follow': notify_follow,
891 'list_member_added': notify_list_member_added,
892 'list_member_removed': notify_list_member_removed,
893 'list_user_subscribed': notify_list_user_subscribed,
894 'list_user_unsubscribed': notify_list_user_unsubscribed,
895 }
896 event_dict.get(e['event'], lambda *args: None)(e)
897
898
899 def show_profile(u):
900 """
901 Show a profile
902 """
903 # Retrieve info
904 name = u['name']
905 screen_name = u['screen_name']
906 description = u['description']
907 profile_image_url = u['profile_image_url']
908 location = u['location']
909 url = u['url']
910 created_at = u['created_at']
911 statuses_count = u['statuses_count']
912 friends_count = u['friends_count']
913 followers_count = u['followers_count']
914
915 # Create content
916 statuses_count = color_func(
917 c['PROFILE']['statuses_count'])(
918 str(statuses_count) +
919 ' tweets')
920 friends_count = color_func(
921 c['PROFILE']['friends_count'])(
922 str(friends_count) +
923 ' following')
924 followers_count = color_func(
925 c['PROFILE']['followers_count'])(
926 str(followers_count) +
927 ' followers')
928 count = statuses_count + ' ' + friends_count + ' ' + followers_count
929 user = cycle_color(
930 name) + color_func(c['PROFILE']['nick'])(' @' + screen_name + ' : ') + count
931 profile_image_raw_url = 'Profile photo: ' + \
932 color_func(c['PROFILE']['profile_image_url'])(profile_image_url)
933 description = ''.join(
934 lmap(lambda x: x + ' ' * 4 if x == '\n' else x, description))
935 description = color_func(c['PROFILE']['description'])(description)
936 location = 'Location : ' + color_func(c['PROFILE']['location'])(location)
937 url = 'URL : ' + (color_func(c['PROFILE']['url'])(url) if url else '')
938 date = parser.parse(created_at)
939 clock = fallback_humanize(date)
940 clock = 'Joined ' + color_func(c['PROFILE']['clock'])(clock)
941
942 # Format
943 line1 = u"{u:>{uw}}".format(
944 u=user,
945 uw=len(user) + 2,
946 )
947 line2 = u"{p:>{pw}}".format(
948 p=profile_image_raw_url,
949 pw=len(profile_image_raw_url) + 4,
950 )
951 line3 = u"{d:>{dw}}".format(
952 d=description,
953 dw=len(description) + 4,
954 )
955 line4 = u"{l:>{lw}}".format(
956 l=location,
957 lw=len(location) + 4,
958 )
959 line5 = u"{u:>{uw}}".format(
960 u=url,
961 uw=len(url) + 4,
962 )
963 line6 = u"{c:>{cw}}".format(
964 c=clock,
965 cw=len(clock) + 4,
966 )
967
968 # Display
969 printNicely('')
970 printNicely(line1)
971 if c['IMAGE_ON_TERM']:
972 try:
973 response = requests.get(profile_image_url)
974 image_to_display(BytesIO(response.content))
975 except:
976 pass
977 else:
978 printNicely(line2)
979 for line in [line3, line4, line5, line6]:
980 printNicely(line)
981 printNicely('')
982
983
984 def print_trends(trends):
985 """
986 Display topics
987 """
988 for topic in trends[:c['TREND_MAX']]:
989 name = topic['name']
990 url = topic['url']
991 line = cycle_color(name) + ': ' + color_func(c['TREND']['url'])(url)
992 printNicely(line)
993 printNicely('')
994
995
996 def print_list(group, noti=False):
997 """
998 Display a list
999 """
1000 for grp in group:
1001 # Format
1002 name = grp['full_name']
1003 name = color_func(c['GROUP']['name'])(name + ' : ')
1004 member = str(grp['member_count'])
1005 member = color_func(c['GROUP']['member'])(member + ' member')
1006 subscriber = str(grp['subscriber_count'])
1007 subscriber = color_func(
1008 c['GROUP']['subscriber'])(
1009 subscriber +
1010 ' subscriber')
1011 description = grp['description'].strip()
1012 description = color_func(c['GROUP']['description'])(description)
1013 mode = grp['mode']
1014 mode = color_func(c['GROUP']['mode'])('Type: ' + mode)
1015 created_at = grp['created_at']
1016 date = parser.parse(created_at)
1017 clock = fallback_humanize(date)
1018 clock = 'Created at ' + color_func(c['GROUP']['clock'])(clock)
1019
1020 prefix = ' ' * 2
1021 # Add spaces in begining of line if this is inside a notification
1022 if noti:
1023 prefix = ' ' * 2 + prefix
1024 # Create lines
1025 line1 = prefix + name + member + ' ' + subscriber
1026 line2 = prefix + ' ' * 2 + description
1027 line3 = prefix + ' ' * 2 + mode
1028 line4 = prefix + ' ' * 2 + clock
1029
1030 # Display
1031 printNicely('')
1032 printNicely(line1)
1033 printNicely(line2)
1034 printNicely(line3)
1035 printNicely(line4)
1036
1037 if not noti:
1038 printNicely('')
1039
1040
1041 def show_calendar(month, date, rel):
1042 """
1043 Show the calendar in rainbow mode
1044 """
1045 month = random_rainbow(month)
1046 date = ' '.join([cycle_color(i) for i in date.split(' ')])
1047 today = str(int(os.popen('date +\'%d\'').read().strip()))
1048 # Display
1049 printNicely(month)
1050 printNicely(date)
1051 for line in rel:
1052 ary = line.split(' ')
1053 ary = lmap(
1054 lambda x: color_func(c['CAL']['today'])(x)
1055 if x == today
1056 else color_func(c['CAL']['days'])(x),
1057 ary)
1058 printNicely(' '.join(ary))
1059
1060
1061 def format_quote(tweet):
1062 """
1063 Quoting format
1064 """
1065 # Retrieve info
1066 screen_name = '@' + tweet['user']['screen_name']
1067 text = tweet['text']
1068 # Validate quote format
1069 if '#owner' not in c['QUOTE_FORMAT']:
1070 printNicely(light_magenta('Quote should contains #owner'))
1071 return False
1072 if '#comment' not in c['QUOTE_FORMAT']:
1073 printNicely(light_magenta('Quote format should have #comment'))
1074 return False
1075 # Build formater
1076 formater = ''
1077 try:
1078 formater = c['QUOTE_FORMAT']
1079 formater = screen_name.join(formater.split('#owner'))
1080 formater = text.join(formater.split('#tweet'))
1081 formater = u2str(formater)
1082 formater = emojize(formater)
1083 except:
1084 pass
1085 # Highlight like a tweet
1086 notice = formater.split()
1087 notice = lmap(
1088 lambda x: light_green(x)
1089 if x == '#comment'
1090 else x,
1091 notice)
1092 notice = lmap(
1093 lambda x: color_func(c['TWEET']['rt'])(x)
1094 if x == 'RT'
1095 else x,
1096 notice)
1097 notice = lmap(lambda x: cycle_color(x) if x[0] == '@' else x, notice)
1098 notice = lmap(
1099 lambda x: color_func(c['TWEET']['link'])(x)
1100 if x[0:4] == 'http'
1101 else x,
1102 notice)
1103 notice = lmap(
1104 lambda x: color_func(c['TWEET']['hashtag'])(x)
1105 if x.startswith('#')
1106 else x,
1107 notice)
1108 notice = ' '.join(notice)
1109 # Notice
1110 notice = light_magenta('Quoting: "') + notice + light_magenta('"')
1111 printNicely(notice)
1112 return formater
1113
1114
1115 # Start the color cycle
1116 start_cycle()