Added `tweet_mode='extended'` option to `t.statuses` calls
[rainbowstream.git] / rainbowstream / draw.py
index 3c0ce67722f8f2c988576213fb1dc0985780b69b..a8bd803436c227c55d98ffcf8c1c87c979caa3cb 100644 (file)
@@ -7,6 +7,7 @@ import arrow
 import re
 import os
 
+from io import BytesIO
 from twitter.util import printNicely
 from functools import wraps
 from pyfiglet import figlet_format
@@ -15,6 +16,7 @@ from .c_image import *
 from .colors import *
 from .config import *
 from .py3patch import *
+from .emoji import *
 
 # Draw global variables
 dg = {}
@@ -37,6 +39,7 @@ def start_cycle():
     """
     dg['cyc'] = init_cycle()
     dg['cache'] = {}
+    dg['humanize_unsupported'] = False
 
 
 def order_rainbow(s):
@@ -145,6 +148,50 @@ def color_func(func_name):
     return globals()[func_name]
 
 
+def fallback_humanize(date, fallback_format=None, use_fallback=False):
+    """
+    Format date with arrow and a fallback format
+    """
+    # Convert to local timezone
+    date = arrow.get(date).to('local')
+    # Set default fallback format
+    if not fallback_format:
+        fallback_format = '%Y/%m/%d %H:%M:%S'
+    # Determine using fallback format or not by a variable
+    if use_fallback:
+        return date.datetime.strftime(fallback_format)
+    try:
+        # Use Arrow's humanize function
+        lang, encode = locale.getdefaultlocale()
+        clock = date.humanize(locale=lang)
+    except:
+        # Notice at the 1st time only
+        if not dg['humanize_unsupported']:
+            dg['humanize_unsupported'] = True
+            printNicely(
+                light_magenta('Humanized date display method does not support your $LC_ALL.'))
+        # Fallback when LC_ALL is not supported
+        clock = date.datetime.strftime(fallback_format)
+    return clock
+
+
+def get_full_text(t):
+    """Handle RTs and extended tweets to always display all the available text"""
+
+    if t.get('retweeted_status'):
+        rt_status = t['retweeted_status']
+        if rt_status.get('extended_tweet'):
+            elem = rt_status['extended_tweet']
+        else:
+            elem = rt_status
+        rt_text = elem.get('full_text', elem.get('text'))
+        t['full_text'] = 'RT @' + rt_status['user']['screen_name'] + ': ' + rt_text
+    elif t.get('extended_tweet'):
+        t['full_text'] = t['extended_tweet']['full_text']
+
+    return t.get('full_text', t.get('text'))
+
+
 def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
     """
     Draw the rainbow
@@ -154,35 +201,44 @@ def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
 
     # Retrieve tweet
     tid = t['id']
-    text = t['text']
+
+    text = get_full_text(t)
     screen_name = t['user']['screen_name']
     name = t['user']['name']
     created_at = t['created_at']
     favorited = t['favorited']
     retweet_count = t['retweet_count']
     favorite_count = t['favorite_count']
+    client = t['source']
     date = parser.parse(created_at)
-    date = arrow.get(date).to('local')
-    if humanize:
-        lang, encode = locale.getdefaultlocale()
-        clock = arrow.get(date).to('local').humanize(locale=lang)
-    else:
-        try:
-            clock_format = c['FORMAT']['TWEET']['CLOCK_FORMAT']
-        except:
-            clock_format = '%Y/%m/%d %H:%M:%S'
-        clock = date.datetime.strftime(clock_format)
+    try:
+        clock_format = c['FORMAT']['TWEET']['CLOCK_FORMAT']
+    except:
+        clock_format = '%Y/%m/%d %H:%M:%S'
+    clock = fallback_humanize(date, clock_format, not humanize)
 
     # Pull extended retweet text
     try:
