2017-06-17 17:55:52 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
import twitter
|
|
|
|
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
|
2017-07-11 20:05:46 +00:00
|
|
|
import traceback
|
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
|
|
|
|
triggers: a list of words, one of them has to be in a tweet for it to be
|
|
|
|
retweeted
|
2017-06-17 17:55:52 +00:00
|
|
|
last_mention: the ID of the last tweet which mentioned you
|
|
|
|
"""
|
|
|
|
|
2017-06-25 16:06:33 +00:00
|
|
|
def __init__(self, trigger, config,
|
2017-06-25 17:31:40 +00:00
|
|
|
historypath="last_mention"):
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
|
|
|
Initializes the bot and loads all the necessary data.
|
|
|
|
|
2017-06-25 17:31:40 +00:00
|
|
|
:param historypath: Path to the file with ID of the last retweeted
|
|
|
|
Tweet
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
2017-06-25 16:06:33 +00:00
|
|
|
self.config = config
|
|
|
|
keys = self.get_api_keys()
|
2017-06-17 18:34:18 +00:00
|
|
|
self.api = twitter.Api(consumer_key=keys[0],
|
|
|
|
consumer_secret=keys[1],
|
|
|
|
access_token_key=keys[2],
|
|
|
|
access_token_secret=keys[3])
|
2017-06-17 17:55:52 +00:00
|
|
|
self.historypath = historypath
|
2017-06-25 17:31:40 +00:00
|
|
|
try:
|
|
|
|
self.user_id = self.config['tapp']['shutdown_contact_userid']
|
|
|
|
self.screen_name = \
|
|
|
|
self.config['tapp']['shutdown_contact_screen_name']
|
|
|
|
except KeyError:
|
|
|
|
self.no_shutdown_contact = True
|
2017-06-17 20:32:20 +00:00
|
|
|
self.last_mention = self.get_history(self.historypath)
|
2017-06-17 19:35:47 +00:00
|
|
|
self.trigger = trigger
|
2017-06-17 17:55:52 +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-06-25 16:06:33 +00:00
|
|
|
After you received keys, store them in your ticketfrei.cfg like this:
|
|
|
|
[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-06-17 19:35:47 +00:00
|
|
|
keys = []
|
2017-06-25 16:06:33 +00:00
|
|
|
keys.append(self.config['tapp']['consumer_key'])
|
|
|
|
keys.append(self.config['tapp']['consumer_secret'])
|
|
|
|
keys.append(self.config['tuser']['access_token_key'])
|
|
|
|
keys.append(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
|
|
|
|
2017-07-19 10:17:15 +00:00
|
|
|
def save_last_mention(self):
|
|
|
|
""" Saves the last retweeted tweet in last_mention. """
|
|
|
|
with open(self.historypath, "w") as f:
|
|
|
|
f.write(str(self.last_mention))
|
|
|
|
|
2017-06-17 19:35:47 +00:00
|
|
|
def format_mastodon(self, status):
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
|
|
|
Bridge your Retweets to mastodon.
|
|
|
|
:todo vmann: add all the mastodon API magic.
|
|
|
|
|
|
|
|
:param status: Object of a tweet.
|
2017-06-25 17:31:40 +00:00
|
|
|
:return: toot: text tooted on mastodon, e.g. "_b3yond: There are
|
|
|
|
uniformed controllers in the U2 at Opernhaus."
|
2017-06-17 17:55:52 +00:00
|
|
|
"""
|
|
|
|
toot = status.user.name + ": " + status.text
|
|
|
|
return toot
|
|
|
|
|
2017-06-17 18:16:03 +00:00
|
|
|
def crawl_mentions(self):
|
2017-06-17 18:34:18 +00:00
|
|
|
"""
|
|
|
|
crawls all Tweets which mention the bot from the twitter rest API.
|
|
|
|
|
|
|
|
:return: list of Status objects
|
|
|
|
"""
|
2017-06-17 19:35:47 +00:00
|
|
|
while 1:
|
2017-06-17 18:16:03 +00:00
|
|
|
try:
|
|
|
|
mentions = self.api.GetMentions(since_id=self.last_mention)
|
2017-06-17 19:35:47 +00:00
|
|
|
return mentions
|
2017-06-17 22:35:34 +00:00
|
|
|
except twitter.TwitterError:
|
2017-07-19 10:17:15 +00:00
|
|
|
traceback.print_exc()
|
2017-06-17 22:35:34 +00:00
|
|
|
sleep(60)
|
2017-06-17 18:16:03 +00:00
|
|
|
except requests.exceptions.ConnectionError:
|
|
|
|
print("[ERROR] Bad Connection.")
|
|
|
|
sleep(10)
|
|
|
|
|
|
|
|
def retweet(self, status):
|
2017-06-17 18:34:18 +00:00
|
|
|
"""
|
|
|
|
Retweets a given tweet.
|
|
|
|
|
|
|
|
:param status: A tweet 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-06-17 23:06:59 +00:00
|
|
|
self.api.PostRetweet(status.id)
|
2017-07-19 10:17:15 +00:00
|
|
|
if status.id > self.last_mention:
|
|
|
|
self.last_mention = status.id
|
2017-06-17 19:35:47 +00:00
|
|
|
return self.format_mastodon(status)
|
2017-06-25 17:31:40 +00:00
|
|
|
# maybe one day we get rid of this error. If not, try to uncomment
|
|
|
|
# these lines.
|
2017-06-17 23:06:59 +00:00
|
|
|
except twitter.error.TwitterError:
|
2017-07-19 10:32:32 +00:00
|
|
|
traceback.print_exc()
|
|
|
|
print("[ERROR] probably you already retweeted this tweet: " + status.text)
|
2017-07-19 10:17:15 +00:00
|
|
|
if status.id > self.last_mention:
|
|
|
|
self.last_mention = status.id
|
2017-07-11 20:49:24 +00:00
|
|
|
return None
|
2017-06-17 18:16:03 +00:00
|
|
|
except requests.exceptions.ConnectionError:
|
2017-07-19 10:32:32 +00:00
|
|
|
traceback.print_exc()
|
2017-06-17 18:16:03 +00:00
|
|
|
print("[ERROR] Bad Connection.")
|
|
|
|
sleep(10)
|
|
|
|
|
2017-06-17 19:35:47 +00:00
|
|
|
def tweet(self, post):
|
|
|
|
"""
|
|
|
|
Tweet a post.
|
|
|
|
|
|
|
|
:param post: String with the text to tweet.
|
|
|
|
"""
|
2017-07-11 21:50:55 +00:00
|
|
|
if len(post) > 140:
|
|
|
|
post = post[:140 - 4] + u' ...'
|
2017-06-17 19:35:47 +00:00
|
|
|
while 1:
|
|
|
|
try:
|
|
|
|
self.api.PostUpdate(status=post)
|
|
|
|
return
|
|
|
|
except requests.exceptions.ConnectionError:
|
2017-07-19 10:32:32 +00:00
|
|
|
traceback.print_exc()
|
2017-06-17 19:35:47 +00:00
|
|
|
print("[ERROR] Bad Connection.")
|
|
|
|
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)
|
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
|
|
|
|
mentions = self.crawl_mentions()
|
2017-06-17 19:35:47 +00:00
|
|
|
mastodon = []
|
2017-06-17 18:16:03 +00:00
|
|
|
|
|
|
|
for status in mentions:
|
|
|
|
# Is the Text of the Tweet in the triggerlist?
|
2017-06-25 17:15:38 +00:00
|
|
|
if self.trigger.is_ok(status.text):
|
2017-06-17 20:11:44 +00:00
|
|
|
# Retweet status
|
2017-07-11 20:49:24 +00:00
|
|
|
toot = self.retweet(status)
|
|
|
|
if toot:
|
|
|
|
mastodon.append(toot)
|
2017-06-17 18:16:03 +00:00
|
|
|
|
|
|
|
# save the id so it doesn't get crawled again
|
2017-07-19 10:32:32 +00:00
|
|
|
if status.id > self.last_mention:
|
|
|
|
self.last_mention = status.id
|
2017-07-19 10:17:15 +00:00
|
|
|
self.save_last_mention()
|
2017-06-17 20:11:44 +00:00
|
|
|
# Return Retweets for tooting on mastodon
|
2017-06-17 19:35:47 +00:00
|
|
|
return mastodon
|
2017-06-17 18:16:03 +00:00
|
|
|
|
|
|
|
def shutdown(self):
|
2017-06-25 17:31:40 +00:00
|
|
|
""" If something breaks, it shuts down the bot and messages the owner.
|
|
|
|
"""
|
2017-06-25 17:15:38 +00:00
|
|
|
print("[ERROR] Shit went wrong, closing down.")
|
2017-06-25 17:31:40 +00:00
|
|
|
if self.no_shutdown_contact:
|
|
|
|
return
|
2017-07-19 10:17:15 +00:00
|
|
|
self.save_last_mention()
|
2017-06-25 17:31:40 +00:00
|
|
|
self.api.PostDirectMessage("Help! I broke down. restart me pls :$",
|
|
|
|
self.user_id, self.screen_name)
|
2017-06-17 17:55:52 +00:00
|
|
|
|
|
|
|
|
2017-06-17 20:32:20 +00:00
|
|
|
if __name__ == "__main__":
|
2017-06-17 17:55:52 +00:00
|
|
|
# create an Api object
|
2017-06-17 23:33:47 +00:00
|
|
|
with open('ticketfrei.cfg') as configfile:
|
|
|
|
config = toml.load(configfile)
|
|
|
|
|
|
|
|
trigger = trigger.Trigger(config)
|
|
|
|
|
2017-06-25 16:31:16 +00:00
|
|
|
bot = RetweetBot(trigger, config)
|
2017-06-25 19:42:06 +00:00
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
bot.flow()
|
2017-07-11 22:12:26 +00:00
|
|
|
sleep(10)
|
2017-07-11 20:05:46 +00:00
|
|
|
except:
|
2017-07-19 10:17:15 +00:00
|
|
|
traceback.print_exc()
|
2017-06-25 19:42:06 +00:00
|
|
|
bot.shutdown()
|