X-Git-Url: https://vcs.fsf.org/?p=rainbowstream.git;a=blobdiff_plain;f=rainbowstream%2Frainbow.py;h=de908e6ae94dfc997b8b1ef73c93b23a47106bf2;hp=f8cc9bf26faf0a867f28ffa51fcffa2403b791a7;hb=954b31016c5bf5c633e9a8793a2659e1a74024b3;hpb=595fdb16db3c29c26b391724d4c83a009bfc078f diff --git a/rainbowstream/rainbow.py b/rainbowstream/rainbow.py index f8cc9bf..de908e6 100644 --- a/rainbowstream/rainbow.py +++ b/rainbowstream/rainbow.py @@ -1,14 +1,10 @@ -""" -Colorful user's timeline stream -""" -from multiprocessing import Process - import os import os.path import sys import signal import argparse import time +import threading import requests import webbrowser @@ -17,55 +13,20 @@ from twitter.api import * from twitter.oauth import OAuth, read_token_file from twitter.oauth_dance import oauth_dance from twitter.util import printNicely -from StringIO import StringIO from .draw import * from .colors import * from .config import * from .consumer import * from .interactive import * -from .db import * from .c_image import * +from .py3patch import * +# Global values g = {} -db = RainbowDB() -cmdset = [ - 'switch', - 'trend', - 'home', - 'view', - 'mentions', - 't', - 'rt', - 'quote', - 'allrt', - 'fav', - 'rep', - 'del', - 'ufav', - 's', - 'mes', - 'show', - 'open', - 'ls', - 'inbox', - 'sent', - 'trash', - 'whois', - 'fl', - 'ufl', - 'mute', - 'unmute', - 'muting', - 'block', - 'unblock', - 'report', - 'cal', - 'theme', - 'h', - 'c', - 'q' -] + +# Lock for streams +StreamLock = threading.Lock() def parse_arguments(): @@ -77,16 +38,6 @@ def parse_arguments(): '-to', '--timeout', help='Timeout for the stream (seconds).') - parser.add_argument( - '-ht', - '--heartbeat-timeout', - help='Set heartbeat timeout.', - default=90) - parser.add_argument( - '-nb', - '--no-block', - action='store_true', - help='Set stream to non-blocking.') parser.add_argument( '-tt', '--track-keywords', @@ -130,84 +81,69 @@ def authen(): CONSUMER_SECRET) -def get_decorated_name(): +def build_mute_dict(dict_data=False): """ - Beginning of every line + Build muting list """ t = Twitter(auth=authen()) - name = '@' + t.account.verify_credentials()['screen_name'] - g['original_name'] = name[1:] - g['decorated_name'] = color_func(c['DECORATED_NAME'])('[' + name + ']: ') - g['ascii_art'] = True - - files = os.listdir(os.path.dirname(__file__)+'/colorset') - themes = [f.split('.')[0] for f in files if f.split('.')[-1] == 'json'] - themes += ['custom'] - g['themes'] = themes - db.theme_store(c['theme']) + # Init cursor + next_cursor = -1 + screen_name_list = [] + name_list = [] + # Cursor loop + while next_cursor != 0: + list = t.mutes.users.list( + screen_name=g['original_name'], + cursor=next_cursor, + skip_status=True, + include_entities=False, + ) + screen_name_list += ['@' + u['screen_name'] for u in list['users']] + name_list += [u['name'] for u in list['users']] + next_cursor = list['next_cursor'] + # Return dict or list + if dict_data: + return dict(zip(screen_name_list, name_list)) + else: + return screen_name_list -def switch(): +def init(args): """ - Switch stream + Init function """ - try: - target = g['stuff'].split()[0] - - # Filter and ignore - args = parse_arguments() - try: - if g['stuff'].split()[-1] == '-f': - only = raw_input('Only nicks: ') - ignore = raw_input('Ignore nicks: ') - args.filter = filter(None, only.split(',')) - args.ignore = filter(None, ignore.split(',')) - elif g['stuff'].split()[-1] == '-d': - args.filter = c['ONLY_LIST'] - args.ignore = c['IGNORE_LIST'] - except: - printNicely(red('Sorry, wrong format.')) - return - - # Public stream - if target == 'public': - keyword = g['stuff'].split()[1] - if keyword[0] == '#': - keyword = keyword[1:] - # Kill old process - os.kill(g['stream_pid'], signal.SIGKILL) - args.track_keywords = keyword - # Start new process - p = Process( - target=stream, - args=( - c['PUBLIC_DOMAIN'], - args)) - p.start() - g['stream_pid'] = p.pid - - # Personal stream - elif target == 'mine': - # Kill old process - os.kill(g['stream_pid'], signal.SIGKILL) - # Start new process - p = Process( - target=stream, - args=( - c['USER_DOMAIN'], - args, - g['original_name'])) - p.start() - g['stream_pid'] = p.pid - printNicely('') - if args.filter: - printNicely(cyan('Only: ' + str(args.filter))) - if args.ignore: - printNicely(red('Ignore: ' + str(args.ignore))) - printNicely('') - g['ascii_art'] = True - except: - printNicely(red('Sorry I can\'t understand.')) + # Handle Ctrl C + ctrl_c_handler = lambda signum, frame: quit() + signal.signal(signal.SIGINT, ctrl_c_handler) + # Get name + t = Twitter(auth=authen()) + credential = t.account.verify_credentials() + screen_name = '@' + credential['screen_name'] + name = credential['name'] + if not get_config('PREFIX'): + set_config('PREFIX', screen_name) + c['original_name'] = g['original_name'] = screen_name[1:] + g['full_name'] = name + g['decorated_name'] = lambda x: color_func( + c['DECORATED_NAME'])('[' + x + ']: ') + # Theme init + files = os.listdir(os.path.dirname(__file__) + '/colorset') + themes = [f.split('.')[0] for f in files if f.split('.')[-1] == 'json'] + g['themes'] = themes + g['pause'] = False + g['message_threads'] = {} + # Startup cmd + g['cmd'] = '' + # Semaphore init + c['lock'] = False + # Init tweet dict and message dict + c['tweet_dict'] = [] + c['message_dict'] = [] + # Image on term + c['IMAGE_ON_TERM'] = args.image_on_term + set_config('IMAGE_ON_TERM', str(c['IMAGE_ON_TERM'])) + # Mute dict + c['IGNORE_LIST'] += build_mute_dict() def trend(): @@ -224,7 +160,6 @@ def trend(): town = g['stuff'].split()[1] except: town = '' - avail = t.trends.available() # World wide if not country: @@ -256,10 +191,41 @@ def home(): if g['stuff'].isdigit(): num = int(g['stuff']) for tweet in reversed(t.statuses.home_timeline(count=num)): - draw(t=tweet, iot=g['iot']) + draw(t=tweet) + printNicely('') + + +def mentions(): + """ + Mentions timeline + """ + t = Twitter(auth=authen()) + num = c['HOME_TWEET_NUM'] + if g['stuff'].isdigit(): + num = int(g['stuff']) + for tweet in reversed(t.statuses.mentions_timeline(count=num)): + draw(t=tweet) printNicely('') +def whois(): + """ + Show profile of a specific user + """ + t = Twitter(auth=authen()) + screen_name = g['stuff'].split()[0] + if screen_name.startswith('@'): + try: + user = t.users.show( + screen_name=screen_name[1:], + include_entities=False) + show_profile(user) + except: + printNicely(red('Omg no user.')) + else: + printNicely(red('A name should begin with a \'@\'')) + + def view(): """ Friend view @@ -272,23 +238,38 @@ def view(): except: num = c['HOME_TWEET_NUM'] for tweet in reversed(t.statuses.user_timeline(count=num, screen_name=user[1:])): - draw(t=tweet, iot=g['iot']) + draw(t=tweet) printNicely('') else: printNicely(red('A name should begin with a \'@\'')) -def mentions(): +def search(): """ - Mentions timeline + Search """ t = Twitter(auth=authen()) - num = c['HOME_TWEET_NUM'] - if g['stuff'].isdigit(): - num = int(g['stuff']) - for tweet in reversed(t.statuses.mentions_timeline(count=num)): - draw(t=tweet, iot=g['iot']) - printNicely('') + # Setup query + query = g['stuff'].strip() + type = c['SEARCH_TYPE'] + if type not in ['mixed', 'recent', 'popular']: + type = 'mixed' + max_record = c['SEARCH_MAX_RECORD'] + count = min(max_record, 100) + # Perform search + rel = t.search.tweets( + q=query, + type=type, + count=count + )['statuses'] + # Return results + if rel: + printNicely('Newest tweets:') + for i in reversed(xrange(count)): + draw(t=rel[i], keyword=query) + printNicely('') + else: + printNicely(magenta('I\'m afraid there is no result')) def tweet(): @@ -309,7 +290,7 @@ def retweet(): except: printNicely(red('Sorry I can\'t understand.')) return - tid = db.rainbow_to_tweet_query(id)[0].tweet_id + tid = c['tweet_dict'][id] t.statuses.retweet(id=tid, include_entities=False, trim_user=True) @@ -317,25 +298,25 @@ def quote(): """ Quote a tweet """ + # Get tweet t = Twitter(auth=authen()) try: id = int(g['stuff'].split()[0]) except: printNicely(red('Sorry I can\'t understand.')) return - tid = db.rainbow_to_tweet_query(id)[0].tweet_id + tid = c['tweet_dict'][id] tweet = t.statuses.show(id=tid) - screen_name = tweet['user']['screen_name'] - text = tweet['text'] - quote = '\"@' + screen_name + ': ' + text + '\"' - quote = quote.encode('utf8') - notice = light_magenta('Compose mode ') - notice += light_yellow('(Enter nothing will cancel the quote)') - notice += light_magenta(':') - printNicely(notice) - extra = raw_input(quote) - if extra: - t.statuses.update(status=quote+extra) + # Get formater + formater = format_quote(tweet) + if not formater: + return + # Get comment + prefix = light_magenta('Compose your ') + light_green('#comment: ') + comment = raw_input(prefix) + if comment: + quote = comment.join(formater.split('#comment')) + t.statuses.update(status=quote) else: printNicely(light_magenta('No text added.')) @@ -351,7 +332,7 @@ def allretweet(): except: printNicely(red('Sorry I can\'t understand.')) return - tid = db.rainbow_to_tweet_query(id)[0].tweet_id + tid = c['tweet_dict'][id] # Get display num if exist try: num = int(g['stuff'].split()[1]) @@ -363,13 +344,13 @@ def allretweet(): printNicely(magenta('This tweet has no retweet.')) return for tweet in reversed(rt_ary): - draw(t=tweet, iot=g['iot']) + draw(t=tweet) printNicely('') -def favorite(): +def conversation(): """ - Favorite + Conversation view """ t = Twitter(auth=authen()) try: @@ -377,10 +358,20 @@ def favorite(): except: printNicely(red('Sorry I can\'t understand.')) return - tid = db.rainbow_to_tweet_query(id)[0].tweet_id - t.favorites.create(_id=tid, include_entities=False) - printNicely(green('Favorited.')) - draw(t.statuses.show(id=tid), iot=g['iot']) + tid = c['tweet_dict'][id] + tweet = t.statuses.show(id=tid) + limit = c['CONVERSATION_MAX'] + thread_ref = [] + thread_ref.append(tweet) + prev_tid = tweet['in_reply_to_status_id'] + while prev_tid and limit: + limit -= 1 + tweet = t.statuses.show(id=prev_tid) + prev_tid = tweet['in_reply_to_status_id'] + thread_ref.append(tweet) + + for tweet in reversed(thread_ref): + draw(t=tweet) printNicely('') @@ -394,26 +385,28 @@ def reply(): except: printNicely(red('Sorry I can\'t understand.')) return - tid = db.rainbow_to_tweet_query(id)[0].tweet_id + tid = c['tweet_dict'][id] user = t.statuses.show(id=tid)['user']['screen_name'] status = ' '.join(g['stuff'].split()[1:]) - status = '@' + user + ' ' + status.decode('utf-8') + status = '@' + user + ' ' + str2u(status) t.statuses.update(status=status, in_reply_to_status_id=tid) -def delete(): +def favorite(): """ - Delete + Favorite """ t = Twitter(auth=authen()) try: - rid = int(g['stuff'].split()[0]) + id = int(g['stuff'].split()[0]) except: printNicely(red('Sorry I can\'t understand.')) return - tid = db.rainbow_to_tweet_query(rid)[0].tweet_id - t.statuses.destroy(id=tid) - printNicely(green('Okay it\'s gone.')) + tid = c['tweet_dict'][id] + t.favorites.create(_id=tid, include_entities=False) + printNicely(green('Favorited.')) + draw(t.statuses.show(id=tid)) + printNicely('') def unfavorite(): @@ -426,51 +419,26 @@ def unfavorite(): except: printNicely(red('Sorry I can\'t understand.')) return - tid = db.rainbow_to_tweet_query(id)[0].tweet_id + tid = c['tweet_dict'][id] t.favorites.destroy(_id=tid) printNicely(green('Okay it\'s unfavorited.')) - draw(t.statuses.show(id=tid), iot=g['iot']) + draw(t.statuses.show(id=tid)) printNicely('') -def search(): - """ - Search - """ - t = Twitter(auth=authen()) - if g['stuff'].startswith('#'): - rel = t.search.tweets(q=g['stuff'])['statuses'] - if rel: - printNicely('Newest tweets:') - for i in reversed(xrange(c['SEARCH_MAX_RECORD'])): - draw(t=rel[i], - iot=g['iot'], - keyword=g['stuff'].strip()[1:]) - printNicely('') - else: - printNicely(magenta('I\'m afraid there is no result')) - else: - printNicely(red('A keyword should be a hashtag (like \'#AKB48\')')) - - -def message(): +def delete(): """ - Send a direct message + Delete """ t = Twitter(auth=authen()) - user = g['stuff'].split()[0] - if user[0].startswith('@'): - try: - content = g['stuff'].split()[1] - except: - printNicely(red('Sorry I can\'t understand.')) - t.direct_messages.new( - screen_name=user[1:], - text=content - ) - printNicely(green('Message sent.')) - else: - printNicely(red('A name should begin with a \'@\'')) + try: + id = int(g['stuff'].split()[0]) + except: + printNicely(red('Sorry I can\'t understand.')) + return + tid = c['tweet_dict'][id] + t.statuses.destroy(id=tid) + printNicely(green('Okay it\'s gone.')) def show(): @@ -483,12 +451,12 @@ def show(): if target != 'image': return id = int(g['stuff'].split()[1]) - tid = db.rainbow_to_tweet_query(id)[0].tweet_id + tid = c['tweet_dict'][id] tweet = t.statuses.show(id=tid) media = tweet['entities']['media'] for m in media: res = requests.get(m['media_url']) - img = Image.open(StringIO(res.content)) + img = Image.open(BytesIO(res.content)) img.show() except: printNicely(red('Sorry I can\'t show this image.')) @@ -502,9 +470,11 @@ def urlopen(): try: if not g['stuff'].isdigit(): return - tid = db.rainbow_to_tweet_query(g['stuff'])[0].tweet_id + tid = c['tweet_dict'][int(g['stuff'])] tweet = t.statuses.show(id=tid) - link_ary = [u for u in tweet['text'].split() if u.startswith('http://')] + link_prefix = ('http://', 'https://') + link_ary = [u for u in tweet['text'].split() + if u.startswith(link_prefix)] if not link_ary: printNicely(light_magenta('No url here @.@!')) return @@ -514,61 +484,19 @@ def urlopen(): printNicely(red('Sorry I can\'t open url in this tweet.')) -def list(): - """ - List friends for followers - """ - t = Twitter(auth=authen()) - # Get name - try: - name = g['stuff'].split()[1] - if name.startswith('@'): - name = name[1:] - else: - printNicely(red('A name should begin with a \'@\'')) - raise Exception('Invalid name') - except: - name = g['original_name'] - # Get list followers or friends - try: - target = g['stuff'].split()[0] - except: - printNicely(red('Omg some syntax is wrong.')) - # Init cursor - d = {'fl': 'followers', 'fr': 'friends'} - next_cursor = -1 - rel = {} - # Cursor loop - while next_cursor != 0: - list = getattr(t, d[target]).list( - screen_name=name, - cursor=next_cursor, - skip_status=True, - include_entities=False, - ) - for u in list['users']: - rel[u['name']] = '@' + u['screen_name'] - next_cursor = list['next_cursor'] - # Print out result - printNicely('All: ' + str(len(rel)) + ' people.') - for name in rel: - user = ' ' + cycle_color(name) + grey(' ' + rel[name] + ' ') - printNicely(user) - - def inbox(): """ - Inbox direct messages + Inbox threads """ t = Twitter(auth=authen()) num = c['MESSAGES_DISPLAY'] - rel = [] if g['stuff'].isdigit(): num = g['stuff'] + # Get inbox messages cur_page = 1 - # Max message per page is 20 so we have to loop + inbox = [] while num > 20: - rel = rel + t.direct_messages( + inbox = inbox + t.direct_messages( count=20, page=cur_page, include_entities=False, @@ -576,32 +504,20 @@ def inbox(): ) num -= 20 cur_page += 1 - rel = rel + t.direct_messages( + inbox = inbox + t.direct_messages( count=num, page=cur_page, include_entities=False, skip_status=False ) - # Display - printNicely('Inbox: newest ' + str(len(rel)) + ' messages.') - for m in reversed(rel): - print_message(m) - printNicely('') - - -def sent(): - """ - Sent direct messages - """ - t = Twitter(auth=authen()) + # Get sent messages num = c['MESSAGES_DISPLAY'] - rel = [] if g['stuff'].isdigit(): - num = int(g['stuff']) + num = g['stuff'] cur_page = 1 - # Max message per page is 20 so we have to loop + sent = [] while num > 20: - rel = rel + t.direct_messages.sent( + sent = sent + t.direct_messages.sent( count=20, page=cur_page, include_entities=False, @@ -609,49 +525,121 @@ def sent(): ) num -= 20 cur_page += 1 - rel = rel + t.direct_messages.sent( + sent = sent + t.direct_messages.sent( count=num, page=cur_page, include_entities=False, skip_status=False ) - # Display - printNicely('Sent: newest ' + str(len(rel)) + ' messages.') - for m in reversed(rel): - print_message(m) - printNicely('') + + d = {} + uniq_inbox = list(set( + [(m['sender_screen_name'], m['sender']['name']) for m in inbox] + )) + uniq_sent = list(set( + [(m['recipient_screen_name'], m['recipient']['name']) for m in sent] + )) + for partner in uniq_inbox: + inbox_ary = [m for m in inbox if m['sender_screen_name'] == partner[0]] + sent_ary = [ + m for m in sent if m['recipient_screen_name'] == partner[0]] + d[partner] = inbox_ary + sent_ary + for partner in uniq_sent: + if partner not in d: + d[partner] = [ + m for m in sent if m['recipient_screen_name'] == partner[0]] + g['message_threads'] = print_threads(d) -def trash(): +def thread(): """ - Remove message + View a thread of message """ - t = Twitter(auth=authen()) try: - rid = int(g['stuff'].split()[0]) - except: - printNicely(red('Sorry I can\'t understand.')) - mid = db.rainbow_to_message_query(rid)[0].message_id - t.direct_messages.destroy(id=mid) - printNicely(green('Message deleted.')) + thread_id = int(g['stuff']) + print_thread( + g['message_threads'][thread_id], + g['original_name'], + g['full_name']) + except Exception: + printNicely(red('No such thread.')) -def whois(): +def message(): """ - Show profile of a specific user + Send a direct message """ t = Twitter(auth=authen()) - screen_name = g['stuff'].split()[0] - if screen_name.startswith('@'): - try: - user = t.users.show( - screen_name=screen_name[1:], - include_entities=False) - show_profile(user, g['iot']) - except: - printNicely(red('Omg no user.')) - else: - printNicely(red('A name should begin with a \'@\'')) + try: + user = g['stuff'].split()[0] + if user[0].startswith('@'): + content = ' '.join(g['stuff'].split()[1:]) + t.direct_messages.new( + screen_name=user[1:], + text=content + ) + printNicely(green('Message sent.')) + else: + printNicely(red('A name should begin with a \'@\'')) + except: + printNicely(red('Sorry I can\'t understand.')) + + +def trash(): + """ + Remove message + """ + t = Twitter(auth=authen()) + try: + id = int(g['stuff'].split()[0]) + except: + printNicely(red('Sorry I can\'t understand.')) + mid = c['message_dict'][id] + t.direct_messages.destroy(id=mid) + printNicely(green('Message deleted.')) + + +def ls(): + """ + List friends for followers + """ + t = Twitter(auth=authen()) + # Get name + try: + name = g['stuff'].split()[1] + if name.startswith('@'): + name = name[1:] + else: + printNicely(red('A name should begin with a \'@\'')) + raise Exception('Invalid name') + except: + name = g['original_name'] + # Get list followers or friends + try: + target = g['stuff'].split()[0] + except: + printNicely(red('Omg some syntax is wrong.')) + # Init cursor + d = {'fl': 'followers', 'fr': 'friends'} + next_cursor = -1 + rel = {} + # Cursor loop + while next_cursor != 0: + list = getattr(t, d[target]).list( + screen_name=name, + cursor=next_cursor, + skip_status=True, + include_entities=False, + ) + for u in list['users']: + rel[u['name']] = '@' + u['screen_name'] + next_cursor = list['next_cursor'] + # Print out result + printNicely('All: ' + str(len(rel)) + ' ' + d[target] + '.') + for name in rel: + user = ' ' + cycle_color(name) + user += color_func(c['TWEET']['nick'])(' ' + rel[name] + ' ') + printNicely(user) def follow(): @@ -693,11 +681,16 @@ def mute(): printNicely(red('A name should be specified. ')) return if screen_name.startswith('@'): - rel = t.mutes.users.create(screen_name=screen_name[1:]) - if isinstance(rel, dict): - printNicely(green(screen_name + ' is muted.')) - else: - printNicely(red(rel)) + try: + rel = t.mutes.users.create(screen_name=screen_name[1:]) + if isinstance(rel, dict): + printNicely(green(screen_name + ' is muted.')) + c['IGNORE_LIST'] += [unc(screen_name)] + c['IGNORE_LIST'] = list(set(c['IGNORE_LIST'])) + else: + printNicely(red(rel)) + except: + printNicely(red('Something is wrong, can not mute now :(')) else: printNicely(red('A name should begin with a \'@\'')) @@ -713,11 +706,15 @@ def unmute(): printNicely(red('A name should be specified. ')) return if screen_name.startswith('@'): - rel = t.mutes.users.destroy(screen_name=screen_name[1:]) - if isinstance(rel, dict): - printNicely(green(screen_name + ' is unmuted.')) - else: - printNicely(red(rel)) + try: + rel = t.mutes.users.destroy(screen_name=screen_name[1:]) + if isinstance(rel, dict): + printNicely(green(screen_name + ' is unmuted.')) + c['IGNORE_LIST'].remove(screen_name) + else: + printNicely(red(rel)) + except: + printNicely(red('Maybe you are not muting this person ?')) else: printNicely(red('A name should begin with a \'@\'')) @@ -726,26 +723,15 @@ def muting(): """ List muting user """ - t = Twitter(auth=authen()) - # Init cursor - next_cursor = -1 - rel = {} - # Cursor loop - while next_cursor != 0: - list = t.mutes.users.list( - screen_name=g['original_name'], - cursor=next_cursor, - skip_status=True, - include_entities=False, - ) - for u in list['users']: - rel[u['name']] = '@' + u['screen_name'] - next_cursor = list['next_cursor'] - # Print out result - printNicely('All: ' + str(len(rel)) + ' people.') - for name in rel: - user = ' ' + cycle_color(name) + grey(' ' + rel[name] + ' ') + # Get dict of muting users + md = build_mute_dict(dict_data=True) + printNicely('All: ' + str(len(md)) + ' people.') + for name in md: + user = ' ' + cycle_color(md[name]) + user += color_func(c['TWEET']['nick'])(' ' + name + ' ') printNicely(user) + # Update from Twitter + c['IGNORE_LIST'] = [n for n in md] def block(): @@ -794,6 +780,336 @@ def report(): printNicely(red('Sorry I can\'t understand.')) +def get_slug(): + """ + Get Slug Decorator + """ + # Get list name + list_name = raw_input(light_magenta('Give me the list\'s name: ')) + # Get list name and owner + try: + owner, slug = list_name.split('/') + if slug.startswith('@'): + slug = slug[1:] + return owner, slug + except: + printNicely( + light_magenta('List name should follow "@owner/list_name" format.')) + raise Exception('Wrong list name') + + +def show_lists(t): + """ + List list + """ + rel = t.lists.list(screen_name=g['original_name']) + if rel: + print_list(rel) + else: + printNicely(light_magenta('You belong to no lists :)')) + + +def list_home(t): + """ + List home + """ + owner, slug = get_slug() + res = t.lists.statuses( + slug=slug, + owner_screen_name=owner, + count=c['LIST_MAX'], + include_entities=False) + for tweet in res: + draw(t=tweet) + printNicely('') + + +def list_members(t): + """ + List members + """ + owner, slug = get_slug() + # Get members + rel = {} + next_cursor = -1 + while next_cursor != 0: + m = t.lists.members( + slug=slug, + owner_screen_name=owner, + cursor=next_cursor, + include_entities=False) + for u in m['users']: + rel[u['name']] = '@' + u['screen_name'] + next_cursor = m['next_cursor'] + printNicely('All: ' + str(len(rel)) + ' members.') + for name in rel: + user = ' ' + cycle_color(name) + user += color_func(c['TWEET']['nick'])(' ' + rel[name] + ' ') + printNicely(user) + + +def list_subscribers(t): + """ + List subscribers + """ + owner, slug = get_slug() + # Get subscribers + rel = {} + next_cursor = -1 + while next_cursor != 0: + m = t.lists.subscribers( + slug=slug, + owner_screen_name=owner, + cursor=next_cursor, + include_entities=False) + for u in m['users']: + rel[u['name']] = '@' + u['screen_name'] + next_cursor = m['next_cursor'] + printNicely('All: ' + str(len(rel)) + ' subscribers.') + for name in rel: + user = ' ' + cycle_color(name) + user += color_func(c['TWEET']['nick'])(' ' + rel[name] + ' ') + printNicely(user) + + +def list_add(t): + """ + Add specific user to a list + """ + owner, slug = get_slug() + # Add + user_name = raw_input(light_magenta('Give me name of the newbie: ')) + if user_name.startswith('@'): + user_name = user_name[1:] + try: + t.lists.members.create( + slug=slug, + owner_screen_name=owner, + screen_name=user_name) + printNicely(green('Added.')) + except: + printNicely(light_magenta('I\'m sorry we can not add him/her.')) + + +def list_remove(t): + """ + Remove specific user from a list + """ + owner, slug = get_slug() + # Remove + user_name = raw_input(light_magenta('Give me name of the unlucky one: ')) + if user_name.startswith('@'): + user_name = user_name[1:] + try: + t.lists.members.destroy( + slug=slug, + owner_screen_name=owner, + screen_name=user_name) + printNicely(green('Gone.')) + except: + printNicely(light_magenta('I\'m sorry we can not remove him/her.')) + + +def list_subscribe(t): + """ + Subscribe to a list + """ + owner, slug = get_slug() + # Subscribe + try: + t.lists.subscribers.create( + slug=slug, + owner_screen_name=owner) + printNicely(green('Done.')) + except: + printNicely( + light_magenta('I\'m sorry you can not subscribe to this list.')) + + +def list_unsubscribe(t): + """ + Unsubscribe a list + """ + owner, slug = get_slug() + # Subscribe + try: + t.lists.subscribers.destroy( + slug=slug, + owner_screen_name=owner) + printNicely(green('Done.')) + except: + printNicely( + light_magenta('I\'m sorry you can not unsubscribe to this list.')) + + +def list_own(t): + """ + List own + """ + rel = [] + next_cursor = -1 + while next_cursor != 0: + res = t.lists.ownerships( + screen_name=g['original_name'], + cursor=next_cursor) + rel += res['lists'] + next_cursor = res['next_cursor'] + if rel: + print_list(rel) + else: + printNicely(light_magenta('You own no lists :)')) + + +def list_new(t): + """ + Create a new list + """ + name = raw_input(light_magenta('New list\'s name: ')) + mode = raw_input(light_magenta('New list\'s mode (public/private): ')) + description = raw_input(light_magenta('New list\'s description: ')) + try: + t.lists.create( + name=name, + mode=mode, + description=description) + printNicely(green(name + ' list is created.')) + except: + printNicely(red('Oops something is wrong with Twitter :(')) + + +def list_update(t): + """ + Update a list + """ + slug = raw_input(light_magenta('Your list that you want to update: ')) + name = raw_input(light_magenta('Update name (leave blank to unchange): ')) + mode = raw_input(light_magenta('Update mode (public/private): ')) + description = raw_input(light_magenta('Update description: ')) + try: + if name: + t.lists.update( + slug='-'.join(slug.split()), + owner_screen_name=g['original_name'], + name=name, + mode=mode, + description=description) + else: + t.lists.update( + slug=slug, + owner_screen_name=g['original_name'], + mode=mode, + description=description) + printNicely(green(slug + ' list is updated.')) + except: + printNicely(red('Oops something is wrong with Twitter :(')) + + +def list_delete(t): + """ + Delete a list + """ + slug = raw_input(light_magenta('Your list that you want to delete: ')) + try: + t.lists.destroy( + slug='-'.join(slug.split()), + owner_screen_name=g['original_name']) + printNicely(green(slug + ' list is deleted.')) + except: + printNicely(red('Oops something is wrong with Twitter :(')) + + +def twitterlist(): + """ + Twitter's list + """ + t = Twitter(auth=authen()) + # List all lists or base on action + try: + g['list_action'] = g['stuff'].split()[0] + except: + show_lists(t) + return + # Sub-function + action_ary = { + 'home': list_home, + 'all_mem': list_members, + 'all_sub': list_subscribers, + 'add': list_add, + 'rm': list_remove, + 'sub': list_subscribe, + 'unsub': list_unsubscribe, + 'own': list_own, + 'new': list_new, + 'update': list_update, + 'del': list_delete, + } + try: + return action_ary[g['list_action']](t) + except: + printNicely(red('Please try again.')) + + +def switch(): + """ + Switch stream + """ + try: + target = g['stuff'].split()[0] + # Filter and ignore + args = parse_arguments() + try: + if g['stuff'].split()[-1] == '-f': + guide = 'To ignore an option, just hit Enter key.' + printNicely(light_magenta(guide)) + only = raw_input('Only nicks [Ex: @xxx,@yy]: ') + ignore = raw_input('Ignore nicks [Ex: @xxx,@yy]: ') + args.filter = filter(None, only.split(',')) + args.ignore = filter(None, ignore.split(',')) + elif g['stuff'].split()[-1] == '-d': + args.filter = c['ONLY_LIST'] + args.ignore = c['IGNORE_LIST'] + except: + printNicely(red('Sorry, wrong format.')) + return + # Public stream + if target == 'public': + keyword = g['stuff'].split()[1] + if keyword[0] == '#': + keyword = keyword[1:] + # Kill old thread + g['stream_stop'] = True + args.track_keywords = keyword + # Start new thread + th = threading.Thread( + target=stream, + args=( + c['PUBLIC_DOMAIN'], + args)) + th.daemon = True + th.start() + # Personal stream + elif target == 'mine': + # Kill old thread + g['stream_stop'] = True + # Start new thread + th = threading.Thread( + target=stream, + args=( + c['USER_DOMAIN'], + args, + g['original_name'])) + th.daemon = True + th.start() + printNicely('') + if args.filter: + printNicely(cyan('Only: ' + str(args.filter))) + if args.ignore: + printNicely(red('Ignore: ' + str(args.ignore))) + printNicely('') + except: + printNicely(red('Sorry I can\'t understand.')) + + def cal(): """ Unix's command `cal` @@ -812,81 +1128,97 @@ def theme(): if not g['stuff']: # List themes for theme in g['themes']: - line = '' - # Detect custom config - if theme == 'custom': - line += light_magenta('custom') - custom_path = os.environ.get( - 'HOME', - os.environ.get('USERPROFILE', - '')) + os.sep + '.rainbow_config.json' - if not os.path.exists(custom_path): - line += light_magenta(' (create your own config file at ~/.rainbow_config.json)') - else: - line += light_magenta(' (loaded)') - else: - line += light_magenta(theme) - if c['theme'] == theme : - line = ' '*2 + light_yellow('* ') + line + line = light_magenta(theme) + if c['THEME'] == theme: + line = ' ' * 2 + light_yellow('* ') + line else: - line = ' '*4 + line + line = ' ' * 4 + line printNicely(line) - elif g['stuff'] == 'current_as_default': - # Set default - path = os.path.dirname(__file__) + '/colorset/init' - f = open(path,'w') - f.write(c['theme']) - f.close() - printNicely(light_green('Okay it will be applied from next time :)')) else: # Change theme try: - # Load new config - if g['stuff'] != 'custom': - new_config = os.path.dirname(__file__) + '/colorset/' + g['stuff'] + '.json' - else: - new_config = os.environ.get( - 'HOME',os.environ.get( - 'USERPROFILE', - '')) + os.sep + '.rainbow_config.json' - new_config = load_config(new_config) - if new_config: - for nc in new_config: - c[nc] = new_config[nc] - # Update db and reset colors - db.theme_update(g['stuff']) - c['theme'] = g['stuff'] - reset_cycle() - g['decorated_name'] = color_func( + # Load new theme + c['THEME'] = reload_theme(g['stuff'], c['THEME']) + # Redefine decorated_name + g['decorated_name'] = lambda x: color_func( c['DECORATED_NAME'])( - '[@' + g['original_name'] + ']: ') + '[' + x + ']: ') printNicely(green('Theme changed.')) except: - if g['stuff'] == 'custom': - printNicely(red('~/.rainbow_config.json is not exists!')) - else: - printNicely(red('No such theme exists.')) + printNicely(red('No such theme exists.')) -def help(): +def config(): """ - Help + Browse and change config """ - s = ' ' * 2 - h, w = os.popen('stty size', 'r').read().split() + all_config = get_all_config() + g['stuff'] = g['stuff'].strip() + # List all config + if not g['stuff']: + for k in all_config: + line = ' ' * 2 + \ + green(k) + ': ' + light_yellow(str(all_config[k])) + printNicely(line) + guide = 'Detailed explanation can be found at ' + \ + color_func(c['TWEET']['link'])( + 'http://rainbowstream.readthedocs.org/en/latest/#config-explanation') + printNicely(guide) + # Print specific config + elif len(g['stuff'].split()) == 1: + if g['stuff'] in all_config: + k = g['stuff'] + line = ' ' * 2 + \ + green(k) + ': ' + light_yellow(str(all_config[k])) + printNicely(line) + else: + printNicely(red('No such config key.')) + # Print specific config's default value + elif len(g['stuff'].split()) == 2 and g['stuff'].split()[-1] == 'default': + key = g['stuff'].split()[0] + try: + value = get_default_config(key) + line = ' ' * 2 + green(key) + ': ' + light_magenta(value) + printNicely(line) + except Exception as e: + printNicely(red(e)) + # Delete specific config key in config file + elif len(g['stuff'].split()) == 2 and g['stuff'].split()[-1] == 'drop': + key = g['stuff'].split()[0] + try: + delete_config(key) + printNicely(green('Config key is dropped.')) + except Exception as e: + printNicely(red(e)) + # Set specific config + elif len(g['stuff'].split()) == 3 and g['stuff'].split()[1] == '=': + key = g['stuff'].split()[0] + value = g['stuff'].split()[-1] + if key == 'THEME' and not validate_theme(value): + printNicely(red('Invalid theme\'s value.')) + return + try: + set_config(key, value) + # Apply theme immediately + if key == 'THEME': + c['THEME'] = reload_theme(value, c['THEME']) + g['decorated_name'] = lambda x: color_func( + c['DECORATED_NAME'])('[' + x + ']: ') + reload_config() + printNicely(green('Updated successfully.')) + except Exception as e: + printNicely(red(e)) + else: + printNicely(light_magenta('Sorry I can\'s understand.')) - # Start - usage = '\n' - usage += s + 'Hi boss! I\'m ready to serve you right now!\n' - usage += s + '-' * (int(w) - 4) + '\n' - usage += s + 'You are ' + \ - light_yellow('already') + ' on your personal stream.\n' - usage += s + 'Any update from Twitter will show up ' + \ - light_yellow('immediately') + '.\n' - usage += s + 'In addtion, following commands are available right now:\n' +def help_discover(): + """ + Discover the world + """ + s = ' ' * 2 # Discover the world - usage += '\n' + usage = '\n' usage += s + grey(u'\u266A' + ' Discover the world \n') usage += s * 2 + light_green('trend') + ' will show global trending topics. ' + \ 'You can try ' + light_green('trend US') + ' or ' + \ @@ -899,11 +1231,19 @@ def help(): magenta('@mdo') + '.\n' usage += s * 2 + light_green('view @mdo') + \ ' will show ' + magenta('@mdo') + '\'s home.\n' - usage += s * 2 + light_green('s #AKB48') + ' will search for "' + \ - light_yellow('AKB48') + '" and return 5 newest tweet.\n' + usage += s * 2 + light_green('s AKB48') + ' will search for "' + \ + light_yellow('AKB48') + '" and return 5 newest tweet. ' + \ + 'Search can be performed with or without hashtag.\n' + printNicely(usage) + +def help_tweets(): + """ + Tweets + """ + s = ' ' * 2 # Tweet - usage += '\n' + usage = '\n' usage += s + grey(u'\u266A' + ' Tweets \n') usage += s * 2 + light_green('t oops ') + \ 'will tweet "' + light_yellow('oops') + '" immediately.\n' @@ -917,6 +1257,8 @@ def help(): usage += s * 2 + \ light_green('allrt 12 20 ') + ' will list 20 newest retweet of the tweet with ' + \ light_yellow('[id=12]') + '.\n' + usage += s * 2 + light_green('conversation 12') + ' will show the chain of ' + \ + 'replies prior to the tweet with ' + light_yellow('[id=12]') + '.\n' usage += s * 2 + light_green('rep 12 oops') + ' will reply "' + \ light_yellow('oops') + '" to tweet with ' + \ light_yellow('[id=12]') + '.\n' @@ -933,22 +1275,36 @@ def help(): light_yellow('[id=12]') + ' in your OS\'s image viewer.\n' usage += s * 2 + light_green('open 12') + ' will open url in tweet with ' + \ light_yellow('[id=12]') + ' in your OS\'s default browser.\n' + printNicely(usage) + +def help_messages(): + """ + Messages + """ + s = ' ' * 2 # Direct message - usage += '\n' + usage = '\n' usage += s + grey(u'\u266A' + ' Direct messages \n') usage += s * 2 + light_green('inbox') + ' will show inbox messages. ' + \ light_green('inbox 7') + ' will show newest 7 messages.\n' - usage += s * 2 + light_green('sent') + ' will show sent messages. ' + \ - light_green('sent 7') + ' will show newest 7 messages.\n' + usage += s * 2 + light_green('thread 2') + ' will show full thread with ' + \ + light_yellow('[thread_id=2]') + '.\n' usage += s * 2 + light_green('mes @dtvd88 hi') + ' will send a "hi" messege to ' + \ magenta('@dtvd88') + '.\n' usage += s * 2 + light_green('trash 5') + ' will remove message with ' + \ light_yellow('[message_id=5]') + '.\n' + printNicely(usage) + +def help_friends_and_followers(): + """ + Friends and Followers + """ + s = ' ' * 2 # Follower and following - usage += '\n' - usage += s + grey(u'\u266A' + ' Fiends and followers \n') + usage = '\n' + usage += s + grey(u'\u266A' + ' Friends and followers \n') usage += s * 2 + \ light_green('ls fl') + \ ' will list all followers (people who are following you).\n' @@ -970,9 +1326,53 @@ def help(): magenta('@dtvd88') + '.\n' usage += s * 2 + light_green('report @dtvd88') + ' will report ' + \ magenta('@dtvd88') + ' as a spam account.\n' + printNicely(usage) + + +def help_list(): + """ + Lists + """ + s = ' ' * 2 + # Twitter list + usage = '\n' + usage += s + grey(u'\u266A' + ' Twitter list\n') + usage += s * 2 + light_green('list') + \ + ' will show all lists you are belong to.\n' + usage += s * 2 + light_green('list home') + \ + ' will show timeline of list. You will be asked for list\'s name.\n' + usage += s * 2 + light_green('list all_mem') + \ + ' will show list\'s all members.\n' + usage += s * 2 + light_green('list all_sub') + \ + ' will show list\'s all subscribers.\n' + usage += s * 2 + light_green('list add') + \ + ' will add specific person to a list owned by you.' + \ + ' You will be asked for list\'s name and person\'s name.\n' + usage += s * 2 + light_green('list rm') + \ + ' will remove specific person from a list owned by you.' + \ + ' You will be asked for list\'s name and person\'s name.\n' + usage += s * 2 + light_green('list sub') + \ + ' will subscribe you to a specific list.\n' + usage += s * 2 + light_green('list unsub') + \ + ' will unsubscribe you from a specific list.\n' + usage += s * 2 + light_green('list own') + \ + ' will show all list owned by you.\n' + usage += s * 2 + light_green('list new') + \ + ' will create a new list.\n' + usage += s * 2 + light_green('list update') + \ + ' will update a list owned by you.\n' + usage += s * 2 + light_green('list del') + \ + ' will delete a list owned by you.\n' + printNicely(usage) + +def help_stream(): + """ + Stream switch + """ + s = ' ' * 2 # Switch - usage += '\n' + usage = '\n' usage += s + grey(u'\u266A' + ' Switching streams \n') usage += s * 2 + light_green('switch public #AKB') + \ ' will switch to public stream and follow "' + \ @@ -987,7 +1387,39 @@ def help(): ' filter will decide nicks will be EXCLUDE.\n' usage += s * 2 + light_green('switch mine -d') + \ ' will use the config\'s ONLY_LIST and IGNORE_LIST.\n' + printNicely(usage) + +def help(): + """ + Help + """ + s = ' ' * 2 + h, w = os.popen('stty size', 'r').read().split() + # Start + usage = '\n' + usage += s + 'Hi boss! I\'m ready to serve you right now!\n' + usage += s + '-' * (int(w) - 4) + '\n' + usage += s + 'You are ' + \ + light_yellow('already') + ' on your personal stream.\n' + usage += s + 'Any update from Twitter will show up ' + \ + light_yellow('immediately') + '.\n' + usage += s + 'In addition, following commands are available right now:\n' + # Twitter help section + usage += '\n' + usage += s + grey(u'\u266A' + ' Twitter help\n') + usage += s * 2 + light_green('h discover') + \ + ' will show help for discover commands.\n' + usage += s * 2 + light_green('h tweets') + \ + ' will show help for tweets commands.\n' + usage += s * 2 + light_green('h messages') + \ + ' will show help for messages commands.\n' + usage += s * 2 + light_green('h friends_and_followers') + \ + ' will show help for friends and followers commands.\n' + usage += s * 2 + light_green('h list') + \ + ' will show help for list commands.\n' + usage += s * 2 + light_green('h stream') + \ + ' will show help for stream commands.\n' # Smart shell usage += '\n' usage += s + grey(u'\u266A' + ' Smart shell\n') @@ -995,22 +1427,70 @@ def help(): 'will be evaluate by Python interpreter.\n' usage += s * 2 + 'Even ' + light_green('cal') + ' will show the calendar' + \ ' for current month.\n' - - # Screening + # Config usage += '\n' - usage += s + grey(u'\u266A' + ' Screening \n') - usage += s * 2 + light_green('theme') + ' will list available theme.' + \ + usage += s + grey(u'\u266A' + ' Config \n') + usage += s * 2 + light_green('theme') + ' will list available theme. ' + \ light_green('theme monokai') + ' will apply ' + light_yellow('monokai') + \ ' theme immediately.\n' + usage += s * 2 + light_green('config') + ' will list all config.\n' + usage += s * 3 + \ + light_green('config ASCII_ART') + ' will output current value of ' +\ + light_yellow('ASCII_ART') + ' config key.\n' + usage += s * 3 + \ + light_green('config TREND_MAX default') + ' will output default value of ' + \ + light_yellow('TREND_MAX') + ' config key.\n' + usage += s * 3 + \ + light_green('config CUSTOM_CONFIG drop') + ' will drop ' + \ + light_yellow('CUSTOM_CONFIG') + ' config key.\n' + usage += s * 3 + \ + light_green('config IMAGE_ON_TERM = true') + ' will set value of ' + \ + light_yellow('IMAGE_ON_TERM') + ' config key to ' + \ + light_yellow('True') + '.\n' + # Screening + usage += '\n' + usage += s + grey(u'\u266A' + ' Screening \n') usage += s * 2 + light_green('h') + ' will show this help again.\n' + usage += s * 2 + light_green('p') + ' will pause the stream.\n' + usage += s * 2 + light_green('r') + ' will unpause the stream.\n' usage += s * 2 + light_green('c') + ' will clear the screen.\n' usage += s * 2 + light_green('q') + ' will quit.\n' - # End usage += '\n' usage += s + '-' * (int(w) - 4) + '\n' usage += s + 'Have fun and hang tight! \n' - printNicely(usage) + # Show help + d = { + 'discover': help_discover, + 'tweets': help_tweets, + 'messages': help_messages, + 'friends_and_followers': help_friends_and_followers, + 'list': help_list, + 'stream': help_stream, + } + if g['stuff']: + d.get( + g['stuff'].strip(), + lambda: printNicely(red('No such command.')) + )() + else: + printNicely(usage) + + +def pause(): + """ + Pause stream display + """ + g['pause'] = True + printNicely(green('Stream is paused')) + + +def replay(): + """ + Replay stream + """ + g['pause'] = False + printNicely(green('Stream is running back now')) def clear(): @@ -1024,9 +1504,11 @@ def quit(): """ Exit all """ - save_history() - os.system('rm -rf rainbow.db') - os.kill(g['stream_pid'], signal.SIGKILL) + try: + save_history() + printNicely(green('See you next time :)')) + except: + pass sys.exit() @@ -1035,6 +1517,10 @@ def reset(): Reset prefix of line """ if g['reset']: + if c.get('USER_JSON_ERROR'): + printNicely(red('Your ~/.rainbow_config.json is messed up:')) + printNicely(red('>>> ' + c['USER_JSON_ERROR'])) + printNicely('') printNicely(magenta('Need tips ? Type "h" and hit Enter key!')) g['reset'] = False try: @@ -1043,50 +1529,100 @@ def reset(): pass +# Command set +cmdset = [ + 'switch', + 'trend', + 'home', + 'view', + 'mentions', + 't', + 'rt', + 'quote', + 'allrt', + 'conversation', + 'fav', + 'rep', + 'del', + 'ufav', + 's', + 'mes', + 'show', + 'open', + 'ls', + 'inbox', + 'thread', + 'trash', + 'whois', + 'fl', + 'ufl', + 'mute', + 'unmute', + 'muting', + 'block', + 'unblock', + 'report', + 'list', + 'cal', + 'config', + 'theme', + 'h', + 'p', + 'r', + 'c', + 'q' +] + +# Handle function set +funcset = [ + switch, + trend, + home, + view, + mentions, + tweet, + retweet, + quote, + allretweet, + conversation, + favorite, + reply, + delete, + unfavorite, + search, + message, + show, + urlopen, + ls, + inbox, + thread, + trash, + whois, + follow, + unfollow, + mute, + unmute, + muting, + block, + unblock, + report, + twitterlist, + cal, + config, + theme, + help, + pause, + replay, + clear, + quit +] + + def process(cmd): """ Process switch """ - return dict(zip( - cmdset, - [ - switch, - trend, - home, - view, - mentions, - tweet, - retweet, - quote, - allretweet, - favorite, - reply, - delete, - unfavorite, - search, - message, - show, - urlopen, - list, - inbox, - sent, - trash, - whois, - follow, - unfollow, - mute, - unmute, - muting, - block, - unblock, - report, - cal, - theme, - help, - clear, - quit - ] - )).get(cmd, reset) + return dict(zip(cmdset, funcset)).get(cmd, reset) def listen(): @@ -1105,6 +1641,7 @@ def listen(): [], # retweet [], # quote [], # allretweet + [], # conversation [], # favorite [], # reply [], # delete @@ -1115,7 +1652,7 @@ def listen(): [''], # open url ['fl', 'fr'], # list [], # inbox - [], # sent + [i for i in g['message_threads']], # sent [], # trash ['@'], # whois ['@'], # follow @@ -1126,9 +1663,32 @@ def listen(): ['@'], # block ['@'], # unblock ['@'], # report + [ + 'home', + 'all_mem', + 'all_sub', + 'add', + 'rm', + 'sub', + 'unsub', + 'own', + 'new', + 'update', + 'del' + ], # list [], # cal - g['themes'] + ['current_as_default'], # theme - [], # help + [key for key in dict(get_all_config())], # config + g['themes'], # theme + [ + 'discover', + 'tweets', + 'messages', + 'friends_and_followers', + 'list', + 'stream' + ], # help + [], # pause + [], # reconnect [], # clear [], # quit ] @@ -1137,60 +1697,64 @@ def listen(): read_history() reset() while True: - if g['prefix']: - line = raw_input(g['decorated_name']) - else: - line = raw_input() - try: - cmd = line.split()[0] - except: - cmd = '' - g['cmd'] = cmd - # Save cmd to global variable and call process try: + # raw_input + if g['prefix']: + line = raw_input(g['decorated_name'](c['PREFIX'])) + else: + line = raw_input() + # Save cmd to compare with readline buffer + g['cmd'] = line.strip() + # Get short cmd to pass to handle function + try: + cmd = line.split()[0] + except: + cmd = '' + # Lock the semaphore + c['lock'] = True + # Save cmd to global variable and call process g['stuff'] = ' '.join(line.split()[1:]) + # Process the command process(cmd)() - except Exception,e : - print e + # Not re-display + if cmd in ['switch', 't', 'rt', 'rep']: + g['prefix'] = False + else: + g['prefix'] = True + # Release the semaphore lock + c['lock'] = False + except EOFError: + printNicely('') + except Exception: printNicely(red('OMG something is wrong with Twitter right now.')) - # Not redisplay prefix - if cmd in ['switch', 't', 'rt', 'rep']: - g['prefix'] = False - else: - g['prefix'] = True def stream(domain, args, name='Rainbow Stream'): """ Track the stream """ - # The Logo art_dict = { c['USER_DOMAIN']: name, c['PUBLIC_DOMAIN']: args.track_keywords, - c['SITE_DOMAIN']: 'Site Stream', + c['SITE_DOMAIN']: name, } - if g['ascii_art']: + if c['ASCII_ART']: ascii_art(art_dict[domain]) - # These arguments are optional: stream_args = dict( - timeout=args.timeout, - block=not args.no_block, - heartbeat_timeout=args.heartbeat_timeout) - + timeout=0.5, # To check g['stream_stop'] after each 0.5 s + block=True, + heartbeat_timeout=c['HEARTBEAT_TIMEOUT'] * 60) # Track keyword query_args = dict() if args.track_keywords: query_args['track'] = args.track_keywords - # Get stream stream = TwitterStream( auth=authen(), domain=domain, **stream_args) - try: if domain == c['USER_DOMAIN']: tweet_iter = stream.user(**query_args) @@ -1201,25 +1765,65 @@ def stream(domain, args, name='Rainbow Stream'): tweet_iter = stream.statuses.filter(**query_args) else: tweet_iter = stream.statuses.sample() - - # Iterate over the stream. + # Block new stream until other one exits + StreamLock.acquire() + g['stream_stop'] = False for tweet in tweet_iter: if tweet is None: printNicely("-- None --") elif tweet is Timeout: - printNicely("-- Timeout --") + if(g['stream_stop']): + StreamLock.release() + break elif tweet is HeartbeatTimeout: printNicely("-- Heartbeat Timeout --") + guide = light_magenta("You can use ") + \ + light_green("switch") + \ + light_magenta(" command to return to your stream.\n") + guide += light_magenta("Type ") + \ + light_green("h stream") + \ + light_magenta(" for more details.") + printNicely(guide) + sys.stdout.write(g['decorated_name'](c['PREFIX'])) + sys.stdout.flush() + StreamLock.release() + break elif tweet is Hangup: printNicely("-- Hangup --") elif tweet.get('text'): + # Check the semaphore pause and lock (stream process only) + if g['pause']: + continue + while c['lock']: + time.sleep(0.5) + # Draw the tweet draw( t=tweet, - iot=args.image_on_term, keyword=args.track_keywords, + humanize=False, fil=args.filter, ig=args.ignore, ) + # Current readline buffer + current_buffer = readline.get_line_buffer().strip() + # There is an unexpected behaviour in MacOSX readline + Python 2: + # after completely delete a word after typing it, + # somehow readline buffer still contains + # the 1st character of that word + if current_buffer and g['cmd'] != current_buffer: + sys.stdout.write( + g['decorated_name'](c['PREFIX']) + str2u(current_buffer)) + sys.stdout.flush() + elif not c['HIDE_PROMPT']: + sys.stdout.write(g['decorated_name'](c['PREFIX'])) + sys.stdout.flush() + elif tweet.get('direct_message'): + # Check the semaphore pause and lock (stream process only) + if g['pause']: + continue + while c['lock']: + time.sleep(0.5) + print_message(tweet['direct_message']) except TwitterHTTPError: printNicely('') printNicely( @@ -1230,32 +1834,28 @@ def fly(): """ Main function """ - # Spawn stream process + # Initial args = parse_arguments() try: - get_decorated_name() - + init(args) except TwitterHTTPError: printNicely('') printNicely( - magenta("I'm afraid we have maximum connection problem with twitter right now :(")) + magenta("We have connection problem with twitter'stream API right now :(")) printNicely(magenta("Let's try again later.")) save_history() - os.system('rm -rf rainbow.db') sys.exit() - - p = Process( + # Spawn stream thread + th = threading.Thread( target=stream, args=( - c['USER_DOMAIN'], + c['USER_DOMAIN'], args, g['original_name'])) - p.start() - + th.daemon = True + th.start() # Start listen process time.sleep(0.5) g['reset'] = True g['prefix'] = True - g['stream_pid'] = p.pid - g['iot'] = args.image_on_term listen()