-        text = 'RT @' + t['retweeted_status']['user']['screen_name'] + ': ' +\
-            t['retweeted_status']['text']
+        # Display as a notification
+        target = t['retweeted_status']['user']['screen_name']
+        if all([target == c['original_name'], not noti]):
+            # Add to evens for 'notification' command
+            t['event'] = 'retweet'
+            c['events'].append(t)
+            notify_retweet(t)
+            return
     except:
         pass
 
     # Unescape HTML character
     text = unescape(text)
 
+    # Get client name
+    try:
+        client = client.split('>')[-2].split('<')[0]
+    except:
+        client = None
+
     # Get expanded url
     try:
         expanded_url = []
@@ -233,7 +289,8 @@ def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
     if favorited:
         fav = color_func(c['TWEET']['favorited'])(u'\u2605')
 
-    tweet = text.split()
+    tweet = text.split(' ')
+    tweet = [x for x in tweet if x != '']
     # Replace url
     if expanded_url:
         for index in xrange(len(expanded_url)):
@@ -249,17 +306,18 @@ def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
         else x,
         tweet)
     # Highlight screen_name
-    tweet = lmap(lambda x: cycle_color(x) if x[0] == '@' else x, tweet)
+    tweet = lmap(
+        lambda x: cycle_color(x) if x.lstrip().startswith('@') else x, tweet)
     # Highlight link
     tweet = lmap(
         lambda x: color_func(c['TWEET']['link'])(x)
-        if x.startswith('http')
+        if x.lstrip().startswith('http')
         else x,
         tweet)
     # Highlight hashtag
     tweet = lmap(
         lambda x: color_func(c['TWEET']['hashtag'])(x)
-        if x.startswith('#')
+        if x.lstrip().startswith('#')
         else x,
         tweet)
     # Highlight my tweet
@@ -268,11 +326,12 @@ def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
                  for x in tweet
                  if not any([
                      x == 'RT',
-                     x.startswith('http'),
-                     x.startswith('#')])
+                     x.lstrip().startswith('http'),
+                     x.lstrip().startswith('#')])
                  ]
     # Highlight keyword
     tweet = ' '.join(tweet)
+    tweet = '\n  '.join(tweet.split('\n'))
     if keyword:
         roj = re.search(keyword, tweet, re.IGNORECASE)
         if roj:
@@ -289,6 +348,7 @@ def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
         formater = nick.join(formater.split('#nick'))
         formater = fav.join(formater.split('#fav'))
         formater = tweet.join(formater.split('#tweet'))
