diff --git a/.gitignore b/.gitignore index 82a5b92..37b193d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *.pyc *.pickle -settings.py +*.pkl \ No newline at end of file diff --git a/botomatic/__init__.py b/botomatic/__init__.py index 635f461..3a35066 100644 --- a/botomatic/__init__.py +++ b/botomatic/__init__.py @@ -1 +1,147 @@ -from botomatic import TBot +#!/usr/env/bin python +# -*- coding: utf-8 -*- + +import pickle +import tweepy +import re +from botomatic import settings + + +class TBot(object): + + __abstract__ = True + + def __init__(self, handle=None, corpus=None, debug_mode=None): + self.handle = handle + self.corpus = corpus + if debug_mode is not None: + self.debug_mode = debug_mode + else: + self.debug_mode = settings.DEBUG_MODE + self.settings = {} + self.tweets = [] + self.follow_handles = [] + self.dms = [] + self.history_filename = handle + "_history.pkl" + self.auth = tweepy.OAuthHandler(settings.CONSUMER_KEY, settings.CONSUMER_SECRET) + try: + self.settings = pickle.load(open(handle + "_settings.pkl", 'rb')) + except (EOFError, FileNotFoundError): + self.authenticate() + pickle.dump(self.settings, open(handle + "_settings.pkl", 'wb')) # right place to save settings? + try: + self.history = pickle.load(open(self.history_filename, 'rb')) + except (EOFError, FileNotFoundError): + self.history = {} + + self.auth.set_access_token(self.settings['key'], self.settings['secret']) + self.api = tweepy.API(self.auth) + + self.run() + + def handle_DMs(self, new_only=True): + if new_only and self.history.get('last_dm_id', None): + dms = self.api.direct_messages(since_id=self.history['last_dm_id']) + else: + dms = self.api.direct_messages() + if dms: + self.history['last_dm_id'] = dms[0].id + return dms + + def handle_mentions(self, new_only=True) -> list: + """We look for the last mention and see if there are any new mentions. + + Args: + new_only: bool, if True we will process only the mentions newer than last mention in history file + + Returns: + mentions: list + """ + last_mention = self.history.get('last_mention_id') + if new_only and last_mention: + mentions = self.api.mentions_timeline(since_id=last_mention) + else: + mentions = self.api.mentions_timeline() + if mentions: + self.history['last_mention_id'] = mentions[0].id + return mentions + + def search(self, query, lang='en'): + return self.api.search(q=query, lang=lang) + + def handle_stream(self): + return self.api.home_timeline() + + def handle_followers(self): # TODO + pass + + def process_tweets(self): + http_re = re.compile(r'http://\S+') + processed_tweets = [] + for tweet in self.tweets: + processed_tweets.append(tweet) + self.tweets = processed_tweets + + def publish_tweets(self, limit=None): + tweeted_count = 0 + + if self.tweets: + for twt in self.tweets: + try: + (tweet, reply_id) = twt + except ValueError: + tweet = twt + reply_id = None + + if self.debug_mode: + print("FAKETWEET: " + tweet[:140]) # for debug mode + else: + try: + if limit: + if tweeted_count >= limit: + continue + else: + status = self.api.update_status(tweet[:140], reply_id) # cap length at 140 chars + self.history['last_tweet_id'] = status.id + tweeted_count += 1 + except tweepy.error.TweepError: # prob a duplicate + pass + + def publish_dms(self): + if self.dms: + for (handle, msg) in self.dms: + user = self.api.get_user(screen_name=handle) + self.api.send_direct_message(screen_name=handle, text=msg) + + def authenticate(self): + print(self.auth.get_authorization_url()) + verifier = input('Verification code: ') + try: + self.auth.get_access_token(verifier) + except tweepy.TweepError: + print('Error: failed to get access token.') + + self.settings['key'] = self.auth.access_token + self.settings['secret'] = self.auth.access_token_secret + + def follow_users(self): + for handle in self.follow_handles: + try: + user = self.api.get_user(screen_name=handle) + user.follow() + except tweepy.error.TweepError: # no such user? + continue + + def run(self): + pass + + def wrap_up(self, tweet_limit=None): + self.process_tweets() + self.follow_users() + self.publish_tweets(tweet_limit) + self.publish_dms() + pickle.dump(self.history, open(self.history_filename, 'wb')) + + +if __name__ == '__main__': + pass diff --git a/botomatic/botomatic.py b/botomatic/botomatic.py deleted file mode 100644 index c88f57d..0000000 --- a/botomatic/botomatic.py +++ /dev/null @@ -1,140 +0,0 @@ -import sys -import os -import pickle -import tweepy # requires version 2.1+ -import urllib -import urllib2 -import json -import re - -import settings - - -class TBot(object): - handle = None - debug_mode = True - settings = {} - tweets = [] - follow_handles = [] - dms = [] - - def __init__(self, handle): - self.history_filename = handle + "_history.pickle" - self.auth = tweepy.OAuthHandler(settings.CONSUMER_KEY, settings.CONSUMER_SECRET) - try: - self.settings = pickle.load(open(handle + "_settings.pickle", 'r')) - except IOError: - self.authenticate() - pickle.dump(self.settings, open(handle + "_settings.pickle", 'w')) # right place to save settings? - - try: - self.history = pickle.load(open(self.history_filename, 'r')) - except IOError: - self.history = {} - - self.auth.set_access_token(self.settings['key'], self.settings['secret']) - self.api = tweepy.API(self.auth) - - self.run() - - def handle_DMs(self, new_only=True): - if new_only and self.history.get('last_dm_id', None): - dms = self.api.direct_messages(since_id=self.history['last_dm_id']) - else: - dms = self.api.direct_messages() - - if dms: - self.history['last_dm_id'] = dms[0].id - - return dms - - def handle_mentions(self, new_only=True): - if new_only and self.history.get('last_mention_id', None): - mentions = self.api.mentions_timeline(since_id=self.history['last_mention_id']) - else: - mentions = self.api.mentions_timeline() - - if mentions: - self.history['last_mention_id'] = mentions[0].id - - return mentions - - def search(self, query, lang='en'): - return self.api.search(q=query, lang=lang) - - def handle_stream(self): - return self.api.home_timeline() - - def handle_followers(self): # TODO - pass - - def process_tweets(self): - http_re = re.compile(r'http://\S+') - processed_tweets = [] - for tweet in self.tweets: - processed_tweets.append(tweet) - self.tweets = processed_tweets - - def publish_tweets(self, limit=None): - tweeted_count = 0 - - if self.tweets: - for twt in self.tweets: - try: - (tweet, reply_id) = twt - except ValueError: - tweet = twt - reply_id = None - - if self.debug_mode: - print("FAKETWEET: " + tweet[:140]) # for debug mode - else: - try: - if limit: - if tweeted_count >= limit: - continue - else: - status = self.api.update_status(tweet[:140], reply_id) # cap length at 140 chars - self.history['last_tweet_id'] = status.id - tweeted_count += 1 - except tweepy.error.TweepError: # prob a duplicate - pass - - def publish_dms(self): - if self.dms: - for (handle, msg) in self.dms: - user = self.api.get_user(screen_name=handle) - self.api.send_direct_message(screen_name=handle, text=msg) - - def authenticate(self): - print(self.auth.get_authorization_url()) - verifier = raw_input('Verification code: ') - try: - self.auth.get_access_token(verifier) - except tweepy.TweepError: - print('Error: failed to get access token.') - - self.settings['key'] = self.auth.access_token - self.settings['secret'] = self.auth.access_token_secret - - def follow_users(self): - for handle in self.follow_handles: - try: - user = self.api.get_user(screen_name=handle) - user.follow() - except tweepy.error.TweepError: # no such user? - continue - - def run(self): - pass - - def wrap_up(self, tweet_limit=None): - self.process_tweets() - self.follow_users() - self.publish_tweets(tweet_limit) - self.publish_dms() - pickle.dump(self.history, open(self.history_filename, 'w')) - - -if __name__ == '__main__': - pass diff --git a/botomatic/example_bots/bc_l.py b/botomatic/example_bots/bc_l.py index 695bf22..7a06c94 100644 --- a/botomatic/example_bots/bc_l.py +++ b/botomatic/example_bots/bc_l.py @@ -1,12 +1,14 @@ from botomatic import TBot import subprocess + class bc_l(TBot): def __init__(self): handle = "bc_l" super(bc_l, self).__init__(handle) - def bc_l(self, input_text): + @staticmethod + def bc_l(input_text): p = subprocess.Popen("bc -l", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) try: out, err = p.communicate(input_text + "\n") @@ -15,7 +17,6 @@ def bc_l(self, input_text): return out - def run(self): for dm in self.handle_DMs(): out = self.bc_l(dm.text) @@ -30,8 +31,8 @@ def run(self): reply = "@%s %s = %s" % (msg.user.screen_name, expression, out.strip()) self.tweets.append(reply) - self.wrap_up() + if __name__ == '__main__': b = bc_l() diff --git a/botomatic/example_bots/bookbookgoose.py b/botomatic/example_bots/bookbookgoose.py index 81ad287..9b5f926 100644 --- a/botomatic/example_bots/bookbookgoose.py +++ b/botomatic/example_bots/bookbookgoose.py @@ -1,15 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + import csv import random from botomatic import TBot data_file = "kindle.csv" + class BookBookGooseBot(TBot): - debug_mode = False - def __init__(self): + def __init__(self, **kwargs): handle = "bookbookgoose" - super(BookBookGooseBot, self).__init__(handle) + super(BookBookGooseBot, self).__init__(handle, **kwargs) def run(self): r = csv.reader(open(data_file)) @@ -23,5 +26,6 @@ def run(self): self.wrap_up() + if __name__ == '__main__': b = BookBookGooseBot() diff --git a/botomatic/example_bots/magic8ball.py b/botomatic/example_bots/magic8ball.py index afe9c88..b5b96ba 100644 --- a/botomatic/example_bots/magic8ball.py +++ b/botomatic/example_bots/magic8ball.py @@ -1,5 +1,7 @@ -import random +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import random from botomatic import TBot RESPONSES = ['It is certain', 'It is decidedly so', 'Without a doubt', 'Yes definitely', 'You may rely on it', 'As I see it yes', @@ -7,12 +9,12 @@ 'Better not tell you now', 'Cannot predict now', 'Concentrate and ask again', 'Don\'t count on it', 'My reply is no', 'My sources say no', 'Outlook not so good', 'Very doubtful'] + class Magic8Ball(TBot): - debug_mode = False - def __init__(self): + def __init__(self, **kwargs): handle = "dodecaDecider" - super(Magic8Ball, self).__init__(handle) + super(Magic8Ball, self).__init__(handle, **kwargs) def run(self): for msg in self.handle_mentions(): @@ -21,6 +23,7 @@ def run(self): self.wrap_up() + if __name__ == '__main__': m = Magic8Ball() diff --git a/botomatic/example_bots/myuberdriver.py b/botomatic/example_bots/myuberdriver.py index 5e6ceb9..046e3cf 100644 --- a/botomatic/example_bots/myuberdriver.py +++ b/botomatic/example_bots/myuberdriver.py @@ -1,38 +1,34 @@ -import urllib -import urllib2 -import json -import re -import sys +#!/usr/bin/env python +# -*- coding: utf-8 -*- from botomatic import TBot + class MyUberDriver(TBot): - debug_mode = False - def __init__(self): + def __init__(self, **kwargs): handle = "myuberdriverbot" self.query = "my uber driver" - super(MyUberDriver, self).__init__(handle) - + super(MyUberDriver, self).__init__(handle, **kwargs) def run(self): # find other people's stuff on twitter and fav/retweet for tweet in self.search("my uber driver"): - if tweet.lang != 'en': # english only + if tweet.lang != 'en': # english only continue if "uber driver" not in tweet.text: continue try: - tweet.retweet() # only retweet one each time the script is run + tweet.retweet() # only retweet one each time the script is run except: pass break - self.wrap_up() + if __name__ == '__main__': r = MyUberDriver() diff --git a/botomatic/example_bots/protip.py b/botomatic/example_bots/protip.py index d96e9ad..6cc19a8 100644 --- a/botomatic/example_bots/protip.py +++ b/botomatic/example_bots/protip.py @@ -1,9 +1,11 @@ -import pickle +#!/usr/bin/env python +# -*- coding: utf-8 -*- + import tweepy from botomatic import TBot + class Protip(TBot): - debug_mode = True def __init__(self): handle = "protipbot" @@ -14,11 +16,11 @@ def run(self): for result in results: try: result.retweet() - except tweepy.error.TweepError: # private status update? + except tweepy.error.TweepError: # private status update? continue - self.wrap_up() + if __name__ == '__main__': p = Protip() diff --git a/botomatic/settings.py b/botomatic/settings.py new file mode 100644 index 0000000..799252f --- /dev/null +++ b/botomatic/settings.py @@ -0,0 +1,5 @@ +import os + +CONSUMER_KEY = os.environ.get('CONSUMER_KEY') +CONSUMER_SECRET = os.environ.get('CONSUMER_SECRET') +DEBUG_MODE = True