2017-12-30 00:11:28 +00:00
|
|
|
#!/usr/bin/env python3
|
2017-06-17 17:55:52 +00:00
|
|
|
|
2017-12-30 00:11:28 +00:00
|
|
|
import tweepy
|
2018-01-18 13:48:53 +00:00
|
|
|
import re
|
2017-06-17 17:55:52 +00:00
|
|
|
import requests
|
2017-06-17 23:33:47 +00:00
|
|
|
import pytoml as toml
|
2017-06-17 19:35:47 +00:00
|
|
|
import trigger
|
2017-06-17 17:55:52 +00:00
|
|
|
from time import sleep
|
2018-01-18 12:54:32 +00:00
|
|
|
import report
|
2018-01-07 19:22:32 +00:00
|
|
|
import logging
|
|
|
|
import sendmail
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
2017-06-17 17:55:52 +00:00
|
|
|
|
|
|
|
|
2017-06-17 20:11:44 +00:00
|
|
|
class RetweetBot(object):
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
2017-06-17 18:34:18 +00:00
|
|
|
This bot retweets all tweets which
|
|
|
|
1) mention him,
|
|
|
|
2) contain at least one of the triggerwords provided.
|
2017-06-17 17:55:52 +00:00
|
|
|
|
2017-06-25 17:31:40 +00:00
|
|
|
api: The api object, generated with your oAuth keys, responsible for
|
|
|
|
communication with twitter rest API
|
2017-06-17 17:55:52 +00:00
|
|
|
last_mention: the ID of the last tweet which mentioned you
|
|
|
|
"""
|
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
def __init__(self, config, history_path="last_mention"):
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
|
|
|
Initializes the bot and loads all the necessary data.
|
|
|
|
|
2018-01-05 13:16:24 +00:00
|
|
|
:param config: (dictionary) config.toml as a dictionary of dictionaries
|
2018-01-05 16:13:41 +00:00
|
|
|
:param history_path: Path to the file with ID of the last retweeted
|
2017-06-25 17:31:40 +00:00
|
|
|
Tweet
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
2017-06-25 16:06:33 +00:00
|
|
|
self.config = config
|
2017-12-30 00:11:28 +00:00
|
|
|
|
|
|
|
# initialize API access
|
2017-06-25 16:06:33 +00:00
|
|
|
keys = self.get_api_keys()
|
2017-12-30 00:11:28 +00:00
|
|
|
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)
|
|
|
|
|
2018-01-05 16:13:41 +00:00
|
|
|
self.history_path = history_path
|
|
|
|
self.last_mention = self.get_history(self.history_path)
|
2017-09-17 17:07:04 +00:00
|
|
|
self.waitcounter = 0
|
2017-12-30 00:11:28 +00:00
|
|
|
|
2017-06-25 16:06:33 +00:00
|
|
|
def get_api_keys(self):
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
|
|
|
How to get these keys is described in doc/twitter_api.md
|
|
|
|
|
2017-12-30 09:32:20 +00:00
|
|
|
After you received keys, store them in your config.toml like this:
|
2017-06-25 16:06:33 +00:00
|
|
|
[tapp]
|
|
|
|
consumer_key = "..."
|
|
|
|
consumer_secret = "..."
|
|
|
|
|
|
|
|
[tuser]
|
|
|
|
access_token_key = "..."
|
|
|
|
access_token_secret = "..."
|
2017-06-17 18:34:18 +00:00
|
|
|
|
|
|
|
:return: keys: list of these 4 strings.
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
2017-12-30 00:11:28 +00:00
|
|
|
keys = [self.config['tapp']['consumer_key'], self.config['tapp']['consumer_secret'],
|
|
|
|
self.config['tuser']['access_token_key'], self.config['tuser']['access_token_secret']]
|
2017-06-17 17:55:52 +00:00
|
|
|
return keys
|
|
|
|
|
|
|
|
def get_history(self, path):
|
2017-06-25 17:31:40 +00:00
|
|
|
""" This counter is needed to keep track of your mentions, so you
|
|
|
|
don't double RT them
|
2017-06-17 18:34:18 +00:00
|
|
|
|
2017-06-25 17:31:40 +00:00
|
|
|
:param path: string: contains path to the file where the ID of the
|
|
|
|
last_mention is stored.
|
2017-06-17 18:34:18 +00:00
|
|
|
:return: last_mention: ID of the last tweet which mentioned the bot
|
|
|
|
"""
|
2017-06-17 20:32:20 +00:00
|
|
|
try:
|
|
|
|
with open(path, "r+") as f:
|
|
|
|
last_mention = f.read()
|
|
|
|
except IOError:
|
2017-07-19 10:32:32 +00:00
|
|
|
with open(path, "w+") as f:
|
|
|
|
last_mention = "0"
|
|
|
|
f.write(last_mention)
|
|
|
|
return int(last_mention)
|
2017-06-17 17:55:52 +00:00
|
|
|
|
2018-01-18 12:06:53 +00:00
|
|
|
def save_last(self):
|
2017-07-19 10:17:15 +00:00
|
|
|
""" Saves the last retweeted tweet in last_mention. """
|
2018-01-05 16:13:41 +00:00
|
|
|
with open(self.history_path, "w") as f:
|
2017-07-19 10:17:15 +00:00
|
|
|
f.write(str(self.last_mention))
|
|
|
|
|
2017-09-17 17:07:04 +00:00
|
|
|
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
|
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
def crawl(self):
|
2017-06-17 18:34:18 +00:00
|
|
|
"""
|
|
|
|
crawls all Tweets which mention the bot from the twitter rest API.
|
|
|
|
|
2018-01-18 12:54:32 +00:00
|
|
|
:return: reports: (list of report.Report objects)
|
2017-06-17 18:34:18 +00:00
|
|
|
"""
|
2018-01-18 12:54:32 +00:00
|
|
|
reports = []
|
2017-09-17 17:07:04 +00:00
|
|
|
try:
|
|
|
|
if not self.waiting():
|
2017-12-30 00:11:28 +00:00
|
|
|
if self.last_mention == 0:
|
|
|
|
mentions = self.api.mentions_timeline()
|
|
|
|
else:
|
|
|
|
mentions = self.api.mentions_timeline(since_id=self.last_mention)
|
2018-01-18 12:54:32 +00:00
|
|
|
for status in mentions:
|
2018-01-18 13:48:53 +00:00
|
|
|
text = re.sub("(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)", "", status.text)
|
2018-01-18 12:59:37 +00:00
|
|
|
reports.append(report.Report(status.author.screen_name,
|
|
|
|
"twitter",
|
2018-01-18 13:48:53 +00:00
|
|
|
text,
|
2018-01-18 12:59:37 +00:00
|
|
|
status.id,
|
|
|
|
status.created_at))
|
2018-01-18 14:14:04 +00:00
|
|
|
self.save_last()
|
2018-01-18 12:54:32 +00:00
|
|
|
return reports
|
2017-12-30 00:11:28 +00:00
|
|
|
except tweepy.RateLimitError:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error("Twitter API Error: Rate Limit Exceeded", exc_info=True)
|
2017-11-24 17:11:35 +00:00
|
|
|
self.waitcounter += 60*15 + 1
|
2017-09-17 17:07:04 +00:00
|
|
|
except requests.exceptions.ConnectionError:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
2017-09-17 17:07:04 +00:00
|
|
|
self.waitcounter += 10
|
2018-01-05 16:13:41 +00:00
|
|
|
return []
|
2017-06-17 18:16:03 +00:00
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
def repost(self, status):
|
2017-06-17 18:34:18 +00:00
|
|
|
"""
|
|
|
|
Retweets a given tweet.
|
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
:param status: (report.Report object)
|
2017-06-17 19:35:47 +00:00
|
|
|
:return: toot: string of the tweet, to toot on mastodon.
|
2017-06-17 18:34:18 +00:00
|
|
|
"""
|
2017-06-17 19:35:47 +00:00
|
|
|
while 1:
|
2017-06-17 18:16:03 +00:00
|
|
|
try:
|
2017-12-30 00:11:28 +00:00
|
|
|
self.api.retweet(status.id)
|
2018-01-18 10:41:08 +00:00
|
|
|
logger.info("Retweeted: " + status.format())
|
2017-07-19 10:17:15 +00:00
|
|
|
if status.id > self.last_mention:
|
|
|
|
self.last_mention = status.id
|
2018-01-18 10:41:08 +00:00
|
|
|
return status.format()
|
2017-06-17 18:16:03 +00:00
|
|
|
except requests.exceptions.ConnectionError:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
2017-06-17 18:16:03 +00:00
|
|
|
sleep(10)
|
2018-01-05 09:52:15 +00:00
|
|
|
# maybe one day we get rid of this error:
|
|
|
|
except tweepy.TweepError:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error("Twitter Error", exc_info=True)
|
2017-12-30 00:11:28 +00:00
|
|
|
if status.id > self.last_mention:
|
|
|
|
self.last_mention = status.id
|
|
|
|
return None
|
2017-06-17 18:16:03 +00:00
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
def post(self, status):
|
2017-06-17 19:35:47 +00:00
|
|
|
"""
|
|
|
|
Tweet a post.
|
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
:param status: (report.Report object)
|
2017-06-17 19:35:47 +00:00
|
|
|
"""
|
2018-01-18 10:41:08 +00:00
|
|
|
text = status.format()
|
|
|
|
if len(text) > 280:
|
|
|
|
text = status.text[:280 - 4] + u' ...'
|
2017-06-17 19:35:47 +00:00
|
|
|
while 1:
|
|
|
|
try:
|
2018-01-18 10:41:08 +00:00
|
|
|
self.api.update_status(status=text)
|
2017-06-17 19:35:47 +00:00
|
|
|
return
|
|
|
|
except requests.exceptions.ConnectionError:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
2017-06-17 19:35:47 +00:00
|
|
|
sleep(10)
|
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
def flow(self, trigger, to_tweet=()):
|
2017-06-17 19:35:47 +00:00
|
|
|
""" 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
|
|
|
|
"""
|
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
# Tweet the reports from other sources
|
2017-06-17 19:35:47 +00:00
|
|
|
for post in to_tweet:
|
2018-01-18 10:41:08 +00:00
|
|
|
self.post(post)
|
2017-06-17 17:55:52 +00:00
|
|
|
|
2017-06-17 18:16:03 +00:00
|
|
|
# Store all mentions in a list of Status Objects
|
2018-01-18 10:41:08 +00:00
|
|
|
mentions = self.crawl()
|
|
|
|
|
|
|
|
# initialise list of strings for other bots
|
|
|
|
all_tweets = []
|
2017-06-17 18:16:03 +00:00
|
|
|
|
2018-01-05 16:13:41 +00:00
|
|
|
for status in mentions:
|
|
|
|
# Is the Text of the Tweet in the triggerlist?
|
2018-01-18 10:41:08 +00:00
|
|
|
if trigger.is_ok(status.text):
|
2018-01-05 16:13:41 +00:00
|
|
|
# Retweet status
|
2018-01-18 10:41:08 +00:00
|
|
|
toot = self.repost(status)
|
2018-01-05 16:13:41 +00:00
|
|
|
if toot:
|
2018-01-18 10:41:08 +00:00
|
|
|
all_tweets.append(toot)
|
2018-01-05 16:13:41 +00:00
|
|
|
|
|
|
|
# save the id so it doesn't get crawled again
|
|
|
|
if status.id > self.last_mention:
|
|
|
|
self.last_mention = status.id
|
2018-01-18 12:06:53 +00:00
|
|
|
self.save_last()
|
2018-01-18 10:41:08 +00:00
|
|
|
# Return Retweets for posting on other bots
|
|
|
|
return all_tweets
|
2017-06-17 18:16:03 +00:00
|
|
|
|
2017-06-17 17:55:52 +00:00
|
|
|
|
2017-06-17 20:32:20 +00:00
|
|
|
if __name__ == "__main__":
|
2018-01-18 10:41:08 +00:00
|
|
|
# get the config dict of dicts
|
2017-12-30 09:32:20 +00:00
|
|
|
with open('config.toml') as configfile:
|
2017-06-17 23:33:47 +00:00
|
|
|
config = toml.load(configfile)
|
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
# set log file
|
2018-01-07 19:22:32 +00:00
|
|
|
fh = logging.FileHandler(config['logging']['logpath'])
|
|
|
|
fh.setLevel(logging.DEBUG)
|
|
|
|
logger.addHandler(fh)
|
2017-06-17 23:33:47 +00:00
|
|
|
|
2018-01-18 10:41:08 +00:00
|
|
|
# initialise trigger
|
2018-01-07 19:22:32 +00:00
|
|
|
trigger = trigger.Trigger(config)
|
2018-01-18 10:41:08 +00:00
|
|
|
|
|
|
|
# initialise twitter bot
|
2018-01-07 19:22:32 +00:00
|
|
|
bot = RetweetBot(trigger, config)
|
2018-01-18 10:41:08 +00:00
|
|
|
|
2017-06-25 19:42:06 +00:00
|
|
|
try:
|
|
|
|
while True:
|
2018-01-18 10:41:08 +00:00
|
|
|
# :todo separate into small functions
|
|
|
|
bot.flow(trigger)
|
2017-11-24 17:11:35 +00:00
|
|
|
sleep(60)
|
|
|
|
except KeyboardInterrupt:
|
2018-01-04 11:20:59 +00:00
|
|
|
print("Good bye. Remember to restart the bot!")
|
2017-07-11 20:05:46 +00:00
|
|
|
except:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error('Shutdown', exc_info=True)
|
2018-01-18 12:06:53 +00:00
|
|
|
bot.save_last()
|
2018-01-07 19:22:32 +00:00
|
|
|
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)
|