+        formater = emojize(formater)
         # Change clock word
         word = [wo for wo in formater.split() if '#clock' in wo][0]
         delimiter = color_func(c['TWEET']['clock'])(
@@ -308,12 +368,20 @@ def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
         delimiter = color_func(c['TWEET']['favorite_count'])(
             str(favorite_count).join(word.split('#fa_count')))
         formater = delimiter.join(formater.split(word))
+        # Change client word
+        word = [wo for wo in formater.split() if '#client' in wo][0]
+        delimiter = color_func(c['TWEET']['client'])(
+            client.join(word.split('#client')))
+        formater = delimiter.join(formater.split(word))
     except:
         pass
 
     # Add spaces in begining of line if this is inside a notification
     if noti:
         formater = '\n  '.join(formater.split('\n'))
+        # Reformat
+        if formater.startswith('\n'):
+            formater = formater[1:]
 
     # Draw
     printNicely(formater)
@@ -449,6 +517,7 @@ def print_right_message(m):
         word = [wo for wo in formater.split() if '#id' in wo][0]
         delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
         formater = delimiter.join(formater.split(word))
+        formater = emojize(formater)
     except Exception:
         printNicely(red('Wrong format in config.'))
         return
@@ -513,6 +582,7 @@ def print_left_message(m):
         word = [wo for wo in formater.split() if '#id' in wo][0]
         delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
         formater = delimiter.join(formater.split(word))
+        formater = emojize(formater)
     except Exception:
         printNicely(red('Wrong format in config.'))
         return
@@ -578,6 +648,7 @@ def print_message(m):
         word = [wo for wo in formater.split() if '#id' in wo][0]
         delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
         formater = delimiter.join(formater.split(word))
+        formater = emojize(formater)
     except:
         printNicely(red('Wrong format in config.'))
         return
@@ -586,6 +657,32 @@ def print_message(m):
     printNicely(formater)
 
 
+def notify_retweet(t):
+    """
+    Notify a retweet
+    """
+    source = t['user']
+    created_at = t['created_at']
+    # Format
+    source_user = cycle_color(source['name']) + \
+        color_func(c['NOTIFICATION']['source_nick'])(
+        ' @' + source['screen_name'])
+    notify = color_func(c['NOTIFICATION']['notify'])(
+        'retweeted your tweet')
+    date = parser.parse(created_at)
+    clock = fallback_humanize(date)
+    clock = color_func(c['NOTIFICATION']['clock'])(clock)
+    meta = c['NOTIFY_FORMAT']
+    meta = source_user.join(meta.split('#source_user'))
+    meta = notify.join(meta.split('#notify'))
+    meta = clock.join(meta.split('#clock'))
+    meta = emojize(meta)
+    # Output
+    printNicely('')
+    printNicely(meta)
+    draw(t=t['retweeted_status'], noti=True)
+
+
 def notify_favorite(e):
     """
     Notify a favorite event
@@ -601,13 +698,16 @@ def notify_favorite(e):
     source_user = cycle_color(source['name']) + \
         color_func(c['NOTIFICATION']['source_nick'])(
         ' @' + source['screen_name'])
-    notify = color_func(c['NOTIFICATION']['notify'])(' favorited your tweet ')
+    notify = color_func(c['NOTIFICATION']['notify'])(
+        'favorited your tweet')
     date = parser.parse(created_at)
-    date = arrow.get(date).to('local')
-    lang, encode = locale.getdefaultlocale()
-    clock = arrow.get(date).to('local').humanize(locale=lang)
+    clock = fallback_humanize(date)
     clock = color_func(c['NOTIFICATION']['clock'])(clock)
-    meta = ' ' * 2 + source_user + notify + clock
+    meta = c['NOTIFY_FORMAT']
+    meta = source_user.join(meta.split('#source_user'))
+    meta = notify.join(meta.split('#notify'))
+    meta = clock.join(meta.split('#clock'))
+    meta = emojize(meta)
     # Output
     printNicely('')
     printNicely(meta)
@@ -630,13 +730,15 @@ def notify_unfavorite(e):
         color_func(c['NOTIFICATION']['source_nick'])(
         ' @' + source['screen_name'])
     notify = color_func(c['NOTIFICATION']['notify'])(
-        ' unfavorited your tweet ')
+        'unfavorited your tweet')
     date = parser.parse(created_at)
-    date = arrow.get(date).to('local')
-    lang, encode = locale.getdefaultlocale()
-    clock = arrow.get(date).to('local').humanize(locale=lang)
+    clock = fallback_humanize(date)
     clock = color_func(c['NOTIFICATION']['clock'])(clock)
-    meta = ' ' * 2 + source_user + notify + clock
+    meta = c['NOTIFY_FORMAT']
+    meta = source_user.join(meta.split('#source_user'))
+    meta = notify.join(meta.split('#notify'))
+    meta = clock.join(meta.split('#clock'))
+    meta = emojize(meta)
     # Output
     printNicely('')
     printNicely(meta)
@@ -657,13 +759,16 @@ def notify_follow(e):
     source_user = cycle_color(source['name']) + \
         color_func(c['NOTIFICATION']['source_nick'])(
         ' @' + source['screen_name'])
-    notify = color_func(c['NOTIFICATION']['notify'])(' followed you ')
+    notify = color_func(c['NOTIFICATION']['notify'])(
+        'followed you')
     date = parser.parse(created_at)
-    date = arrow.get(date).to('local')
-    lang, encode = locale.getdefaultlocale()
-    clock = arrow.get(date).to('local').humanize(locale=lang)
+    clock = fallback_humanize(date)
     clock = color_func(c['NOTIFICATION']['clock'])(clock)
-    meta = ' ' * 2 + source_user + notify + clock
+    meta = c['NOTIFY_FORMAT']
+    meta = source_user.join(meta.split('#source_user'))
+    meta = notify.join(meta.split('#notify'))
+    meta = clock.join(meta.split('#clock'))
+    meta = emojize(meta)
     # Output
     printNicely('')
     printNicely(meta)
@@ -684,13 +789,16 @@ def notify_list_member_added(e):
     source_user = cycle_color(source['name']) + \
         color_func(c['NOTIFICATION']['source_nick'])(
         ' @' + source['screen_name'])
-    notify = color_func(c['NOTIFICATION']['notify'])(' added you to a list ')
+    notify = color_func(c['NOTIFICATION']['notify'])(
+        'added you to a list')
     date = parser.parse(created_at)
-    date = arrow.get(date).to('local')
-    lang, encode = locale.getdefaultlocale()
-    clock = arrow.get(date).to('local').humanize(locale=lang)
+    clock = fallback_humanize(date)
     clock = color_func(c['NOTIFICATION']['clock'])(clock)
-    meta = ' ' * 2 + source_user + notify + clock
+    meta = c['NOTIFY_FORMAT']
+    meta = source_user.join(meta.split('#source_user'))
+    meta = notify.join(meta.split('#notify'))
+    meta = clock.join(meta.split('#clock'))
+    meta = emojize(meta)
     # Output
     printNicely('')
     printNicely(meta)
@@ -713,13 +821,15 @@ def notify_list_member_removed(e):
         color_func(c['NOTIFICATION']['source_nick'])(
         ' @' + source['screen_name'])
     notify = color_func(c['NOTIFICATION']['notify'])(
-        ' removed you from a list ')
+        'removed you from a list')
     date = parser.parse(created_at)
-    date = arrow.get(date).to('local')
-    lang, encode = locale.getdefaultlocale()
-    clock = arrow.get(date).to('local').humanize(locale=lang)
+    clock = fallback_humanize(date)
     clock = color_func(c['NOTIFICATION']['clock'])(clock)
-    meta = ' ' * 2 + source_user + notify + clock
+    meta = c['NOTIFY_FORMAT']
+    meta = source_user.join(meta.split('#source_user'))
+    meta = notify.join(meta.split('#notify'))
+    meta = clock.join(meta.split('#clock'))
+    meta = emojize(meta)
     # Output
     printNicely('')
     printNicely(meta)
@@ -742,13 +852,15 @@ def notify_list_user_subscribed(e):
         color_func(c['NOTIFICATION']['source_nick'])(
         ' @' + source['screen_name'])
     notify = color_func(c['NOTIFICATION']['notify'])(
-        ' subscribed to your list ')
+        'subscribed to your list')
     date = parser.parse(created_at)
-    date = arrow.get(date).to('local')
-    lang, encode = locale.getdefaultlocale()
-    clock = arrow.get(date).to('local').humanize(locale=lang)
+    clock = fallback_humanize(date)
     clock = color_func(c['NOTIFICATION']['clock'])(clock)
-    meta = ' ' * 2 + source_user + notify + clock
+    meta = c['NOTIFY_FORMAT']
+    meta = source_user.join(meta.split('#source_user'))
+    meta = notify.join(meta.split('#notify'))
+    meta = clock.join(meta.split('#clock'))
+    meta = emojize(meta)
     # Output
     printNicely('')
     printNicely(meta)
@@ -771,13 +883,15 @@ def notify_list_user_unsubscribed(e):
         color_func(c['NOTIFICATION']['source_nick'])(
         ' @' + source['screen_name'])
     notify = color_func(c['NOTIFICATION']['notify'])(
-        ' unsubscribed from your list ')
+        'unsubscribed from your list')
     date = parser.parse(created_at)
-    date = arrow.get(date).to('local')
-    lang, encode = locale.getdefaultlocale()
-    clock = arrow.get(date).to('local').humanize(locale=lang)
+    clock = fallback_humanize(date)
     clock = color_func(c['NOTIFICATION']['clock'])(clock)
-    meta = ' ' * 2 + source_user + notify + clock
+    meta = c['NOTIFY_FORMAT']
+    meta = source_user.join(meta.split('#source_user'))
+    meta = notify.join(meta.split('#notify'))
+    meta = clock.join(meta.split('#clock'))
+    meta = emojize(meta)
     # Output
     printNicely('')
     printNicely(meta)
@@ -789,6 +903,7 @@ def print_event(e):
     Notify an event
     """
     event_dict = {
+        'retweet': notify_retweet,
         'favorite': notify_favorite,
         'unfavorite': notify_unfavorite,
         'follow': notify_follow,
@@ -797,7 +912,7 @@ def print_event(e):
         'list_user_subscribed': notify_list_user_subscribed,
         'list_user_unsubscribed': notify_list_user_unsubscribed,
     }
-    event_dict[e['event']](e)
+    event_dict.get(e['event'], lambda *args: None)(e)
 
 
 def show_profile(u):
@@ -840,9 +955,8 @@ def show_profile(u):
     location = 'Location : ' + color_func(c['PROFILE']['location'])(location)
     url = 'URL : ' + (color_func(c['PROFILE']['url'])(url) if url else '')
     date = parser.parse(created_at)
-    lang, encode = locale.getdefaultlocale()
-    clock = arrow.get(date).to('local').humanize(locale=lang)
-    clock = 'Join at ' + color_func(c['PROFILE']['clock'])(clock)
+    clock = fallback_humanize(date)
+    clock = 'Joined ' + color_func(c['PROFILE']['clock'])(clock)
 
     # Format
     line1 = u"{u:>{uw}}".format(
@@ -919,8 +1033,7 @@ def print_list(group, noti=False):
         mode = color_func(c['GROUP']['mode'])('Type: ' + mode)
         created_at = grp['created_at']
         date = parser.parse(created_at)
-        lang, encode = locale.getdefaultlocale()
-        clock = arrow.get(date).to('local').humanize(locale=lang)
+        clock = fallback_humanize(date)
         clock = 'Created at ' + color_func(c['GROUP']['clock'])(clock)
 
         prefix = ' ' * 2
@@ -940,7 +1053,8 @@ def print_list(group, noti=False):
         printNicely(line3)
         printNicely(line4)
 
-    printNicely('')
+    if not noti:
+        printNicely('')
 
 
 def show_calendar(month, date, rel):
@@ -968,8 +1082,10 @@ def format_quote(tweet):
     Quoting format
     """
     # Retrieve info
-    screen_name = '@' + tweet['user']['screen_name']
-    text = tweet['text']
+    screen_name = tweet['user']['screen_name']
+    text = get_full_text(t)
+    tid         = str( tweet['id'] )
+
     # Validate quote format
     if '#owner' not in c['QUOTE_FORMAT']:
         printNicely(light_magenta('Quote should contains #owner'))
@@ -977,13 +1093,17 @@ def format_quote(tweet):
     if '#comment' not in c['QUOTE_FORMAT']:
         printNicely(light_magenta('Quote format should have #comment'))
         return False
+
     # Build formater
     formater = ''
     try:
         formater = c['QUOTE_FORMAT']
-        formater = screen_name.join(formater.split('#owner'))
-        formater = text.join(formater.split('#tweet'))
-        formater = u2str(formater)
+
+        formater = formater.replace('#owner', screen_name)
+        formater = formater.replace('#tweet', text)
+        formater = formater.replace('#tid',   tid)
+
+        formater = emojize(formater)
     except:
         pass
     # Highlight like a tweet