forked from ticketfrei/ticketfrei
Standardized reports; moved flow() logic to crawl(), repost(), & post(); bots don't own Trigger anymore
This commit is contained in:
parent
9f060b405e
commit
ff73c5dc21
36
report.py
Normal file
36
report.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
class Report(object):
|
||||
"""
|
||||
A ticketfrei report object.
|
||||
|
||||
Toots, Tweets, and E-Mails can be formed into ticketfrei reports.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, author, source, text, id, timestamp):
|
||||
"""
|
||||
Constructor of a ticketfrei report
|
||||
|
||||
:param author: username of the author
|
||||
:param source: mastodon, twitter, or email
|
||||
:param text: the text of the report
|
||||
:param id: id in the network
|
||||
:param timestamp: time of the report
|
||||
"""
|
||||
self.author = author
|
||||
self.type = source
|
||||
self.text = text
|
||||
self.timestamp = timestamp
|
||||
self.id = id
|
||||
|
||||
def format(self):
|
||||
"""
|
||||
Format the report for bot.post()
|
||||
|
||||
:rtype: string
|
||||
:return: toot: text to be tooted, e.g. "_b3yond: There are
|
||||
uniformed controllers in the U2 at Opernhaus."
|
||||
"""
|
||||
strng = self.author + ": " + self.text
|
||||
return strng
|
83
retootbot.py
83
retootbot.py
|
@ -9,14 +9,14 @@ import time
|
|||
import trigger
|
||||
import logging
|
||||
import sendmail
|
||||
import report
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RetootBot(object):
|
||||
def __init__(self, config, trigger):
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.trigger = trigger
|
||||
self.client_id = self.register()
|
||||
self.m = self.login()
|
||||
|
||||
|
@ -53,34 +53,61 @@ class RetootBot(object):
|
|||
)
|
||||
return m
|
||||
|
||||
def retoot(self, toots=()):
|
||||
def crawl(self):
|
||||
"""
|
||||
Crawl mentions from Mastodon.
|
||||
|
||||
:return: list of statuses
|
||||
"""
|
||||
all = self.m.notifications()
|
||||
mentions = []
|
||||
for status in all:
|
||||
if (status['type'] == 'mention' and status['status']['id'] not in self.seen_toots):
|
||||
# save state
|
||||
self.seen_toots.add(status['status']['id'])
|
||||
with os.fdopen(os.open('seen_toots.pickle.part', os.O_WRONLY | os.O_EXCL | os.O_CREAT), 'wb') as f:
|
||||
pickle.dump(self.seen_toots, f)
|
||||
os.rename('seen_toots.pickle.part', 'seen_toots.pickle')
|
||||
# add mention to mentions
|
||||
mentions.append(report.Report(status['account']['acct'],
|
||||
"mastodon",
|
||||
re.sub(r'<[^>]*>', '', status['status']['content']),
|
||||
status['status']['id'],
|
||||
status['status']['created_at']))
|
||||
return mentions
|
||||
|
||||
def repost(self, mention):
|
||||
"""
|
||||
Retoots a mention.
|
||||
|
||||
:param mention: (report.Report object)
|
||||
"""
|
||||
logger.info('Boosting toot from %s' % (
|
||||
mention.format()))
|
||||
self.m.status_reblog(mention.id)
|
||||
|
||||
|
||||
def post(self, report):
|
||||
"""
|
||||
Toots a report from other sources.
|
||||
|
||||
:param report: (report.Report object)
|
||||
"""
|
||||
toot = report.format()
|
||||
self.m.toot(toot)
|
||||
|
||||
def flow(self, trigger, reports=()):
|
||||
# toot external provided messages
|
||||
for toot in toots:
|
||||
self.m.toot(toot)
|
||||
for report in reports:
|
||||
self.post(report)
|
||||
|
||||
# boost mentions
|
||||
retoots = []
|
||||
for notification in self.m.notifications():
|
||||
if (notification['type'] == 'mention'
|
||||
and notification['status']['id'] not in self.seen_toots):
|
||||
self.seen_toots.add(notification['status']['id'])
|
||||
text_content = re.sub(r'<[^>]*>', '',
|
||||
notification['status']['content'])
|
||||
if not self.trigger.is_ok(text_content):
|
||||
continue
|
||||
logger.info('Boosting toot from %s: %s' % (
|
||||
notification['status']['account']['acct'],
|
||||
notification['status']['content']))
|
||||
self.m.status_reblog(notification['status']['id'])
|
||||
retoots.append('%s: %s' % (
|
||||
notification['status']['account']['acct'],
|
||||
re.sub(r'@\S*', '', text_content)))
|
||||
# If the Mastodon instance returns interesting Errors, add them here:
|
||||
|
||||
# save state
|
||||
with os.fdopen(os.open('seen_toots.pickle.part', os.O_WRONLY | os.O_EXCL | os.O_CREAT), 'wb') as f:
|
||||
pickle.dump(self.seen_toots, f)
|
||||
os.rename('seen_toots.pickle.part', 'seen_toots.pickle')
|
||||
for mention in self.crawl():
|
||||
if not trigger.is_ok(mention.text):
|
||||
continue
|
||||
self.repost(mention)
|
||||
retoots.append(mention)
|
||||
|
||||
# return mentions for mirroring
|
||||
return retoots
|
||||
|
@ -96,11 +123,11 @@ if __name__ == '__main__':
|
|||
logger.addHandler(fh)
|
||||
|
||||
trigger = trigger.Trigger(config)
|
||||
bot = RetootBot(config, trigger)
|
||||
bot = RetootBot(config)
|
||||
|
||||
try:
|
||||
while True:
|
||||
bot.retoot()
|
||||
bot.flow(trigger)
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("Good bye. Remember to restart the bot!")
|
||||
|
|
|
@ -19,18 +19,14 @@ class RetweetBot(object):
|
|||
|
||||
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"):
|
||||
def __init__(self, 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
|
||||
"""
|
||||
|
@ -46,7 +42,6 @@ class RetweetBot(object):
|
|||
|
||||
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):
|
||||
|
@ -101,19 +96,7 @@ class RetweetBot(object):
|
|||
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):
|
||||
def crawl(self):
|
||||
"""
|
||||
crawls all Tweets which mention the bot from the twitter rest API.
|
||||
|
||||
|
@ -134,20 +117,20 @@ class RetweetBot(object):
|
|||
self.waitcounter += 10
|
||||
return []
|
||||
|
||||
def retweet(self, status):
|
||||
def repost(self, status):
|
||||
"""
|
||||
Retweets a given tweet.
|
||||
|
||||
:param status: A tweet object.
|
||||
:param status: (report.Report 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))
|
||||
logger.info("Retweeted: " + status.format())
|
||||
if status.id > self.last_mention:
|
||||
self.last_mention = status.id
|
||||
return self.format_mastodon(status)
|
||||
return status.format()
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
||||
sleep(10)
|
||||
|
@ -158,67 +141,76 @@ class RetweetBot(object):
|
|||
self.last_mention = status.id
|
||||
return None
|
||||
|
||||
def tweet(self, post):
|
||||
def post(self, status):
|
||||
"""
|
||||
Tweet a post.
|
||||
|
||||
:param post: String with the text to tweet.
|
||||
:param status: (report.Report object)
|
||||
"""
|
||||
if len(post) > 280:
|
||||
post = post[:280 - 4] + u' ...'
|
||||
text = status.format()
|
||||
if len(text) > 280:
|
||||
text = status.text[:280 - 4] + u' ...'
|
||||
while 1:
|
||||
try:
|
||||
self.api.update_status(status=post)
|
||||
self.api.update_status(status=text)
|
||||
return
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
||||
sleep(10)
|
||||
|
||||
def flow(self, to_tweet=()):
|
||||
def flow(self, trigger, 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
|
||||
# Tweet the reports from other sources
|
||||
for post in to_tweet:
|
||||
self.tweet(post)
|
||||
self.post(post)
|
||||
|
||||
# Store all mentions in a list of Status Objects
|
||||
mentions = self.crawl_mentions()
|
||||
mastodon = []
|
||||
mentions = self.crawl()
|
||||
|
||||
# initialise list of strings for other bots
|
||||
all_tweets = []
|
||||
|
||||
for status in mentions:
|
||||
# Is the Text of the Tweet in the triggerlist?
|
||||
if self.trigger.is_ok(status.text):
|
||||
if trigger.is_ok(status.text):
|
||||
# Retweet status
|
||||
toot = self.retweet(status)
|
||||
toot = self.repost(status)
|
||||
if toot:
|
||||
mastodon.append(toot)
|
||||
all_tweets.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
|
||||
# Return Retweets for posting on other bots
|
||||
return all_tweets
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# create an Api object
|
||||
# get the config dict of dicts
|
||||
with open('config.toml') as configfile:
|
||||
config = toml.load(configfile)
|
||||
|
||||
# set log file
|
||||
fh = logging.FileHandler(config['logging']['logpath'])
|
||||
fh.setLevel(logging.DEBUG)
|
||||
logger.addHandler(fh)
|
||||
|
||||
# initialise trigger
|
||||
trigger = trigger.Trigger(config)
|
||||
|
||||
# initialise twitter bot
|
||||
bot = RetweetBot(trigger, config)
|
||||
|
||||
try:
|
||||
while True:
|
||||
bot.flow()
|
||||
# :todo separate into small functions
|
||||
bot.flow(trigger)
|
||||
sleep(60)
|
||||
except KeyboardInterrupt:
|
||||
print("Good bye. Remember to restart the bot!")
|
||||
|
|
|
@ -9,7 +9,6 @@ from retootbot import RetootBot
|
|||
from retweetbot import RetweetBot
|
||||
from trigger import Trigger
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# read config in TOML format (https://github.com/toml-lang/toml#toml)
|
||||
with open('config.toml') as configfile:
|
||||
|
@ -21,14 +20,14 @@ if __name__ == '__main__':
|
|||
logger.addHandler(fh)
|
||||
|
||||
trigger = Trigger(config)
|
||||
mbot = RetootBot(config, trigger)
|
||||
tbot = RetweetBot(trigger, config)
|
||||
mbot = RetootBot(config)
|
||||
tbot = RetweetBot(config)
|
||||
|
||||
try:
|
||||
statuses = []
|
||||
while True:
|
||||
statuses = mbot.retoot(statuses)
|
||||
statuses = tbot.flow(statuses)
|
||||
statuses = mbot.flow(trigger, statuses)
|
||||
statuses = tbot.flow(trigger, to_tweet=statuses)
|
||||
time.sleep(60)
|
||||
except KeyboardInterrupt:
|
||||
print("Good bye. Remember to restart the bot!")
|
||||
|
|
Loading…
Reference in a new issue