Merge branch 'multi-deployment' of https://github.com/b3yond/ticketfrei into multi-deployment

This commit is contained in:
Tech 2018-03-28 23:33:43 +02:00
commit 5db529702c
11 changed files with 171 additions and 182 deletions

View file

@ -20,14 +20,13 @@ class Mailbot(object):
other bots that it received mails. 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 Creates a Bot who listens to mails and forwards them to other
bots. bots.
:param config: (dictionary) config.toml as a dictionary of dictionaries
""" """
self.user = User(db, uid) self.user = User(uid)
try: try:
self.last_mail = self.user.get_seen_mail() self.last_mail = self.user.get_seen_mail()
@ -115,7 +114,7 @@ class Mailbot(object):
:param status: (report.Report object) :param status: (report.Report object)
""" """
mailer = sendmail.Mailer(config) mailer = sendmail.Mailer()
mailer.send(status.format(), self.mailinglist, mailer.send(status.format(), self.mailinglist,
"Warnung: Kontrolleure gesehen") "Warnung: Kontrolleure gesehen")

View file

@ -26,10 +26,9 @@ class MastodonBot(Bot):
return mentions return mentions
for status in notifications: for status in notifications:
if (status['type'] == 'mention' and if (status['type'] == 'mention' and
status['status']['id'] > self.seen_toots): status['status']['id'] > user.get_seen_toot()):
# save state # save state
self.seen_toots = status['status']['id'] user.save_seen_toot(status['status']['id'])
self.save_last()
# add mention to mentions # add mention to mentions
text = re.sub(r'<[^>]*>', '', status['status']['content']) text = re.sub(r'<[^>]*>', '', status['status']['content'])
text = re.sub( text = re.sub(

View file

@ -1,193 +1,71 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from config import config
import logging import logging
import tweepy import tweepy
import re import re
import requests import requests
from time import sleep
import report import report
from user import User from bot import Bot
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TwitterBot(object): class TwitterBot(Bot):
""" def get_api(self, user):
This bot retweets all tweets which keys = user.get_api_keys()
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()
auth = tweepy.OAuthHandler(consumer_key=keys[0], auth = tweepy.OAuthHandler(consumer_key=keys[0],
consumer_secret=keys[1]) consumer_secret=keys[1])
auth.set_access_token(keys[2], # access_token_key auth.set_access_token(keys[2], # access_token_key
keys[3]) # access_token_secret keys[3]) # access_token_secret
self.api = tweepy.API(auth) return tweepy.API(auth)
self.last_mention = self.user.get_seen_tweet() def crawl(self, user):
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):
""" """
crawls all Tweets which mention the bot from the twitter rest API. crawls all Tweets which mention the bot from the twitter rest API.
:return: reports: (list of report.Report objects) :return: reports: (list of report.Report objects)
""" """
reports = [] reports = []
api = self.get_api(user)
last_mention = user.get_seen_tweet()
try: try:
if not self.waiting(): if last_mention == 0:
if self.last_mention == 0: mentions = api.mentions_timeline()
mentions = self.api.mentions_timeline() else:
else: mentions = api.mentions_timeline(
mentions = self.api.mentions_timeline( since_id=last_mention)
since_id=self.last_mention) for status in mentions:
for status in mentions: text = re.sub(
text = re.sub( "(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)",
"(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)", "", status.text)
"", status.text) reports.append(report.Report(status.author.screen_name,
reports.append(report.Report(status.author.screen_name, "twitter",
"twitter", text,
text, status.id,
status.id, status.created_at))
status.created_at)) user.save_seen_tweet(last_mention)
self.save_last() return reports
return reports
except tweepy.RateLimitError: except tweepy.RateLimitError:
logger.error("Twitter API Error: Rate Limit Exceeded", logger.error("Twitter API Error: Rate Limit Exceeded",
exc_info=True) exc_info=True)
self.waitcounter += 60*15 + 1 # :todo implement rate limiting
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
logger.error("Twitter API Error: Bad Connection", exc_info=True) logger.error("Twitter API Error: Bad Connection", exc_info=True)
self.waitcounter += 10
except tweepy.TweepError: except tweepy.TweepError:
logger.error("Twitter API Error: General Error", exc_info=True) logger.error("Twitter API Error: General Error", exc_info=True)
return [] return []
def repost(self, status): def post(self, user, report):
""" api = self.get_api(user)
Retweets a given tweet. try:
if report.source == self:
:param status: (report.Report object) api.retweet(report.id)
:return: toot: string of the tweet, to toot on mastodon. else:
""" # text = report.format()
while 1: if len(report.text) > 280:
try: text = report.text[:280 - 4] + u' ...'
self.api.retweet(status.id) except requests.exceptions.ConnectionError:
logger.info("Retweeted: " + status.format()) logger.error("Twitter API Error: Bad Connection",
if status.id > self.last_mention: exc_info=True)
self.last_mention = status.id # :todo implement rate limiting
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

View file

@ -33,7 +33,7 @@ if __name__ == '__main__':
time.sleep(60) # twitter rate limit >.< time.sleep(60) # twitter rate limit >.<
except: except:
logger.error('Shutdown', exc_info=True) logger.error('Shutdown', exc_info=True)
mailer = sendmail.Mailer(config) mailer = sendmail.Mailer()
try: try:
mailer.send('', config['web']['contact'], mailer.send('', config['web']['contact'],
'Ticketfrei Crash Report', 'Ticketfrei Crash Report',

4
bot.py
View file

@ -1,8 +1,8 @@
class Bot(object): class Bot(object):
# returns a list of Report objects # returns a list of Report objects
def crawl(user): def crawl(self, user):
pass pass
# post/boost Report object # post/boost Report object
def post(user, report): def post(self, user, report):
pass pass

View file

@ -10,9 +10,6 @@ import smtplib
from mastodon import Mastodon from mastodon import Mastodon
logger = logging.getLogger(__name__)
@get('/') @get('/')
@view('template/propaganda.tpl') @view('template/propaganda.tpl')
def propaganda(): def propaganda():
@ -153,6 +150,12 @@ def login_mastodon(user):
return dict(error='Login to Mastodon failed.') 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() application = bottle.default_app()
bottle.install(SessionPlugin('/')) bottle.install(SessionPlugin('/'))

104
mall bugfixes Normal file
View file

@ -0,0 +1,104 @@
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/twitterbot.py b/active_bots/twitterbot.py
index 787cdfb..065da45 100755
--- a/active_bots/twitterbot.py
+++ b/active_bots/twitterbot.py
@@ -24,16 +24,13 @@ class TwitterBot(object):
last_mention: the ID of the last tweet which mentioned you
"""

- def __init__(self, uid, db):
+ def __init__(self, uid):
"""
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)
+ self.user = User(uid)

# initialize API access
keys = self.get_api_keys()
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/user.py b/user.py
index c4e99e4..ce95cd3 100644
--- a/user.py
+++ b/user.py
@@ -53,7 +53,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=?;",

View file

@ -8,6 +8,7 @@ Students: usually already have a ticket, but may be solidaric
Leftist scene Leftist scene
* Flyers in alternative centers * Flyers in alternative centers
* Graffitis in alternative neighbourhoods * Graffitis in alternative neighbourhoods
* Posters in social centers
Schools: Schools:
* especially trade schools * especially trade schools

View file

@ -2,6 +2,7 @@
import smtplib import smtplib
import ssl import ssl
from config import config
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.application import MIMEApplication from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
@ -12,13 +13,12 @@ class Mailer(object):
Maintains the connection to the mailserver and sends text to users. 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 Creates an SMTP client to send a mail. Is called only once
when you actually want to send a mail. After you sent the when you actually want to send a mail. After you sent the
mail, the SMTP client is shut down again. 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 # This generates the From address by stripping the part until the first
# period from the mail server address and won't work always. # period from the mail server address and won't work always.
@ -65,9 +65,5 @@ class Mailer(object):
# For testing: # For testing:
if __name__ == '__main__': if __name__ == '__main__':
import prepare m = Mailer()
config = prepare.get_config()
m = Mailer(config)
print(m.send("This is a test mail.", m.fromaddr, "Test")) print(m.send("This is a test mail.", m.fromaddr, "Test"))

View file

@ -20,7 +20,7 @@ class SessionPlugin(object):
uid = request.get_cookie('uid', secret=db.secret) uid = request.get_cookie('uid', secret=db.secret)
if uid is None: if uid is None:
return redirect(self.loginpage) return redirect(self.loginpage)
kwargs[self.keyword] = User(db, uid) kwargs[self.keyword] = User(uid)
return callback(*args, **kwargs) return callback(*args, **kwargs)
return wrapper return wrapper

11
user.py
View file

@ -1,3 +1,4 @@
from config import config
from bottle import response from bottle import response
from db import db from db import db
import jwt import jwt
@ -53,7 +54,7 @@ class User(object):
return jwt.encode({ return jwt.encode({
'email': email, 'email': email,
'uid': self.uid 'uid': self.uid
}, self.secret).decode('ascii') }, db.secret).decode('ascii')
def is_appropriate(self, report): def is_appropriate(self, report):
db.execute("SELECT pattern FROM triggerpatterns WHERE user_id=?;", db.execute("SELECT pattern FROM triggerpatterns WHERE user_id=?;",
@ -81,6 +82,14 @@ class User(object):
instance = db.cur.fetchone() instance = db.cur.fetchone()
return instance[1], instance[2], row[0], instance[0] 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): def get_seen_toot(self):
db.execute("SELECT toot_id FROM seen_toots WHERE user_id = ?;", db.execute("SELECT toot_id FROM seen_toots WHERE user_id = ?;",
(self.uid, )) (self.uid, ))