Standardized reports; moved flow() logic to crawl(), repost(), & post(); bots don't own Trigger anymore

remotes/1705286528371406548/stable1
b3yond 2018-01-18 11:41:08 +01:00
parent 9f060b405e
commit ff73c5dc21
4 changed files with 127 additions and 73 deletions

36
report.py Normal file
View 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

View File

@ -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!")

View File

@ -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!")

View File

@ -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!")