# # Copyright (c) 2013 by DarkDefender # File bugs here: https://github.com/DarkDefender/weetweet # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import sys import ast import re import os import tempfile import time import calendar import socket import getpass import string import itertools # This twitter plugin can be extended even more. Just look at the twitter api # doc here: https://dev.twitter.com/docs/api/1.1 # Thanks to ttytter for interface ideas! # http://www.floodgap.com/software/ttytter/ # I've borrowed some ideas, functions for ainmosni and his twitter plugin # https://github.com/ainmosni/weetwit weechat_call = True import_ok = True required_twitter_version = "1.14.1" try: import weechat #We need to import sys to see which python version weechat runs (2 or 3) import sys is_py3 = sys.version_info >= (3, 0) except: #import html parser so we can convert html strings to plain text try: import html.parser except: print("You need to have python3 installed to run this script!") exit(0) weechat_call = False try: #Import python twitter lib from twitter import * from twitter.stream import Timeout, HeartbeatTimeout, Hangup except: import_ok = False try: #Import for version checking from pkg_resources import parse_version, get_distribution version = get_distribution("twitter").version if parse_version(required_twitter_version) > parse_version(version): import_ok = False except: if weechat_call: weechat.prnt("", "You need to have pkg_resources installed for version checking") else: print("You need to have pkg_resources installed for version checking") # These two keys is what identifies this twitter client as "weechat twitter" # If you want to change it you can register your own keys at: # https://dev.twitter.com/apps/new CONSUMER_SECRET = 'ivx3oxxkSOAOofRuhmGXQK4nkLFNXD94wbJiRUBhN1g' CONSUMER_KEY = 'NVkYe8DAeaw6YRcjw662ZQ' script_options = { "oauth_token" : ("", "your twitter oauth token"), "oauth_secret" : ("", "your twitter oath secret"), "auth_complete" : (False, "whether you've finished the auth process or not"), "screen_name" : ("", "your twitter username"), "last_id" : ("", "the ID of the most recent tweet"), "print_id" : (True, "display short tweet indexes"), "alt_rt_style" : (False, "use alternate retweet display style"), "home_replies" : (False, "show replies on the home timeline"), "tweet_nicks" : (True, "add twitter usernames to the nicklist"), "index_pattern": ("an", "a pattern of character groups to build " "short tweet indexes from. the default 'an' generates " "looping indexes from a0 to z9. a = [a-z], n = [0-9]"), } #TODO have a dict for each buffer tweet_dict = {'cur_index': None} #Mega command dict command_dict = dict( user="u", replies="r", view_tweet="v", thread="th", link="l", retweet="rt", delete="d", tweet="t", reply="re", new_tweets="new", follow_user="follow", unfollow_user="unfollow", following="f", followers="fo", about="a", block="b", unblock="ub", blocked_users="blocks", favorite="fav", unfavorite="unfav", favorites="favs", fav_retweet="fart", rate_limits="limits", home_timeline="home", clear_nicks="cnicks", clear_buffer="clear", create_stream="stream", restart_home_stream="re_home") desc_dict = dict( user=" [ ||], Request user timeline, " + "if is given it will get tweets older than , " + " is how many tweets to get, valid number is 1-200", replies="[ ||], Get any replies/mentions of you " + "if is given it will get tweets older than , " + " is how many tweets to get, valid number is 1-200", view_tweet=", View/get tweet with ", thread=", View/get the reply chain of tweets (the thread) where " + " is the last tweet in the thread.", link=", Get a link to tweet with ", retweet=", Retweet ", delete=", Delete tweet . You can only delete your own tweets...", tweet="Tweet the text following this command", reply=" , reply to . You need to have @ " + "of the user that you reply to in the tweet text. If this is not " + "the case this will be treated like a normal tweet instead.", new_tweets="Get new tweets from your home_timeline. This is only " + "useful if you have disconnected from the home twitter stream", follow_user=", Add user to people you follow", unfollow_user=", Remove user for people you follow", following="[||| ], Show 'friends' of or " + "if no user were given show the people you follow. If not all " + "followers were printed supply the of the last list to get " + "the new batch of nicks", followers="[||| ], Who followes or " + "if no user were given show your follower. If not all " + "followers were printed supply the of the last list to get " + "the new batch of nicks", about=", Print info about ", block=", Block ", unblock=", Unblock ", blocked_users="Print a list of users you have currently blocked", favorite=", Add tweet to you favorites", unfavorite=", Remove tweet from your favorites", favorites="[|][ ||], Request favs, " + "if is not given get your own favs. " + "If is given it will get tweets older than , " + " is how many tweets to get, valid number is 1-200", fav_retweet=", favorites and retweets ", rate_limits="[|], get the current status of the twitter " + "api limits. It prints how much you have left/used. " + " if is supplied it will only get/print that sub_group.", home_timeline="[ ||], Get tweets from you home " + "timeline" + "if is given it will get tweets older than , " + " is how many tweets to get, valid number is 1-200", clear_nicks="Clear nicks from the 'Tweet_parse' nick group. "+ "These nicks are parsed from recived tweets, it can get " + "messy at times...", clear_buffer="Clear the twitter buffer of text "+ "same as '/buffer clear'", create_stream="Create a twitter stream with the following filter "+ "options: . Note that the user list and the "+ "keyword list must be comma seperated. IE user1, user2, user3 keyword1, keyword2.\n" + "If you only want to stream keywords, write '&' as 'users' input.\n"+ "NOTE: you can only have one stream at a time because "+ "twitter will IP ban you if you repeatedly request more than one "+ "stream.", restart_home_stream="Restart the home timeline stream after it has " + "shutdown.") SCRIPT_NAME = "weetweet" SCRIPT_FILE_PATH = os.path.abspath(__file__) twit_buf = "" sock_hooks = {} proc_hooks = {} sock_fd_dict = {} tweet_nicks_group = {} friends_nicks_group = {} html_escape_table = { '"': """, "'": "'", } def html_escape(text): """Produce entities within text.""" return "".join(html_escape_table.get(c, c) for c in text) def index_gen(): #Generates indexes starting from a0 going up to z9, then repeating #index_pattern is a pattern of character groups to use for each part of the index # a = [a-z] # n = [0-9] #This setting needs a script reload to take effect index_parts = [] for p in script_options['index_pattern']: if p == 'a': index_parts.append(string.ascii_lowercase) elif p == 'n': index_parts.append(string.digits) while True: for index in itertools.product(*index_parts): yield "".join(index) #Create our index generator index_generator = index_gen() def dict_tweet(tweet_id): cur_index = tweet_dict['cur_index'] if not tweet_id in tweet_dict.values(): cur_index = next(index_generator) tweet_dict[cur_index] = tweet_id tweet_dict['cur_index'] = cur_index return cur_index else: for index, t_id in tweet_dict.items(): if t_id == tweet_id: return index return None def read_config(): for item in script_options: script_options[item] = weechat.config_string( weechat.config_get("plugins.var.python."+SCRIPT_NAME+"." + item)) for item in ["auth_complete", "print_id", "alt_rt_style", "home_replies", "tweet_nicks"]: #Convert to bool script_options[item] = weechat.config_string_to_boolean(script_options[item]) for item in ["oauth_token", "oauth_secret"]: #Convert potentially encrypted tokens script_options[item] = weechat.string_eval_expression(script_options[item], {}, {}, {}) def config_cb(data, option, value): """Callback called when a script option is changed.""" # for example, read all script options to script variables... # ... read_config() return weechat.WEECHAT_RC_OK def add_to_nicklist(buf, nick, group=""): """Add nick to the nicklist.""" if group == "": group = friends_nicks_group[buf] weechat.nicklist_add_nick(buf, group, nick, 'bar_fg', '', '', 1) def remove_from_nicklist(buf, nick, group=""): """Remove nick from the nicklist.""" if group == "": group = friends_nicks_group[buf] nick_ptr = weechat.nicklist_search_nick(buf, group, nick) weechat.nicklist_remove_nick(buf, nick_ptr) def parse_for_nicks(text, buffer): #Parse text for twitter nicks and add them to nicklist regex = re.compile(r'@([A-Za-z0-9_]+)') for nick in regex.findall(text): add_to_nicklist(buffer, nick, tweet_nicks_group[buffer]) def print_tweet_data(buffer, tweets, data): for message in tweets: nick = message[1] text = message[3] reply_id = "" if script_options['tweet_nicks']: parse_for_nicks(text, buffer) add_to_nicklist(buffer, nick, tweet_nicks_group[buffer]) if script_options['print_id']: t_id = weechat.color('reset') + ' ' + dict_tweet(message[2]) else: t_id = '' if len(message) == 5: #This is a reply to a tweet arrow_col = weechat.color('chat_prefix_suffix') reset_col = weechat.color('reset') reply_id = arrow_col + "<" + reset_col + dict_tweet( message[4]) + arrow_col + "> " + reset_col temp_text = text text = reply_id reply_id = temp_text weechat.prnt_date_tags(buffer, message[0], "notify_message", "%s%s\t%s%s" % (nick, t_id, text, reply_id)) if data == "id": try: if( script_options['last_id'] == "" or int(script_options['last_id']) < int(tweets[-1][2]) ): script_options['last_id'] = tweets[-1][2] # Save last id weechat.config_set_plugin("last_id", script_options["last_id"]) except: pass def trim_tweet_data(tweet_data, screen_name, alt_rt_style, is_py3): # Because of the huge amount of data, we need to cut down on most of it # because we only really want a small subset of it. This also prevents the # output buffer from overflowing when fetching many tweets at once. h = html.parser.HTMLParser() output = [] for message in tweet_data: if message.get('retweeted_status'): if alt_rt_style: if message['user']['screen_name'] == screen_name: #escape highlighting message['user']['screen_name'] = "" if message.get('full_text'): message['full_text'] = message['retweeted_status']['full_text'] + \ " (retweeted by " + message['user']['screen_name'] + ")" else: message['text'] = message['retweeted_status']['text'] + \ " (retweeted by " + message['user']['screen_name'] + ")" message['user'] = message['retweeted_status']['user'] else: if message.get('full_text'): message['full_text'] = "RT @{}: {}".format( message['retweeted_status']['user']['screen_name'], message['retweeted_status']['full_text']) else: message['text'] = "RT @{}: {}".format( message['retweeted_status']['user']['screen_name'], message['retweeted_status']['text']) mes_list = [calendar.timegm(time.strptime(message['created_at'], '%a %b %d %H:%M:%S +0000 %Y')), message['user']['screen_name'], message['id_str']] if is_py3: if message.get('full_text'): mes_list.append(h.unescape(message['full_text'])) else: mes_list.append(h.unescape(message['text'])) else: #convert text to bytes so python2 can read it correctly if message.get('full_text'): mes_list.append(h.unescape(message['full_text']).encode('utf-8')) else: mes_list.append(h.unescape(message['text']).encode('utf-8')) if message["in_reply_to_status_id_str"] != None: mes_list.append(message["in_reply_to_status_id_str"]) output.append(mes_list) output.reverse() return output def stream_message(buffer, tweet): #TODO because we can not use userstreams anymore, this code will probably never be called if 'delete' in tweet: # Only show the 'Got request to delete' dialog if it didn't come from us #Colorize the tweet id arrow_col = weechat.color('chat_prefix_suffix') reset_col = weechat.color('reset') dict_id = dict_tweet(tweet['delete']['status']['id_str']) id_str = arrow_col + "<" + reset_col + dict_id + arrow_col + "> " + reset_col weechat.prnt(buffer, "%s%s" % (weechat.prefix("network"), "Got request to delete: " + id_str)) elif 'event' in tweet: event_str = tweet['event'] if event_str[-1] == "e": event_str += "d" elif event_str[-1] != "d": event_str += "ed" extra_str = "" if 'target_object' in tweet: if 'id_str' in tweet['target_object']: arrow_col = weechat.color('chat_prefix_suffix') reset_col = weechat.color('reset') dict_id = dict_tweet(tweet['target_object']['id_str']) extra_str = "'s tweet " + arrow_col + "<" + reset_col + dict_id + arrow_col + \ "> " + reset_col #TODO make the event printing better weechat.prnt_date_tags( buffer, 0, "no_highlight", "%s%s" % (weechat.prefix("network"), tweet['source']['screen_name'] + \ " " + event_str + \ " " + tweet['target']['screen_name'] + extra_str)) elif 'friends' in tweet: #This should be the initital message you get listing #friend ids when connecting to the twitter stream weechat.prnt(buffer, "%s%s" % (weechat.prefix("network"), "Connected to twitter streaming API.")) #Get new tweets since the last update #(we might have missed some if this is after a reconnect) #Get latest tweets from timeline my_command_cb("silent", buffer, "new") else: weechat.prnt(buffer, "%s%s" % (weechat.prefix("network"), "recv stream data: " + str(tweet))) def twitter_stream_cb(buffer, fd): #accept connection server = sock_fd_dict[sock_fd_dict[str(fd)]] conn, addr = server.accept() tweet = b"" data = True while data: try: data = conn.recv(1024) tweet += data except: break try: tweet = tweet.decode('utf-8') tweet = ast.literal_eval(tweet) except: weechat.prnt(buffer, "Error recv stream message:") weechat.prnt(buffer, tweet) return weechat.WEECHAT_RC_OK #Is this a text message (normal tweet)? if isinstance(tweet, list): if buffer == twit_buf: #Update last recv id print_tweet_data(buffer, tweet, "id") else: print_tweet_data(buffer, tweet, "") elif tweet == "options": #We need to send over the stream options options = dict(screen_name=script_options['screen_name'], name=sock_fd_dict[str(fd)], alt_rt_style=int(script_options['alt_rt_style']), home_replies=int(script_options['home_replies']), token=script_options["oauth_token"], secret=script_options["oauth_secret"], last_id=script_options['last_id'], is_py3=is_py3) if is_py3: conn.sendall(bytes(str(options), "utf-8")) else: conn.sendall(bytes(str(options))) else: #https://dev.twitter.com/docs/streaming-apis/messages #TODO handle stream events stream_message(buffer, tweet) conn.close() return weechat.WEECHAT_RC_OK def twitter_stream(cmd_args): if len(cmd_args) < 3: return "Invalid stream command" if not os.path.exists(cmd_args[2]): return "The socket file doesn't exist! " + cmd_args[2] def connect(): client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) client.connect(cmd_args[2]) #Don't block by default, timeout if no data is present client.setblocking(False) return client client = connect() client.settimeout(3) client.sendall(bytes('"options"', "utf-8")) client.shutdown(socket.SHUT_WR) data = True options = b"" while data: try: data = client.recv(1024) options += data except: break try: options = options.decode('utf-8') option_dict = ast.literal_eval(options) except: return "Did not manage to get startup arguments to stream" client.close() oauth_token = option_dict['token'] oauth_secret = option_dict['secret'] home_replies = option_dict['home_replies'] alt_rt_style = option_dict['alt_rt_style'] screen_name = option_dict['screen_name'] name = option_dict['name'] last_id = option_dict['last_id'] is_py3 = option_dict['is_py3'] if len(cmd_args) >= 4: stream_args = cmd_args[3:] else: stream_args = "" # These arguments are optional. But the current code only handles this # configuration. So it's defined here if the defaults change. stream_options = dict(timeout=None, block=True, heartbeat_timeout=90) # Reconnect timer list [1sec, 5sec, 10sec, 1min, 2min] re_timer = [1, 2, 10, 60, 120] re_timer_idx = 0 kwargs = dict(tweet_mode='extended') if name == "twitter": #Twitter has removed home user streams, so we have to do this instead... twitter = Twitter(auth=OAuth( oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET), api_version='1.1', domain='api.twitter.com', retry=True) while True: #TODO check with limits for how often we can make requests and how many we have left. if last_id == "": tweet_data = twitter.statuses.home_timeline(exclude_replies=no_home_replies, tweet_mode='extended') else: tweet_data = twitter.statuses.home_timeline( since_id=last_id, count=200, exclude_replies=home_replies, tweet_mode='extended') if not tweet_data == []: tweet = trim_tweet_data(tweet_data, screen_name, alt_rt_style, is_py3) client = connect() client.sendall(bytes(str(tweet), "utf-8")) client.close() stream_end_message = "Text message" #Update last_id (for the stream process) last_id = tweet[-1][2] #We have to wait at least a minute because of the twitter rate limits time.sleep(65) while True: try: stream = TwitterStream(auth=OAuth( oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET), **stream_options) twitter = Twitter(auth=OAuth( oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET), api_version='1.1', domain='api.twitter.com') if stream_args[0] != "&": follow = stream_args[0] kwargs['screen_name'] = follow twitter_data = twitter.users.lookup(**kwargs) follow_ids = "" for user in twitter_data: follow_ids += user['id_str'] + ", " follow_ids = follow_ids[:-1] if len(stream_args) == 2: track = stream_args[1] kwargs['track'] = track kwargs['follow'] = follow_ids tweet_iter = stream.statuses.filter(**kwargs) else: kwargs['follow'] = follow_ids tweet_iter = stream.statuses.filter(**kwargs) else: track = stream_args[1] kwargs['track'] = track tweet_iter = stream.statuses.filter(**kwargs) except: stream_end_message = "Connection problem (could not connect to twitter)" break stream_end_message = "Unknown reason" # Iterate over the stream. for tweet in tweet_iter: # You must test that your tweet has text. It might be a delete # or data message. if tweet is None: stream_end_message = "'None' reply" elif tweet is Timeout: stream_end_message = "Timeout" elif tweet is HeartbeatTimeout: stream_end_message = "Heartbeat Timeout" elif tweet is Hangup: stream_end_message = "Hangup" elif tweet.get('full_text') or tweet.get('text'): tweet = trim_tweet_data([tweet], screen_name, alt_rt_style, is_py3) client = connect() client.sendall(bytes(str(tweet), "utf-8")) client.close() stream_end_message = "Text message" # Reset the reconnect timer index when we get a new message re_timer_idx = 0 else: #Got a other type of message client = connect() client.sendall(bytes(str(tweet), "utf-8")) client.close() stream_end_message = "Unhandled type message" client = connect() client.sendall(bytes( '"Disconnected, trying to reconnect in {0} sec. Reason: {1}"'.format( re_timer[re_timer_idx], stream_end_message), "utf-8")) client.close() time.sleep(re_timer[re_timer_idx]) if re_timer_idx < (len(re_timer) - 1): re_timer_idx += 1 return "Stream shut down after: " + stream_end_message + \ ". You'll have to restart the stream manually. (:re_home, if home stream)" def stream_close_cb(name, buffer): global sock_fd_dict global proc_hooks weechat.unhook(sock_hooks[name]) #Is the process already unhooked? if proc_hooks[name]: weechat.unhook(proc_hooks[name]) #remove fd key for key, value in sock_fd_dict.items(): if value == name: sock_fd_dict.pop(key, None) break sock_fd_dict[name].close() proc_hooks.pop(name, None) sock_fd_dict.pop(name, None) return weechat.WEECHAT_RC_OK def create_stream(name, args=""): global sock_fd_dict global proc_hooks global sock_hooks if proc_hooks.get(name): return "Stream has already been created, close it before trying to open a new one" #Check if buffer exists buffer = weechat.buffer_search("python", name) if buffer == "": buffer = weechat.buffer_new(name, "buffer_input_cb", name, "stream_close_cb", name) setup_buffer(buffer) if not sock_fd_dict.get(name): file_name = tempfile.gettempdir() + "/we_tw_" + getpass.getuser() + "_" + name if os.path.exists(file_name): os.remove(file_name) server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(file_name) #Don't block, timeout if no data is present server.setblocking(False) server.listen(1) file_fd = server.fileno() sock_fd_dict[str(file_fd)] = name sock_fd_dict[name] = server sock_hooks[name] = weechat.hook_fd(file_fd, 1, 0, 0, "twitter_stream_cb", buffer) proc_hooks[name] = weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + \ "stream " + file_name + " " + args, 0, "my_process_cb", str([buffer, "Stream"])) return "Started stream" def my_process_cb(data, command, rc, out, err): if rc == weechat.WEECHAT_HOOK_PROCESS_ERROR: weechat.prnt("", "Error with command '%s'" % command.replace(script_options["oauth_token"], "").replace(script_options["oauth_secret"], "")) if err != "": weechat.prnt("", "stderr: %s" % err) return weechat.WEECHAT_RC_OK data = ast.literal_eval(data) buffer = data[0] end_mes = data[1] if out != "": if out[0] != "[" and out[0] != "{": #If message is just a string print it weechat.prnt(buffer, "%s%s" % (weechat.prefix("network"), out)) process_output = [] else: process_output = ast.literal_eval(out) #List message if len(end_mes) >= 1 and end_mes[0] == "L": if isinstance(process_output[-1], int): t_id = dict_tweet(str(process_output[-1])) + "\t" process_output = process_output[:-1] more = " ..." else: t_id = weechat.prefix("network") more = "" for nick in process_output: if end_mes == "LYFollowing": end_mes = "LYou are following" add_to_nicklist(buffer, nick) elif script_options['tweet_nicks']: add_to_nicklist(buffer, nick, tweet_nicks_group[buffer]) weechat.prnt_date_tags(buffer, 0, "no_highlight", "%s%s: %s%s" % (t_id, end_mes[1:], process_output, more)) return weechat.WEECHAT_RC_OK if end_mes == "About": weechat.prnt(buffer, "Nick: %s | Name: %s | Protected: %s" % (process_output['screen_name'], process_output['name'], process_output['protected'])) weechat.prnt(buffer, "Description: %s" % process_output['description']) weechat.prnt(buffer, "Location: %s | Time zone: %s" % (process_output['location'], process_output['time_zone'])) weechat.prnt(buffer, "Created at: %s | Verified user: %s" % (process_output['created_at'], process_output['verified'])) weechat.prnt(buffer, "Following: %s | Followers: %s | Favourites: %s | Tweets: %s" % (process_output['friends_count'], process_output['followers_count'], process_output['favourites_count'], process_output['statuses_count'])) weechat.prnt(buffer, "Are you currently following this person: %s" % (process_output['following'])) return weechat.WEECHAT_RC_OK elif end_mes == "Stream": global proc_hooks #Clean up the stream hooks name = weechat.buffer_get_string(buffer, "name") #Set the hook to none because it has already been unhooked proc_hooks[name] = None stream_close_cb(name, buffer) #TODO restart stream correctly #create_stream(name) return weechat.WEECHAT_RC_OK print_tweet_data(buffer, process_output, end_mes) if end_mes != "id" and end_mes != "": weechat.prnt(buffer, "%s%s" % (weechat.prefix("network"), end_mes)) if err != "": weechat.prnt("", "stderr: %s" % err) return weechat.WEECHAT_RC_OK def get_twitter_data(cmd_args): # Read the oauth token and auth with the twitter api. # Return the requested tweets no_home_replies = True alt_rt_style = False is_py3 = False screen_name = "" h = html.parser.HTMLParser() try: if cmd_args[-1][0] == "[": option_list = ast.literal_eval(cmd_args[-1]) cmd_args.pop(-1) if "home_replies" in option_list: no_home_replies = False if "alt_rt_style" in option_list: alt_rt_style = True if "is_py3" in option_list: is_py3 = True screen_name = option_list[0] except: pass if len(cmd_args) < 4: return "Invalid command" oauth_token = cmd_args[1] oauth_secret = cmd_args[2] try: if cmd_args[3] == "auth": twitter = Twitter( auth=OAuth(oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET), format='', api_version=None) if len(cmd_args) == 5: #pin return twitter.oauth.access_token(oauth_verifier=cmd_args[4]) return twitter.oauth.request_token() twitter = Twitter(auth=OAuth( oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET), api_version='1.1', domain='api.twitter.com') if cmd_args[3] == "settings": #this only gets called from within weechat return twitter.account.settings()['screen_name'] elif cmd_args[3] == "u": kwargs = dict(count=20, screen_name=cmd_args[4], tweet_mode='extended') if len(cmd_args) == 7: kwargs['count'] = int(cmd_args[6]) kwargs['max_id'] = cmd_args[5] elif len(cmd_args) == 6: if int(cmd_args[5]) <= 200: kwargs['count'] = int(cmd_args[5]) else: kwargs['max_id'] = cmd_args[5] tweet_data = twitter.statuses.user_timeline(**kwargs) elif cmd_args[3] == "r": if len(cmd_args) == 6: kwargs = dict(count=int(cmd_args[5]), max_id=cmd_args[4], tweet_mode='extended') tweet_data = twitter.statuses.mentions_timeline(**kwargs) elif len(cmd_args) == 5: if int(cmd_args[4]) <= 200: kwargs = dict(count=int(cmd_args[4]), tweet_mode='extended') tweet_data = twitter.statuses.mentions_timeline(**kwargs) else: kwargs = dict(max_id=cmd_args[4], tweet_mode='extended') tweet_data = twitter.statuses.mentions_timeline(**kwargs) else: tweet_data = twitter.statuses.mentions_timeline(tweet_mode='extended') elif cmd_args[3] == "v": tweet_data = [twitter.statuses.show._(cmd_args[4])(tweet_mode='extended')] elif cmd_args[3] == "th": #only fetch up to 20 tweets in one go tweets_left = 0 tweet_id = cmd_args[4] tweet_data = [] while tweets_left < 20: tweets_left += 1 #TODO handle deleted tweets temp_tweet = twitter.statuses.show._(tweet_id)(tweet_mode='extended') tweet_data.append(temp_tweet) if temp_tweet["in_reply_to_status_id_str"] != None: tweet_id = temp_tweet["in_reply_to_status_id_str"] else: break elif cmd_args[3] == "l": tweet_data = [twitter.statuses.show._(cmd_args[4])()] output = "Link for tweet: https://twitter.com/{}/status/{}".format( tweet_data[0]['user']['screen_name'], tweet_data[0]['id_str']) return output elif cmd_args[3] == "rt": tweet_data = [twitter.statuses.retweet._(cmd_args[4])()] elif cmd_args[3] == "d": #deletes tweet made by the user _(...) converts the id string to a call #returns the tweet that was deleted (not a list(dict) just a dict) #make it into a list so we don't have to write special cases for this tweet_data = [twitter.statuses.destroy._(cmd_args[4])()] elif cmd_args[3] == "t": #returns the tweet that was sent (not a list(dict) just a dict) #make it into a list so we don't have to write special cases for this tweet_data = [twitter.statuses.update(status=h.unescape(cmd_args[4]))] elif cmd_args[3] == "re": tweet_data = [twitter.statuses.update(status=h.unescape(cmd_args[5]), in_reply_to_status_id=cmd_args[4])] elif cmd_args[3] == "new": tweet_data = twitter.statuses.home_timeline( since_id=cmd_args[4], count=200, exclude_replies=no_home_replies, tweet_mode='extended') if tweet_data == []: return "No new tweets available." elif cmd_args[3] == "follow": tweet_data = [] twitter.friendships.create(screen_name=cmd_args[4]) elif cmd_args[3] == "unfollow": tweet_data = [] twitter.friendships.destroy(screen_name=cmd_args[4]) elif cmd_args[3] == "f" or cmd_args[3] == "fo": if len(cmd_args) == 6: kwargs = dict(screen_name=cmd_args[4], stringify_ids=True, cursor=int(cmd_args[5]), count=250) else: kwargs = dict(screen_name=cmd_args[4], stringify_ids=True, cursor=-1, count=250) if cmd_args[3] == "f": tweet_data = twitter.friends.ids(**kwargs) else: tweet_data = twitter.followers.ids(**kwargs) kwargs['cursor'] = tweet_data['next_cursor'] friend_ids = tweet_data['ids'] friend_list = list() while len(friend_ids) > 100: tweet_data = twitter.users.lookup(user_id=", ".join(friend_ids[:100])) friend_ids = friend_ids[100:] for user in tweet_data: friend_list.append(user['screen_name']) tweet_data = twitter.users.lookup(user_id=", ".join(friend_ids)) for user in tweet_data: friend_list.append(user['screen_name']) if kwargs['cursor'] != 0: friend_list.append(kwargs['cursor']) return friend_list elif cmd_args[3] == "a": return twitter.users.show(screen_name=cmd_args[4]) elif cmd_args[3] == "b": tweet_data = [] twitter.blocks.create(screen_name=cmd_args[4]) elif cmd_args[3] == "ub": tweet_data = [] twitter.blocks.destroy(screen_name=cmd_args[4]) elif cmd_args[3] == "blocks": tweet_data = twitter.blocks.list(skip_status=True) block_list = list() for user in tweet_data['users']: block_list.append(user['screen_name']) return block_list elif cmd_args[3] == "fav": tweet_data = [twitter.favorites.create(_id=cmd_args[4])] elif cmd_args[3] == "unfav": tweet_data = [twitter.favorites.destroy(_id=cmd_args[4])] elif cmd_args[3] == "favs": if len(cmd_args) >= 5: kwargs = dict() if not cmd_args[4].isdigit(): kwargs['screen_name'] = cmd_args[4] cmd_args.pop(4) if len(cmd_args) == 5: if int(cmd_args[4]) <= 200: kwargs['count'] = int(cmd_args[4]) else: kwargs['max_id'] = cmd_args[4] elif len(cmd_args) == 6: kwargs['count'] = int(cmd_args[5]) kwargs['max_id'] = cmd_args[4] tweet_data = twitter.favorites.list(**kwargs) else: tweet_data = twitter.favorites.list() elif cmd_args[3] == "fart": twitter.favorites.create(_id=cmd_args[4]) tweet_data = [twitter.statuses.retweet._(cmd_args[4])()] elif cmd_args[3] == "limits": output = "" if len(cmd_args) >= 5: tweet_data = twitter.application.rate_limit_status( resources=", ".join(cmd_args[4:])) else: tweet_data = twitter.application.rate_limit_status() for res in tweet_data['resources']: output += res + ":\n" for sub_res in tweet_data['resources'][res]: output += " " + sub_res[len(res)+2:] + ":\n" output += " " + 'reset' + ": " + \ time.strftime('%Y-%m-%d %H:%M:%S', time.localtime( tweet_data['resources'][res][sub_res]['reset'])) + "\n" output += " " + 'limit' + ": " + \ str(tweet_data['resources'][res][sub_res]['limit']) + "\n" output += " " + 'remaining' + ": " + \ str(tweet_data['resources'][res][sub_res]['remaining']) + "\n" return output elif cmd_args[3] == "home": if len(cmd_args) == 6: kwargs = dict(count=int(cmd_args[5]), max_id=cmd_args[4], exclude_replies=no_home_replies, tweet_mode='extended') tweet_data = twitter.statuses.home_timeline(**kwargs) elif len(cmd_args) == 5: if int(cmd_args[4]) <= 200: tweet_data = twitter.statuses.home_timeline( count=int(cmd_args[4]), exclude_replies=no_home_replies, tweet_mode='extended') else: tweet_data = twitter.statuses.home_timeline(max_id=cmd_args[4], exclude_replies=no_home_replies, tweet_mode='extended') else: tweet_data = twitter.statuses.home_timeline(exclude_replies=no_home_replies, tweet_mode='extended') else: return "Invalid command: " + cmd_args[3] except TwitterHTTPError as err: #See if we can print a pretty error message first data = err.response_data if "errors" in data and "message" in data["errors"][0]: return "Error: " + data["errors"][0]["message"] return "Unexpected error in get_twitter_data:%s\n Call: %s" % (sys.exc_info(), cmd_args[3]) except: return "Unexpected error in get_twitter_data:%s\n Call: %s" % (sys.exc_info(), cmd_args[3]) return trim_tweet_data(tweet_data, screen_name, alt_rt_style, is_py3) # callback for data received in input def buffer_input_cb(data, buffer, input_data): # ... end_message = "" options = [script_options['screen_name']] if script_options['alt_rt_style']: options.append("alt_rt_style") if script_options['home_replies']: options.append("home_replies") if is_py3: options.append("is_py3") if input_data[0] == ':': if data != "silent": weechat.prnt_date_tags(buffer, 0, "no_highlight", input_data) input_args = input_data.split() command = input_args[0][1:] if command_dict.get(command): input_data = input_data.replace(command, command_dict[command], 1) command = command_dict[command] if command == 'd' and tweet_dict.get(input_args[1]): input_data = 'd ' + tweet_dict[input_args[1]] weechat.prnt(buffer, "%sYou deleted the following tweet:" % weechat.prefix("network")) elif command == 'v' and tweet_dict.get(input_args[1]): input_data = 'v ' + tweet_dict[input_args[1]] end_message = "Done" elif command == 'th' and tweet_dict.get(input_args[1]): input_data = 'th ' + tweet_dict[input_args[1]] end_message = "Done" elif command == 'l' and tweet_dict.get(input_args[1]): input_data = 'l ' + tweet_dict[input_args[1]] elif command == 'rt' and tweet_dict.get(input_args[1]): end_message = "id" input_data = 'rt ' + tweet_dict[input_args[1]] elif command == 're' and tweet_dict.get(input_args[1]): end_message = "id" len_id = len(tweet_dict['cur_index']) input_data = 're ' + tweet_dict[input_args[1]] \ + " '" + html_escape(input_data[(2+1+len_id+1):]) + "'" elif command == 'new': end_message = "id" if script_options['last_id'] != "": input_data = 'new ' + script_options['last_id'] else: input_data = 'home' elif command == 'home' or command == 'r' or ( command == 'favs' and len(input_args) >= 2 and input_args[1].isdigit()): input_data = command if len(input_args) == 3 and tweet_dict.get(input_args[1]) and input_args[2].isdigit(): num = int(input_args[2]) # 200 tweets is the max request limit if num <= 200 and num > 0: input_data += " " + tweet_dict[input_args[1]] + " " + input_args[2] else: input_data += " " + tweet_dict[input_args[1]] elif len(input_args) == 2: if tweet_dict.get(input_args[1]): input_data += " " + tweet_dict[input_args[1]] elif input_args[1].isdigit(): num = int(input_args[1]) # 200 tweets is the max request limit if num <= 200 and num > 0: input_data += " " + input_args[1] end_message = "Done" elif command == 'u' or (command == 'favs' and len(input_args) >= 3): input_data = " ".join(input_args[:2])[1:] if len(input_args) == 4 and tweet_dict.get(input_args[2]) and input_args[3].isdigit(): num = int(input_args[3]) # 200 tweets is the max request limit if num <= 200 and num > 0: input_data += " " + tweet_dict[input_args[2]] + " " + input_args[3] else: input_data += " " + tweet_dict[input_args[2]] elif len(input_args) == 3: if tweet_dict.get(input_args[2]): input_data += " " + tweet_dict[input_args[2]] elif input_args[2].isdigit(): num = int(input_args[2]) # 200 tweets is the max request limit if num <= 200 and num > 0: input_data += " " + input_args[2] end_message = "Done" elif command == 'auth': if len(input_args) == 2: oauth_dance(buffer, input_args[1]) else: oauth_dance(buffer) return weechat.WEECHAT_RC_OK elif command == 'f' or command == 'fo': #L because we are returning a list to be printed later on end_message = "L" if len(input_args) == 3 and tweet_dict.get(input_args[2]): input_data = command + " " + input_args[1] + " " + tweet_dict[input_args[2]] elif len(input_args) == 2: if tweet_dict.get(input_args[1]): input_data = command + " " + script_options['screen_name'] + " " \ + tweet_dict[input_args[1]] #Your list, not any other users end_message += "Y" else: input_data = input_data[1:] else: input_data = command + " " + script_options['screen_name'] end_message += "Y" if command == 'f': end_message += "Following" else: end_message += "Followers" elif command == 'a': input_data = input_data[1:] end_message = "About" elif command == 'blocks': input_data = input_data[1:] end_message = "LBlock list" elif command == 'fav' and tweet_dict.get(input_args[1]): input_data = 'fav ' + tweet_dict[input_args[1]] weechat.prnt(buffer, "%sYou fave'd the following tweet:" % weechat.prefix("network")) elif command == 'unfav' and tweet_dict.get(input_args[1]): input_data = 'unfav ' + tweet_dict[input_args[1]] weechat.prnt(buffer, "%sYou unfave'd the following tweet:" % weechat.prefix("network")) elif command == 'fart' and tweet_dict.get(input_args[1]): end_message = "id" input_data = 'fart ' + tweet_dict[input_args[1]] weechat.prnt(buffer, "%sYou fave'd and retweeted the following tweet:" % weechat.prefix("network")) elif command == 'cnicks': #global tweet_nicks_group if tweet_nicks_group[buffer] != "": weechat.nicklist_remove_group(buffer, tweet_nicks_group[buffer]) tweet_nicks_group[buffer] = "" tweet_nicks_group[buffer] = weechat.nicklist_add_group(buffer, "", "Tweet_parse", "weechat.color.nicklist_group", 1) return weechat.WEECHAT_RC_OK elif command == 'help': weechat.command(buffer, "/help twitter") weechat.prnt(buffer, "Exec command /help twitter, check your root buffer") return weechat.WEECHAT_RC_OK elif command == 'clear': weechat.command(buffer, "/buffer clear") return weechat.WEECHAT_RC_OK elif command == 'stream': args = html_escape(input_data[7:]) weechat.prnt(buffer, create_stream("t_stream", args)) return weechat.WEECHAT_RC_OK elif command == 're_home': weechat.prnt(buffer, create_stream("twitter")) return weechat.WEECHAT_RC_OK else: input_data = input_data[1:] end_message = "Done" else: end_message = "id" #esacpe special chars when printing to commandline input_data = 't ' + "'" + html_escape(input_data) + "'" #input_data = 't ' + "'" + html.escape(input_data) + "'" weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + \ script_options["oauth_token"] + " " + script_options["oauth_secret"] + \ " " + input_data + " " + '"' + str(options) + '"', 10 * 1000, "my_process_cb", str([buffer, end_message])) return weechat.WEECHAT_RC_OK def my_command_cb(data, buffer, args): # ... buffer_input_cb(data, twit_buf, ":"+args) return weechat.WEECHAT_RC_OK def hook_commands_and_completions(): compl_list = [] com_list = [] desc_list = [] for command in sorted(command_dict): compl_list.append(command) com_list.append(command + weechat.color("*red") + " or " + \ weechat.color('reset') + command_dict[command] + "\n") desc_list.append(weechat.color("chat_nick_other") + command + ": \n" + \ desc_dict[command]) weechat.hook_command("twitter", "Command to interact with the twitter api/plugin", " | ".join(com_list), "You can type all of these command in the twitter buffer if you add a " "':' before the command, IE:\n" ":limits\n\n" "If you don't type a command in the twitter buffer you will tweet that " "instead, \n" "text after 280 chars will turn red to let you know were twitter will " "cut off your tweet.\n\n" + weechat.color("*red") + "NOTE:\n" "There are limits on how many twitter api calls you can do, some calls " "are _quite_ restricted.\n" "So if you get HTML errors from the twitter lib you probably exceeded " "the limit\n" "you can check out your limits with the rate_limits/limits command.\n" "_Most_ commands in this plugin only uses one call. If you want to check " "old tweets\n" "in your home timeline it's better to request many tweets in one go.\n" "That way you don't have to request new tweets as often to go further " "back in the timeline.\n" "And thus you are less likely to hit the limit of requests you can do " "in the 15 min time window.\n" "\nYou can write newlines in your tweet with html newline ' ' " "(you can autocomplete it)\n" "\nThe 'number' next to the nicks in the chat window is the of the " "tweet it's used\n" "in the some of the twitter plugin commands.\n\n" "Command desc:\n"+ "\n".join(desc_list), " || ".join(compl_list), "my_command_cb", "") # callback called when buffer is closed # TODO rewrite this so it unloads the plugin def buffer_close_cb(data, buffer): # ... #TODO handle multiple buffers and free up global buffer pointers weechat.unhook_all() return weechat.WEECHAT_RC_OK def tweet_length(message): """Replace URLs with placeholders, 20 for http URLs, 21 for https.""" # regexes to match URLs octet = r'(?:2(?:[0-4]\d|5[0-5])|1\d\d|\d{1, 2})' ip_addr = r'%s(?:\.%s){3}' % (octet, octet) # Base domain regex off RFC 1034 and 1738 label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?' domain = r'%s(?:\.%s)*\.[a-z][-0-9a-z]*[a-z]?' % (label, label) url_re = re.compile(r'(\w+://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (domain, ip_addr), re.I) new_message = message for url in url_re.findall(message): short_url = 'x' * 20 if url.startswith('https'): short_url = 'x' * 21 new_message = new_message.replace(url, short_url) return len(new_message) def my_modifier_cb(data, modifier, modifier_data, string): if not weechat.current_buffer() in friends_nicks_group: return string #check if this is a commandline argument if string == "" or string[0] == "/": return string length = tweet_length(string) # Subtract local command argument from length if string[:3] == ":re": #:re a2 length = length - 6 if length > 280: index = 280 - length string = string[:index] + weechat.color("*red") + string[index:] return string def parse_oauth_tokens(result): for r in result.split('&'): k, v = r.split('=') if k == 'oauth_token': oauth_token = v elif k == 'oauth_token_secret': oauth_token_secret = v return oauth_token, oauth_token_secret def oauth_proc_cb(data, command, rc, out, err): #global script_options buffer = twit_buf if rc == weechat.WEECHAT_HOOK_PROCESS_ERROR: weechat.prnt("", "Error with command '%s'" % command.replace(script_options["oauth_token"], "").replace( script_options["oauth_secret"], "")) return weechat.WEECHAT_RC_OK if len(out) > 16 and out[:16] == "Unexpected error": #Auth command failed (most likely connection problems) weechat.prnt("", out) weechat.prnt(buffer, "An error occured, check root buffer. Most likely a connection " + "problem, fix it and the reload weetweet") return weechat.WEECHAT_RC_OK if out != "": if data == "nick": weechat.config_set_plugin('screen_name', out.strip()) finish_init() elif data == "friends": process_output = ast.literal_eval(out) if isinstance(process_output[-1], int): t_id = dict_tweet(str(process_output[-1])) + "\t" process_output = process_output[:-1] weechat.prnt_date_tags( buffer, 0, "no_highlight", t_id + "It seems like you are following more than 250 people." "Due to twitter api limits it is nearly impossible to get " "large groups of followers in one go. However the " "nicks will be added when they tweet something so if you don't " "have to be able autocomplete them from the start this is not a " "problem for you. If you want to get the rest of the nicks you can " "use the id of this text.") for nick in process_output: add_to_nicklist(buffer, nick) elif data == "auth1": #First auth step to request pin code oauth_token, oauth_token_secret = parse_oauth_tokens(out) script_options['oauth_token'] = oauth_token script_options['oauth_secret'] = oauth_token_secret weechat.prnt(buffer, """ Copy the PIN number that appears on the linked web page and type ":auth " in weechat. For example ":auth 123456" """) oauth_url = ('https://api.twitter.com/oauth/authorize?oauth_token=' + oauth_token) weechat.prnt(buffer, " Please go here to get your PIN: " + oauth_url) elif data == "auth2": oauth_token, oauth_token_secret = parse_oauth_tokens(out) weechat.config_set_plugin('oauth_token', oauth_token) weechat.config_set_plugin('oauth_secret', oauth_token_secret) weechat.config_set_plugin('auth_complete', "on") weechat.prnt(buffer, " Done! now you can begin using this script!") weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + \ script_options["oauth_token"] + " " + \ script_options["oauth_secret"] + " " + \ "settings []", 10 * 1000, "oauth_proc_cb", "nick") return weechat.WEECHAT_RC_OK def oauth_dance(buffer, pin=""): #Auth the twitter client if pin == "": weechat.prnt(buffer, "Hi there! We're gonna get you all set up to use this plugin.") weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + "'' " + "'' " + "auth", 10 * 1000, "oauth_proc_cb", "auth1") else: oauth_verifier = pin.strip() weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + \ script_options["oauth_token"] + " " + \ script_options["oauth_secret"] + \ " " + "auth "+ oauth_verifier, 10 * 1000, "oauth_proc_cb", "auth2") def setup_buffer(buffer): # set title weechat.buffer_set(buffer, "title", "Twitter buffer, type ':help' for options.") # disable logging, by setting local variable "no_log" to "1" weechat.buffer_set(buffer, "localvar_set_no_log", "1") #create main nicklist friends_nicks_group[buffer] = weechat.nicklist_add_group(buffer, "", "Friends", "weechat.color.nicklist_group", 1) tweet_nicks_group[buffer] = weechat.nicklist_add_group(buffer, "", "Tweet_parse", "weechat.color.nicklist_group", 1) autocomp_group = weechat.nicklist_add_group(buffer, "", "Autocomp", "weechat.color.nicklist_group", 1) #newline autocomplete weechat.nicklist_add_nick(buffer, autocomp_group, " ", 'bar_fg', '', '', 1) #show nicklist weechat.buffer_set(buffer, "nicklist", "1") user_nick = script_options['screen_name'] weechat.buffer_set(buffer, "localvar_set_nick", user_nick) add_to_nicklist(buffer, user_nick) # Highlight user nick weechat.buffer_set(buffer, "highlight_words", user_nick) def finish_init(): buffer = twit_buf if script_options['screen_name'] == "": weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + \ script_options["oauth_token"] + " " + \ script_options["oauth_secret"] + " " + \ "settings []", 10 * 1000, "oauth_proc_cb", "nick") return setup_buffer(buffer) #Add friends to nick list weechat.hook_process("python3 " + SCRIPT_FILE_PATH + " " + \ script_options["oauth_token"] + " " + \ script_options["oauth_secret"] + " " + \ "f " + script_options['screen_name'] + " []", 10 * 1000, "oauth_proc_cb", "friends") if __name__ == "__main__" and weechat_call: weechat.register(SCRIPT_NAME, "DarkDefender", "1.2.9", "GPL3", "Weechat twitter client", "", "") if not import_ok: weechat.prnt("", "Can't load twitter python lib >= " + required_twitter_version) weechat.prnt("", "Install it via your package manager or go to " "http://mike.verdone.ca/twitter/") else: hook_commands_and_completions() #Set register script options if not available for option, value in list(script_options.items()): default_value = value[0] description = value[1] if isinstance(default_value, bool): if default_value: default_value = "on" else: default_value = "off" weechat.config_set_desc_plugin(option, description + ' (default: "' + default_value + '")') if not weechat.config_is_set_plugin(option): weechat.config_set_plugin(option, default_value) script_options[option] = default_value read_config() #Hook for config changes weechat.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", "config_cb", "") #Create buffer twit_buf = weechat.buffer_new("twitter", "buffer_input_cb", "", "buffer_close_cb", "") #Hook text input so we can update the bar item weechat.hook_modifier("input_text_display", "my_modifier_cb", "") if script_options['auth_complete']: finish_init() #Create home_timeline stream. #This will indirectly trigger the ":new" command if stream sucessfully starts create_stream("twitter") else: weechat.prnt(twit_buf, """You have to register this plugin with twitter for it to work. Type ":auth" and follow the instructions to do that""") elif import_ok: if sys.argv[1] == "stream": print(twitter_stream(sys.argv)) else: print(get_twitter_data(sys.argv)) else: print("Can't load twitter python lib >= " + required_twitter_version) # vim: tw=0 colorcolumn=100