diff --git a/active_bots/mailbot.py b/active_bots/mailbot.py index a98872a..a11231f 100644 --- a/active_bots/mailbot.py +++ b/active_bots/mailbot.py @@ -20,14 +20,13 @@ class Mailbot(object): other bots that it received mails. """ - def __init__(self, uid, db): + def __init__(self, uid): """ Creates a Bot who listens to mails and forwards them to other bots. - :param config: (dictionary) config.toml as a dictionary of dictionaries """ - self.user = User(db, uid) + self.user = User(uid) try: self.last_mail = self.user.get_seen_mail() @@ -115,7 +114,7 @@ class Mailbot(object): :param status: (report.Report object) """ - mailer = sendmail.Mailer(config) + mailer = sendmail.Mailer() mailer.send(status.format(), self.mailinglist, "Warnung: Kontrolleure gesehen") diff --git a/active_bots/mastodonbot.py b/active_bots/mastodonbot.py index ad741f5..c0e0240 100755 --- a/active_bots/mastodonbot.py +++ b/active_bots/mastodonbot.py @@ -26,10 +26,9 @@ class MastodonBot(Bot): return mentions for status in notifications: if (status['type'] == 'mention' and - status['status']['id'] > self.seen_toots): + status['status']['id'] > user.get_seen_toot()): # save state - self.seen_toots = status['status']['id'] - self.save_last() + user.save_seen_toot(status['status']['id']) # add mention to mentions text = re.sub(r'<[^>]*>', '', status['status']['content']) text = re.sub( diff --git a/active_bots/twitterbot.py b/active_bots/twitterbot.py index 787cdfb..051502b 100755 --- a/active_bots/twitterbot.py +++ b/active_bots/twitterbot.py @@ -1,193 +1,71 @@ #!/usr/bin/env python3 -from config import config import logging import tweepy import re import requests -from time import sleep import report -from user import User +from bot import Bot logger = logging.getLogger(__name__) -class TwitterBot(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 - last_mention: the ID of the last tweet which mentioned you - """ - - def __init__(self, uid, db): - """ - Initializes the bot and loads all the necessary data. - - :param config: (dictionary) config.toml as a dictionary of dictionaries - :param history_path: Path to the file with ID of the last retweeted - Tweet - """ - self.db = db - self.user = User(db, uid) - - # initialize API access - keys = self.get_api_keys() +class TwitterBot(Bot): + def get_api(self, user): + keys = user.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) + return tweepy.API(auth) - self.last_mention = self.user.get_seen_tweet() - 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 = [config['twitter']['consumer_key'], - config['twitter']['consumer_secret']] - row = self.user.get_twitter_token() - keys.append(row[0]) - keys.append(row[1]) - return keys - - def save_last(self): - """ Saves the last retweeted tweet in last_mention. """ - self.user.save_seen_tweet(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 crawl(self): + def crawl(self, user): """ crawls all Tweets which mention the bot from the twitter rest API. :return: reports: (list of report.Report objects) """ reports = [] + api = self.get_api(user) + last_mention = user.get_seen_tweet() 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) - for status in mentions: - text = re.sub( - "(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)", - "", status.text) - reports.append(report.Report(status.author.screen_name, - "twitter", - text, - status.id, - status.created_at)) - self.save_last() - return reports + if last_mention == 0: + mentions = api.mentions_timeline() + else: + mentions = api.mentions_timeline( + since_id=last_mention) + for status in mentions: + text = re.sub( + "(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)", + "", status.text) + reports.append(report.Report(status.author.screen_name, + "twitter", + text, + status.id, + status.created_at)) + user.save_seen_tweet(last_mention) + return reports except tweepy.RateLimitError: logger.error("Twitter API Error: Rate Limit Exceeded", exc_info=True) - self.waitcounter += 60*15 + 1 + # :todo implement rate limiting except requests.exceptions.ConnectionError: logger.error("Twitter API Error: Bad Connection", exc_info=True) - self.waitcounter += 10 except tweepy.TweepError: logger.error("Twitter API Error: General Error", exc_info=True) return [] - def repost(self, status): - """ - Retweets a given tweet. - - :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: " + status.format()) - if status.id > self.last_mention: - self.last_mention = status.id - self.save_last() - return status.format() - 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 - self.save_last() - return None - - def post(self, status): - """ - Tweet a post. - - :param status: (report.Report object) - """ - text = status.format() - if len(text) > 280: - text = status.text[:280 - 4] + u' ...' - while 1: - try: - 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, 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 reports from other sources - for post in to_tweet: - self.post(post) - - # Store all mentions in a list of Status Objects - 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 trigger.is_ok(status.text): - # Retweet status - toot = self.repost(status) - if toot: - all_tweets.append(toot) - - # Return Retweets for posting on other bots - return all_tweets + def post(self, user, report): + api = self.get_api(user) + try: + if report.source == self: + api.retweet(report.id) + else: + # text = report.format() + if len(report.text) > 280: + text = report.text[:280 - 4] + u' ...' + except requests.exceptions.ConnectionError: + logger.error("Twitter API Error: Bad Connection", + exc_info=True) + # :todo implement rate limiting diff --git a/backend.py b/backend.py index 5ed1efd..bba591a 100755 --- a/backend.py +++ b/backend.py @@ -33,7 +33,7 @@ if __name__ == '__main__': time.sleep(60) # twitter rate limit >.< except: logger.error('Shutdown', exc_info=True) - mailer = sendmail.Mailer(config) + mailer = sendmail.Mailer() try: mailer.send('', config['web']['contact'], 'Ticketfrei Crash Report', diff --git a/bot.py b/bot.py index 0f135f7..b003ab8 100644 --- a/bot.py +++ b/bot.py @@ -1,8 +1,8 @@ class Bot(object): # returns a list of Report objects - def crawl(user): + def crawl(self, user): pass # post/boost Report object - def post(user, report): + def post(self, user, report): pass diff --git a/frontend.py b/frontend.py index c13c999..b11fd55 100755 --- a/frontend.py +++ b/frontend.py @@ -10,9 +10,6 @@ import smtplib from mastodon import Mastodon -logger = logging.getLogger(__name__) - - @get('/') @view('template/propaganda.tpl') def propaganda(): @@ -153,6 +150,12 @@ def login_mastodon(user): return dict(error='Login to Mastodon failed.') +logpath = config['logging']['logpath'] +logger = logging.getLogger() +fh = logging.FileHandler(logpath) +fh.setLevel(logging.DEBUG) +logger.addHandler(fh) + application = bottle.default_app() bottle.install(SessionPlugin('/')) diff --git a/mall bugfixes b/mall bugfixes new file mode 100644 index 0000000..3ab1dcf --- /dev/null +++ b/mall bugfixes @@ -0,0 +1,104 @@ +[1mdiff --git a/active_bots/mailbot.py b/active_bots/mailbot.py[m +[1mindex a98872a..a11231f 100644[m +[1m--- a/active_bots/mailbot.py[m +[1m+++ b/active_bots/mailbot.py[m +[36m@@ -20,14 +20,13 @@[m [mclass Mailbot(object):[m + other bots that it received mails.[m + """[m + [m +[31m- def __init__(self, uid, db):[m +[32m+[m[32m def __init__(self, uid):[m + """[m + Creates a Bot who listens to mails and forwards them to other[m + bots.[m + [m +[31m- :param config: (dictionary) config.toml as a dictionary of dictionaries[m + """[m +[31m- self.user = User(db, uid)[m +[32m+[m[32m self.user = User(uid)[m + [m + try:[m + self.last_mail = self.user.get_seen_mail()[m +[36m@@ -115,7 +114,7 @@[m [mclass Mailbot(object):[m + [m + :param status: (report.Report object)[m + """[m +[31m- mailer = sendmail.Mailer(config)[m +[32m+[m[32m mailer = sendmail.Mailer()[m + mailer.send(status.format(), self.mailinglist,[m + "Warnung: Kontrolleure gesehen")[m + [m +[1mdiff --git a/active_bots/twitterbot.py b/active_bots/twitterbot.py[m +[1mindex 787cdfb..065da45 100755[m +[1m--- a/active_bots/twitterbot.py[m +[1m+++ b/active_bots/twitterbot.py[m +[36m@@ -24,16 +24,13 @@[m [mclass TwitterBot(object):[m + last_mention: the ID of the last tweet which mentioned you[m + """[m + [m +[31m- def __init__(self, uid, db):[m +[32m+[m[32m def __init__(self, uid):[m + """[m + Initializes the bot and loads all the necessary data.[m + [m +[31m- :param config: (dictionary) config.toml as a dictionary of dictionaries[m +[31m- :param history_path: Path to the file with ID of the last retweeted[m + Tweet[m + """[m +[31m- self.db = db[m +[31m- self.user = User(db, uid)[m +[32m+[m[32m self.user = User(uid)[m + [m + # initialize API access[m + keys = self.get_api_keys()[m +[1mdiff --git a/sendmail.py b/sendmail.py[m +[1mindex df91d1d..93028d9 100755[m +[1m--- a/sendmail.py[m +[1m+++ b/sendmail.py[m +[36m@@ -2,6 +2,7 @@[m + [m + import smtplib[m + import ssl[m +[32m+[m[32mfrom config import config[m + from email.mime.text import MIMEText[m + from email.mime.application import MIMEApplication[m + from email.mime.multipart import MIMEMultipart[m +[36m@@ -12,13 +13,12 @@[m [mclass Mailer(object):[m + Maintains the connection to the mailserver and sends text to users.[m + """[m + [m +[31m- def __init__(self, config):[m +[32m+[m[32m def __init__(self):[m + """[m + Creates an SMTP client to send a mail. Is called only once[m + when you actually want to send a mail. After you sent the[m + mail, the SMTP client is shut down again.[m + [m +[31m- :param config: The config file generated from config.toml[m + """[m + # This generates the From address by stripping the part until the first[m + # period from the mail server address and won't work always.[m +[36m@@ -65,9 +65,5 @@[m [mclass Mailer(object):[m + [m + # For testing:[m + if __name__ == '__main__':[m +[31m- import prepare[m +[31m-[m +[31m- config = prepare.get_config()[m +[31m-[m +[31m- m = Mailer(config)[m +[32m+[m[32m m = Mailer()[m + print(m.send("This is a test mail.", m.fromaddr, "Test"))[m +[1mdiff --git a/user.py b/user.py[m +[1mindex c4e99e4..ce95cd3 100644[m +[1m--- a/user.py[m +[1m+++ b/user.py[m +[36m@@ -53,7 +53,7 @@[m [mclass User(object):[m + return jwt.encode({[m + 'email': email,[m + 'uid': self.uid[m +[31m- }, self.secret).decode('ascii')[m +[32m+[m[32m }, db.secret).decode('ascii')[m + [m + def is_appropriate(self, report):[m + db.execute("SELECT pattern FROM triggerpatterns WHERE user_id=?;",[m diff --git a/promotion/README.md b/promotion/README.md index 2c69555..5b89d99 100644 --- a/promotion/README.md +++ b/promotion/README.md @@ -8,6 +8,7 @@ Students: usually already have a ticket, but may be solidaric Leftist scene * Flyers in alternative centers * Graffitis in alternative neighbourhoods +* Posters in social centers Schools: * especially trade schools diff --git a/sendmail.py b/sendmail.py index df91d1d..93028d9 100755 --- a/sendmail.py +++ b/sendmail.py @@ -2,6 +2,7 @@ import smtplib import ssl +from config import config from email.mime.text import MIMEText from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart @@ -12,13 +13,12 @@ class Mailer(object): Maintains the connection to the mailserver and sends text to users. """ - def __init__(self, config): + def __init__(self): """ Creates an SMTP client to send a mail. Is called only once when you actually want to send a mail. After you sent the mail, the SMTP client is shut down again. - :param config: The config file generated from config.toml """ # This generates the From address by stripping the part until the first # period from the mail server address and won't work always. @@ -65,9 +65,5 @@ class Mailer(object): # For testing: if __name__ == '__main__': - import prepare - - config = prepare.get_config() - - m = Mailer(config) + m = Mailer() print(m.send("This is a test mail.", m.fromaddr, "Test")) diff --git a/session.py b/session.py index 5c68cfd..5d0c263 100644 --- a/session.py +++ b/session.py @@ -20,7 +20,7 @@ class SessionPlugin(object): uid = request.get_cookie('uid', secret=db.secret) if uid is None: return redirect(self.loginpage) - kwargs[self.keyword] = User(db, uid) + kwargs[self.keyword] = User(uid) return callback(*args, **kwargs) return wrapper diff --git a/user.py b/user.py index c4e99e4..e4edf10 100644 --- a/user.py +++ b/user.py @@ -1,3 +1,4 @@ +from config import config from bottle import response from db import db import jwt @@ -53,7 +54,7 @@ class User(object): return jwt.encode({ 'email': email, 'uid': self.uid - }, self.secret).decode('ascii') + }, db.secret).decode('ascii') def is_appropriate(self, report): db.execute("SELECT pattern FROM triggerpatterns WHERE user_id=?;", @@ -81,6 +82,14 @@ class User(object): instance = db.cur.fetchone() return instance[1], instance[2], row[0], instance[0] + def get_twitter_credentials(self): + keys = [config['twitter']['consumer_key'], + config['twitter']['consumer_secret']] + row = self.get_twitter_token() + keys.append(row[0]) + keys.append(row[1]) + return keys + def get_seen_toot(self): db.execute("SELECT toot_id FROM seen_toots WHERE user_id = ?;", (self.uid, ))