X-Git-Url: https://vcs.fsf.org/?p=rainbowstream.git;a=blobdiff_plain;f=rainbowstream%2Frainbow.py;h=500c2dba3244f86b9dbdbcebf7113b19233d49a7;hp=94b2e4b67f2454acd5d1cafefa90a43f7f18961f;hb=47cee7034e3f8b4b5b0cf841b69b54e5b623e5e4;hpb=4dc385b53c1e928a0c9441e9393134e18034d522 diff --git a/rainbowstream/rainbow.py b/rainbowstream/rainbow.py index 94b2e4b..500c2db 100644 --- a/rainbowstream/rainbow.py +++ b/rainbowstream/rainbow.py @@ -7,6 +7,8 @@ import time import threading import requests import webbrowser +import traceback +import pkg_resources from twitter.stream import TwitterStream, Timeout, HeartbeatTimeout, Hangup from twitter.api import * @@ -21,6 +23,7 @@ from .consumer import * from .interactive import * from .c_image import * from .py3patch import * +from .emoji import * # Global values g = {} @@ -108,6 +111,35 @@ def build_mute_dict(dict_data=False): return screen_name_list +def debug_option(): + """ + Save traceback when run in debug mode + """ + if g['debug']: + g['traceback'].append(traceback.format_exc()) + + +def upgrade_center(): + """ + Check latest and notify to upgrade + """ + try: + current = pkg_resources.get_distribution("rainbowstream").version + url = 'https://raw.githubusercontent.com/DTVD/rainbowstream/master/setup.py' + readme = requests.get(url).text + latest = readme.split("version = \'")[1].split("\'")[0] + if current != latest: + notice = light_magenta('RainbowStream latest version is ') + notice += light_green(latest) + notice += light_magenta(' while your current version is ') + notice += light_yellow(current) + '\n' + notice += light_magenta('You should upgrade with ') + notice += light_green('pip install -U rainbowstream') + printNicely(notice) + except: + pass + + def init(args): """ Init function @@ -115,21 +147,34 @@ def init(args): # Handle Ctrl C ctrl_c_handler = lambda signum, frame: quit() signal.signal(signal.SIGINT, ctrl_c_handler) + # Upgrade notify + upgrade_center() # Get name t = Twitter(auth=authen()) - name = '@' + t.account.verify_credentials()['screen_name'] + credential = t.account.verify_credentials() + screen_name = '@' + credential['screen_name'] + name = credential['name'] if not get_config('PREFIX'): - set_config('PREFIX', name) - g['original_name'] = name[1:] + set_config('PREFIX', screen_name) + c['PREFIX'] = emojize(c['PREFIX']) + g['PREFIX'] = u2str(c['PREFIX']) + c['original_name'] = g['original_name'] = screen_name[1:] + g['full_name'] = name g['decorated_name'] = lambda x: color_func( - c['DECORATED_NAME'])('[' + x + ']: ') + c['DECORATED_NAME'])('[' + x + ']: ', rl=True) # 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'] = '' + # Debug option default = True + g['debug'] = True + g['traceback'] = [] + # Events + c['events'] = [] # Semaphore init c['lock'] = False # Init tweet dict and message dict @@ -191,6 +236,18 @@ def home(): printNicely('') +def notification(): + """ + Show notifications + """ + if c['events']: + for e in c['events']: + print_event(e) + printNicely('') + else: + printNicely(magenta('Nothing at this time.')) + + def mentions(): """ Mentions timeline @@ -217,7 +274,8 @@ def whois(): include_entities=False) show_profile(user) except: - printNicely(red('Omg no user.')) + debug_option() + printNicely(red('No user.')) else: printNicely(red('A name should begin with a \'@\'')) @@ -233,7 +291,8 @@ def view(): num = int(g['stuff'].split()[1]) except: num = c['HOME_TWEET_NUM'] - for tweet in reversed(t.statuses.user_timeline(count=num, screen_name=user[1:])): + for tweet in reversed( + t.statuses.user_timeline(count=num, screen_name=user[1:])): draw(t=tweet) printNicely('') else: @@ -245,13 +304,24 @@ def search(): Search """ t = Twitter(auth=authen()) - g['stuff'] = g['stuff'].strip() - rel = t.search.tweets(q=g['stuff'])['statuses'] + # 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(c['SEARCH_MAX_RECORD'])): - draw(t=rel[i], - keyword=g['stuff']) + for i in reversed(xrange(count)): + draw(t=rel[i], keyword=query) printNicely('') else: printNicely(magenta('I\'m afraid there is no result')) @@ -297,7 +367,8 @@ def quote(): if not formater: return # Get comment - prefix = light_magenta('Compose your ') + light_green('#comment: ') + prefix = light_magenta('Compose your ', rl=True) + \ + light_green('#comment: ', rl=True) comment = raw_input(prefix) if comment: quote = comment.join(formater.split('#comment')) @@ -444,6 +515,7 @@ def show(): img = Image.open(BytesIO(res.content)) img.show() except: + debug_option() printNicely(red('Sorry I can\'t show this image.')) @@ -457,30 +529,32 @@ def urlopen(): return 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 for link in link_ary: webbrowser.open(link) except: + debug_option() printNicely(red('Sorry I can\'t open url in this tweet.')) 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, @@ -488,32 +562,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, @@ -521,17 +583,45 @@ 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 thread(): + """ + View a thread of message + """ + try: + thread_id = int(g['stuff']) + print_thread( + g['message_threads'][thread_id], + g['original_name'], + g['full_name']) + except Exception: + debug_option() + printNicely(red('No such thread.')) def message(): @@ -539,19 +629,20 @@ def message(): Send a direct message """ 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: + 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: + debug_option() + printNicely(red('Sorry I can\'t understand.')) def trash(): @@ -659,6 +750,7 @@ def mute(): else: printNicely(red(rel)) except: + debug_option() printNicely(red('Something is wrong, can not mute now :(')) else: printNicely(red('A name should begin with a \'@\'')) @@ -751,10 +843,11 @@ def report(): def get_slug(): """ - Get Slug Decorator + Get slug """ # Get list name - list_name = raw_input(light_magenta('Give me the list\'s name: ')) + list_name = raw_input( + light_magenta('Give me the list\'s name ("@owner/list_name"): ', rl=True)) # Get list name and owner try: owner, slug = list_name.split('/') @@ -847,7 +940,10 @@ def list_add(t): """ owner, slug = get_slug() # Add - user_name = raw_input(light_magenta('Give me name of the newbie: ')) + user_name = raw_input( + light_magenta( + 'Give me name of the newbie: ', + rl=True)) if user_name.startswith('@'): user_name = user_name[1:] try: @@ -857,6 +953,7 @@ def list_add(t): screen_name=user_name) printNicely(green('Added.')) except: + debug_option() printNicely(light_magenta('I\'m sorry we can not add him/her.')) @@ -866,7 +963,10 @@ def list_remove(t): """ owner, slug = get_slug() # Remove - user_name = raw_input(light_magenta('Give me name of the unlucky one: ')) + user_name = raw_input( + light_magenta( + 'Give me name of the unlucky one: ', + rl=True)) if user_name.startswith('@'): user_name = user_name[1:] try: @@ -876,6 +976,7 @@ def list_remove(t): screen_name=user_name) printNicely(green('Gone.')) except: + debug_option() printNicely(light_magenta('I\'m sorry we can not remove him/her.')) @@ -891,6 +992,7 @@ def list_subscribe(t): owner_screen_name=owner) printNicely(green('Done.')) except: + debug_option() printNicely( light_magenta('I\'m sorry you can not subscribe to this list.')) @@ -907,6 +1009,7 @@ def list_unsubscribe(t): owner_screen_name=owner) printNicely(green('Done.')) except: + debug_option() printNicely( light_magenta('I\'m sorry you can not unsubscribe to this list.')) @@ -933,9 +1036,15 @@ 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: ')) + name = raw_input(light_magenta('New list\'s name: ', rl=True)) + mode = raw_input( + light_magenta( + 'New list\'s mode (public/private): ', + rl=True)) + description = raw_input( + light_magenta( + 'New list\'s description: ', + rl=True)) try: t.lists.create( name=name, @@ -943,6 +1052,7 @@ def list_new(t): description=description) printNicely(green(name + ' list is created.')) except: + debug_option() printNicely(red('Oops something is wrong with Twitter :(')) @@ -950,10 +1060,16 @@ 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: ')) + slug = raw_input( + light_magenta( + 'Your list that you want to update: ', + rl=True)) + name = raw_input( + light_magenta( + 'Update name (leave blank to unchange): ', + rl=True)) + mode = raw_input(light_magenta('Update mode (public/private): ', rl=True)) + description = raw_input(light_magenta('Update description: ', rl=True)) try: if name: t.lists.update( @@ -970,6 +1086,7 @@ def list_update(t): description=description) printNicely(green(slug + ' list is updated.')) except: + debug_option() printNicely(red('Oops something is wrong with Twitter :(')) @@ -977,13 +1094,17 @@ def list_delete(t): """ Delete a list """ - slug = raw_input(light_magenta('Your list that you want to delete: ')) + slug = raw_input( + light_magenta( + 'Your list that you want to delete: ', + rl=True)) try: t.lists.destroy( slug='-'.join(slug.split()), owner_screen_name=g['original_name']) printNicely(green(slug + ' list is deleted.')) except: + debug_option() printNicely(red('Oops something is wrong with Twitter :(')) @@ -1069,13 +1190,47 @@ def switch(): g['original_name'])) th.daemon = True th.start() + # Stream base on list + elif target == 'list': + owner, slug = get_slug() + # Force python 2 not redraw readline buffer + g['cmd'] = '/'.join([owner, slug]) + printNicely(light_yellow('getting list members ...')) + # Get members + t = Twitter(auth=authen()) + members = [] + 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']: + members.append('@' + u['screen_name']) + next_cursor = m['next_cursor'] + printNicely(light_yellow('... done.')) + # Build thread filter array + args.filter = members + # Kill old thread + g['stream_stop'] = True + # Start new thread + th = threading.Thread( + target=stream, + args=( + c['USER_DOMAIN'], + args, + slug)) + th.daemon = True + th.start() printNicely('') if args.filter: - printNicely(cyan('Only: ' + str(args.filter))) + printNicely(cyan('Include: ' + str(len(args.filter)) + ' people.')) if args.ignore: - printNicely(red('Ignore: ' + str(args.ignore))) + printNicely(red('Ignore: ' + str(len(args.ignore)) + ' people.')) printNicely('') - except: + except Exception: + debug_option() printNicely(red('Sorry I can\'t understand.')) @@ -1194,6 +1349,8 @@ def help_discover(): light_green('trend JP Tokyo') + '.\n' usage += s * 2 + light_green('home') + ' will show your timeline. ' + \ light_green('home 7') + ' will show 7 tweets.\n' + usage += s * 2 + \ + light_green('notification') + ' will show your recent notification.\n' usage += s * 2 + light_green('mentions') + ' will show mentions timeline. ' + \ light_green('mentions 7') + ' will show 7 mention tweets.\n' usage += s * 2 + light_green('whois @mdo') + ' will show profile of ' + \ @@ -1257,8 +1414,8 @@ def help_messages(): 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 ' + \ @@ -1356,6 +1513,8 @@ def help_stream(): ' 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' + usage += s * 2 + light_green('switch list') + \ + ' will switch to a Twitter list\'s stream. You will be asked for list name\n' printNicely(usage) @@ -1503,6 +1662,7 @@ cmdset = [ 'switch', 'trend', 'home', + 'notification', 'view', 'mentions', 't', @@ -1520,7 +1680,7 @@ cmdset = [ 'open', 'ls', 'inbox', - 'sent', + 'thread', 'trash', 'whois', 'fl', @@ -1547,6 +1707,7 @@ funcset = [ switch, trend, home, + notification, view, mentions, tweet, @@ -1564,7 +1725,7 @@ funcset = [ urlopen, ls, inbox, - sent, + thread, trash, whois, follow, @@ -1601,9 +1762,10 @@ def listen(): d = dict(zip( cmdset, [ - ['public', 'mine'], # switch + ['public', 'mine', 'list'], # switch [], # trend [], # home + [], # notification ['@'], # view [], # mentions [], # tweet @@ -1621,7 +1783,7 @@ def listen(): [''], # open url ['fl', 'fr'], # list [], # inbox - [], # sent + [i for i in g['message_threads']], # sent [], # trash ['@'], # whois ['@'], # follow @@ -1669,7 +1831,8 @@ def listen(): try: # raw_input if g['prefix']: - line = raw_input(g['decorated_name'](c['PREFIX'])) + # Only use PREFIX as a string with raw_input + line = raw_input(g['decorated_name'](g['PREFIX'])) else: line = raw_input() # Save cmd to compare with readline buffer @@ -1695,9 +1858,25 @@ def listen(): except EOFError: printNicely('') except Exception: + debug_option() printNicely(red('OMG something is wrong with Twitter right now.')) +def reconn_notice(): + """ + Notice when Hangup or 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() + + def stream(domain, args, name='Rainbow Stream'): """ Track the stream @@ -1741,24 +1920,21 @@ def stream(domain, args, name='Rainbow Stream'): if tweet is None: printNicely("-- None --") elif tweet is Timeout: + # Because the stream check for each 0.3s + # so we shouldn't output anything here 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() + reconn_notice() StreamLock.release() break elif tweet is Hangup: printNicely("-- Hangup --") + reconn_notice() + StreamLock.release() + break elif tweet.get('text'): # Check the semaphore pause and lock (stream process only) if g['pause']: @@ -1793,6 +1969,9 @@ def stream(domain, args, name='Rainbow Stream'): while c['lock']: time.sleep(0.5) print_message(tweet['direct_message']) + elif tweet.get('event'): + c['events'].append(tweet) + print_event(tweet) except TwitterHTTPError: printNicely('') printNicely(