ticketfrei/retweetbot.py

235 lines
7.6 KiB
Python
Executable File

#!/usr/bin/env python3
import tweepy
import requests
import pytoml as toml
import trigger
from time import sleep
import logging
import sendmail
logger = logging.getLogger(__name__)
class RetweetBot(object):
"""
This bot retweets all tweets which
1) mention him,
2) contain at least one of the triggerwords provided.
api: The api object, generated with your oAuth keys, responsible for
communication with twitter rest API
triggers: a list of words, one of them has to be in a tweet for it to be
retweeted
last_mention: the ID of the last tweet which mentioned you
"""
def __init__(self, trigger, config, history_path="last_mention"):
"""
Initializes the bot and loads all the necessary data.
:param trigger: object of the trigger
:param config: (dictionary) config.toml as a dictionary of dictionaries
:param logger: object of the logger
:param history_path: Path to the file with ID of the last retweeted
Tweet
"""
self.config = config
# initialize API access
keys = self.get_api_keys()
auth = tweepy.OAuthHandler(consumer_key=keys[0],
consumer_secret=keys[1])
auth.set_access_token(keys[2], # access_token_key
keys[3]) # access_token_secret
self.api = tweepy.API(auth)
self.history_path = history_path
self.last_mention = self.get_history(self.history_path)
self.trigger = trigger
self.waitcounter = 0
def get_api_keys(self):
"""
How to get these keys is described in doc/twitter_api.md
After you received keys, store them in your config.toml like this:
[tapp]
consumer_key = "..."
consumer_secret = "..."
[tuser]
access_token_key = "..."
access_token_secret = "..."
:return: keys: list of these 4 strings.
"""
keys = [self.config['tapp']['consumer_key'], self.config['tapp']['consumer_secret'],
self.config['tuser']['access_token_key'], self.config['tuser']['access_token_secret']]
return keys
def get_history(self, path):
""" This counter is needed to keep track of your mentions, so you
don't double RT them
:param path: string: contains path to the file where the ID of the
last_mention is stored.
:return: last_mention: ID of the last tweet which mentioned the bot
"""
try:
with open(path, "r+") as f:
last_mention = f.read()
except IOError:
with open(path, "w+") as f:
last_mention = "0"
f.write(last_mention)
return int(last_mention)
def save_last_mention(self):
""" Saves the last retweeted tweet in last_mention. """
with open(self.history_path, "w") as f:
f.write(str(self.last_mention))
def waiting(self):
"""
If the counter is not 0, you should be waiting instead.
:return: self.waitcounter(int): if 0, do smth.
"""
if self.waitcounter > 0:
sleep(1)
self.waitcounter -= 1
return self.waitcounter
def format_mastodon(self, status):
"""
Bridge your Retweets to mastodon.
:rtype: string
:param status: Object of a tweet.
:return: toot: text tooted on mastodon, e.g. "_b3yond: There are
uniformed controllers in the U2 at Opernhaus."
"""
toot = status.user.name + ": " + status.text
return toot
def crawl_mentions(self):
"""
crawls all Tweets which mention the bot from the twitter rest API.
:return: list of Status objects
"""
try:
if not self.waiting():
if self.last_mention == 0:
mentions = self.api.mentions_timeline()
else:
mentions = self.api.mentions_timeline(since_id=self.last_mention)
return mentions
except tweepy.RateLimitError:
logger.error("Twitter API Error: Rate Limit Exceeded", exc_info=True)
self.waitcounter += 60*15 + 1
except requests.exceptions.ConnectionError:
logger.error("Twitter API Error: Bad Connection", exc_info=True)
self.waitcounter += 10
return []
def retweet(self, status):
"""
Retweets a given tweet.
:param status: A tweet object.
:return: toot: string of the tweet, to toot on mastodon.
"""
while 1:
try:
self.api.retweet(status.id)
logger.info("Retweeted: " + self.format_mastodon(status))
if status.id > self.last_mention:
self.last_mention = status.id
return self.format_mastodon(status)
except requests.exceptions.ConnectionError:
logger.error("Twitter API Error: Bad Connection", exc_info=True)
sleep(10)
# maybe one day we get rid of this error:
except tweepy.TweepError:
logger.error("Twitter Error", exc_info=True)
if status.id > self.last_mention:
self.last_mention = status.id
return None
def tweet(self, post):
"""
Tweet a post.
:param post: String with the text to tweet.
"""
if len(post) > 280:
post = post[:280 - 4] + u' ...'
while 1:
try:
self.api.update_status(status=post)
return
except requests.exceptions.ConnectionError:
logger.error("Twitter API Error: Bad Connection", exc_info=True)
sleep(10)
def flow(self, to_tweet=()):
""" The flow of crawling mentions and retweeting them.
:param to_tweet: list of strings to tweet
:return list of retweeted tweets, to toot on mastodon
"""
# Tweet the toots the Retootbot gives to us
for post in to_tweet:
self.tweet(post)
# Store all mentions in a list of Status Objects
mentions = self.crawl_mentions()
mastodon = []
for status in mentions:
# Is the Text of the Tweet in the triggerlist?
if self.trigger.is_ok(status.text):
# Retweet status
toot = self.retweet(status)
if toot:
mastodon.append(toot)
# save the id so it doesn't get crawled again
if status.id > self.last_mention:
self.last_mention = status.id
self.save_last_mention()
# Return Retweets for tooting on mastodon
return mastodon
if __name__ == "__main__":
# create an Api object
with open('config.toml') as configfile:
config = toml.load(configfile)
fh = logging.FileHandler(config['logging']['logpath'])
fh.setLevel(logging.DEBUG)
logger.addHandler(fh)
trigger = trigger.Trigger(config)
bot = RetweetBot(trigger, config)
try:
while True:
bot.flow()
sleep(60)
except KeyboardInterrupt:
print("Good bye. Remember to restart the bot!")
except:
logger.error('Shutdown', exc_info=True)
bot.save_last_mention()
try:
mailer = sendmail.Mailer(config)
mailer.send('', config['mail']['contact'],
'Ticketfrei Crash Report',
attachment=config['logging']['logpath'])
except:
logger.error('Mail sending failed', exc_info=True)