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

master
Tech 2018-03-26 20:55:23 +02:00
commit 5d9dc443d2
14 changed files with 286 additions and 306 deletions

157
README.md
View File

@ -1,114 +1,107 @@
# Ticketfrei social bot
Version: 1.0
Version: 2.0beta
Ticketfrei is a mastodon/twitter/mail bot to dodge ticket controllers
in public transport systems.
Ticketfrei is a mastodon/twitter/mail bot to dodge ticket controllers in public
transport systems.
The functionality is simple: it retweets every tweet where it is
mentioned.
The functionality is simple: it retweets every tweet where it is mentioned.
This leads to a community which evolves around it; if you see ticket
controllers, you tweet their location and mention the bot. The bot
then retweets your tweet and others can read the info and think twice
if they want to buy a ticket. If enough people, a critical mass,
participate for the bot to become reliable, you have positive
self-reinforcing dynamics.
controllers, you tweet their location and mention the bot. The bot then
retweets your tweet and others can read the info and think twice if they want
to buy a ticket. If enough people, a critical mass, participate for the bot to
become reliable, you have positive self-reinforcing dynamics.
In the promotion folder, you will find some promotion material you
can use to build up such a community in your city. It is in german
though =/
Today, you can use a Twitter, a Mastodon, and Mail with the account. They will
communicate with each other; if someone warns others via Mail, Twitter and
Mastodon users will also see the message. And vice versa.
Website: https://wiki.links-tech.org/IT/Ticketfrei
In version 2, this bot has received a frontend website. On this website, people
can register an own bot for their city - the website manages multiple bots for
multiple citys. This way, you do not have to host it yourself.
## Install
In the promotion folder, you will find some (german) promotion material you can
use to build up such a community in your city.
Setting up a ticketfrei bot for your city is quite easy. Here are the
few steps:
Website: ticketfrei.links-tech.org
First you need to install python3 and virtualenv with your favourite
package manager.
More information: https://wiki.links-tech.org/IT/Ticketfrei
Create and activate virtualenv:
## Do you want Ticketfrei in your city?
```shell
sudo apt install python3 virtualenv uwsgi uwsgi-plugin-python nginx
virtualenv -p python3 .
. bin/activate
```
Just got to ticketfrei.links-tech.org or another website where this software is
running.
Install the dependencies:
```shell
pip install tweepy pytoml requests Mastodon.py bottle pyjwt
```
* Register a twitter account
* Register a Mastodon account
* Register on the ticketfrei site
* Configure account
* The hard part: do the promotion! You need a community.
Configure the bot:
```shell
cp config.toml.example config.toml
vim config.toml
```
### Maintaining
You can use a Twitter, a Mastodon, and Mail with the account. They
will communicate with each other; if someone warns others via Mail,
Twitter and Mastodon users will also see the message. And vice versa.
You have to configure all of the accounts via config.toml; it should
be fairly intuitive to enter the right values.
## Maintaining
There is one security hole: people could start mentioning the bot
with useless information, turning it into a spammer. That's why it
has to be maintained; if someone spams the bot, mute them and undo
the retweet. So it won't retweet their future tweets and the useless
retweet is deleted if someone tries to check if something was
retweeted in the last hour or something.
There is one security hole: people could start mentioning the bot with useless
information, turning it into a spammer. That's why it has to be maintained; if
someone spams the bot, mute them and undo the retweet. So it won't retweet
their future tweets and the useless retweet is deleted if someone tries to
check if something was retweeted in the last hour or something.
To this date, we have never heard of this happening though.
### blacklisting
You also need to edit the goodlist and the blacklist. They are in the
"goodlists" and "blacklists" folders. All text files in those
directories will be used, so you should delete our templates; but
feel free to use them as an orientation.
You also need to edit the goodlist and the blacklist. You can do this on the
website, in the settings of your bot.
Just add the words to the goodlist, which you want to require. A
report is only spread, if it contains at least one of them. If you
want to RT everything, just add a ```*```.
Just add the words to the goodlist, which you want to require. A report is only
spread, if it contains at least one of them. If you want to RT everything, just
add a ```*```.
There is also a blacklist, which you can use to automatically sort
out malicious tweets. Be careful though, our filter can't read the
intention with which a word was used. Maybe you wanted it there.
There is also a blacklist, which you can use to automatically sort out
malicious tweets. Be careful though, our filter can't read the intention with
which a word was used. Maybe you wanted it there.
### screen
## Do you want to offer a Ticketfrei website to others?
To keep the bots running when you are logged out of the shell, you
can use screen:
If you want to offer this website to others, feel free to do so. If you have questions, just open
a GitHub issue or write to tech@lists.links-tech.org, we are happy to help and share best practices.
We wrote these installation notes, so you can set up the website easily:
### Install
To Do:
```shell
sudo apt-get install screen
echo "if [ -z "$STY" ]; then screen -RR; fi" >> ~/.bash_login
screen
python3 ticketfrei.py
sudo apt install python3 virtualenv uwsgi uwsgi-plugin-python nginx
```
To log out of the screen session, press "ctrl+a", and then "d".
* set up nginx
* set up LetsEncrypt https://certbot.eff.org/
* set up mariadb
* set up uwsgi
### Manually creating the database
Install the necessary packages, create and activate virtualenv:
Unfortunately, if you want to help developing, you have to create the
database manually for now.
At the moment, we use a SQLITE3 database. If you are in the repo
directory, just open it with ```sqlitebrowser ticketfrei.sqlite```.
Then execute following SQL to create the tables:
```sql
CREATE TABLE `user` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
`email` TEXT,
`pass_hashed` TEXT,
`enabled` INTEGER
)
```shell
virtualenv -p python3 .
. bin/activate
```
Install the dependencies:
```shell
pip install tweepy pytoml requests Mastodon.py bottle pyjwt
```
Configure the bot:
```shell
cp config.toml.example config.toml
vim config.toml
```
This configuration is only for the admin. Users can log into
twitter/mastodon/mail and configure their personal bot on the settings page.

View File

@ -1,10 +1,11 @@
#!/usr/bin/env python3
import prepare
import logging
import time
import sendmail
from db import DB
from config import config
from mastodonbot import MastodonBot
from twitterbot import TwitterBot
@ -20,18 +21,22 @@ def get_users(db):
return users
def init_bots(config, logger, db, users):
def init_bots(config, db, users):
for uid in users:
users[uid].append(Trigger(config, uid, db))
users[uid].append(MastodonBot(config, logger, uid, db))
users[uid].append(TwitterBot(config, logger, uid, db))
users[uid].append(Mailbot(config, logger, uid, db))
users[uid].append(MastodonBot(config, uid, db))
users[uid].append(TwitterBot(config, uid, db))
users[uid].append(Mailbot(config, uid, db))
return users
def run():
config = prepare.get_config()
logger = prepare.get_logger(config)
if __name__ == '__main__':
logpath = config['logging']['logpath']
logger = logging.getLogger()
fh = logging.FileHandler(logpath)
fh.setLevel(logging.DEBUG)
logger.addHandler(fh)
db = DB()
while True:
@ -68,7 +73,3 @@ def run():
attachment=config['logging']['logpath'])
except:
logger.error('Mail sending failed', exc_info=True)
if __name__ == '__main__':
run()

5
config.py Normal file
View File

@ -0,0 +1,5 @@
import pytoml as toml
# read config in TOML format (https://github.com/toml-lang/toml#toml)
with open('config.toml') as configfile:
config = toml.load(configfile)

View File

@ -10,8 +10,8 @@ contact = "b3yond@riseup.net"
[mail]
mailserver = "smtp.riseup.net"
user = "nbgticketfrei"
passphrase = "5MUIGw,GmSj)t@xW!jixq=b0U+@SJ{K{"
user = "user"
passphrase = "sup3rs3cur3"
[logging]
# The directory where logs should be stored.

17
db.py
View File

@ -2,19 +2,18 @@ from bottle import redirect, request
from functools import wraps
from inspect import Signature
import jwt
from os import path, urandom
import logging
from os import urandom
from pylibscrypt import scrypt_mcf, scrypt_mcf_check
import sqlite3
import prepare
from user import User
logger = logging.getLogger(__name__)
class DB(object):
def __init__(self):
self.config = prepare.get_config()
self.logger = prepare.get_logger(self.config)
dbfile = path.join(path.dirname(path.abspath(__file__)),
self.config['database']['db_path'])
def __init__(self, dbfile):
self.conn = sqlite3.connect(dbfile)
self.cur = self.conn.cursor()
self.create()
@ -149,8 +148,8 @@ class DBPlugin(object):
name = 'DBPlugin'
api = 2
def __init__(self, loginpage):
self.db = DB()
def __init__(self, dbfile, loginpage):
self.db = DB(dbfile)
self.loginpage = loginpage
def close(self):

View File

@ -0,0 +1,16 @@
[Unit]
Description=Ticketfrei Web Application
After=syslog.target network.target
[Service]
ExecStart=/usr/bin/uwsgi --ini /srv/ticketfrei/deployment/uwsgi.ini
# Requires systemd version 211 or newer
RuntimeDirectory=uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
[Install]
WantedBy=multi-user.target

11
deployment/uwsgi.ini Normal file
View File

@ -0,0 +1,11 @@
[uwsgi]
plugins = python3
master = true
uid = www-data
gid = www-data
processes = 1
# logto = /var/log/ticketfrei.log
socket = /var/run/ticketfrei/ticketfrei.sock
chmod-socket = 660
wsgi-file = /srv/ticketfrei/frontend.py
virtualenv = /srv/ticketfrei/venv

View File

@ -1,11 +1,15 @@
import bottle
from bottle import get, post, redirect, request, response, view
from config import config
from db import DBPlugin
import logging
import tweepy
import sendmail
import smtplib
from mastodon import Mastodon
import prepare
logger = logging.getLogger(__name__)
@get('/')
@ -26,14 +30,15 @@ def register_post(db):
return dict(error='Email address already in use.')
# send confirmation mail
confirm_link = request.url + "/../confirm/" + db.token(email, password)
send_confirmation_mail(db.config, confirm_link, email)
send_confirmation_mail(confirm_link, email)
return dict(info='Confirmation mail sent.')
def send_confirmation_mail(config, confirm_link, email):
m = sendmail.Mailer(config)
def send_confirmation_mail(confirm_link, email):
m = sendmail.Mailer()
try:
m.send("Complete your registration here: " + confirm_link, email, "[Ticketfrei] Confirm your account")
m.send("Complete your registration here: " + confirm_link, email,
"[Ticketfrei] Confirm your account")
except smtplib.SMTPRecipientsRefused:
return "Please enter a valid E-Mail address."
@ -88,14 +93,15 @@ def login_twitter(user):
Starts the twitter OAuth authentication process.
:return: redirect to twitter.
"""
consumer_key = user.db.config["tapp"]["consumer_key"]
consumer_secret = user.db.config["tapp"]["consumer_secret"]
callback_url = bottle.request.get_header('host') + "/login/twitter/callback"
consumer_key = config["tapp"]["consumer_key"]
consumer_secret = config["tapp"]["consumer_secret"]
callback_url = request.get_header('host') + "/login/twitter/callback"
auth = tweepy.OAuthHandler(consumer_key, consumer_secret, callback_url)
try:
redirect_url = auth.get_authorization_url()
except tweepy.TweepError:
user.db.logger.error('Twitter OAuth Error: Failed to get request token.', exc_info=True)
logger.error('Twitter OAuth Error: Failed to get request token.',
exc_info=True)
return dict(error="Failed to get request token.")
user.save_request_token(auth.request_token)
return bottle.redirect(redirect_url)
@ -108,9 +114,9 @@ def twitter_callback(user):
:return:
"""
# twitter passes the verifier/oauth token secret in a GET request.
verifier = bottle.request.query('oauth_verifier')
consumer_key = user.db.config["tapp"]["consumer_key"]
consumer_secret = user.db.config["tapp"]["consumer_secret"]
verifier = request.query('oauth_verifier')
consumer_key = config["twitter"]["consumer_key"]
consumer_secret = config["twitter"]["consumer_secret"]
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
request_token = user.get_request_token
auth.request_token = {"oauth_token": request_token,
@ -127,22 +133,29 @@ def login_mastodon(user):
:return: redirect to twitter.
"""
# get app tokens
instance_url = bottle.request.forms.get('instance_url')
masto_email = bottle.request.forms.get('email')
instance_url = request.forms.get('instance_url')
masto_email = request.forms.get('email')
print(masto_email)
masto_pass = bottle.request.forms.get('pass')
masto_pass = request.forms.get('pass')
print(masto_pass)
client_id, client_secret = user.get_mastodon_app_keys(instance_url)
m = Mastodon(client_id=client_id, client_secret=client_secret, api_base_url=instance_url)
m = Mastodon(client_id=client_id, client_secret=client_secret,
api_base_url=instance_url)
try:
access_token = m.log_in(masto_email, masto_pass)
user.save_masto_token(access_token, instance_url)
return dict(info='Thanks for supporting decentralized social networks!')
return dict(
info='Thanks for supporting decentralized social networks!'
)
except:
user.db.logger.error('Login to Mastodon failed.', exc_info=True)
logger.error('Login to Mastodon failed.', exc_info=True)
return dict(error='Login to Mastodon failed.')
config = prepare.get_config()
bottle.install(DBPlugin('/'))
bottle.run(host=config['web']['host'], port=8080)
if __name__ == '__main__':
# testing only
bottle.install(DBPlugin(':memory:', '/'))
bottle.run(host='localhost', port=8080)
else:
bottle.install(DBPlugin(config['database']['db_path'], '/'))
application = bottle.default_app()

View File

@ -1,5 +1,7 @@
#!/usr/bin/env python3
from config import config
import logging
import sendmail
import ssl
import datetime
@ -9,21 +11,22 @@ import report
from user import User
logger = logging.getLogger(__name__)
class Mailbot(object):
"""
Bot which sends Mails if mentioned via twitter/mastodon, and tells
other bots that it received mails.
"""
def __init__(self, config, logger, uid, db):
def __init__(self, uid, db):
"""
Creates a Bot who listens to mails and forwards them to other
bots.
:param config: (dictionary) config.toml as a dictionary of dictionaries
"""
self.config = config
self.logger = logger
self.user = User(db, uid)
try:
@ -36,23 +39,24 @@ class Mailbot(object):
except TypeError:
self.mailinglist = None
self.mailbox = imaplib.IMAP4_SSL(self.config["mail"]["imapserver"])
self.mailbox = imaplib.IMAP4_SSL(config["mail"]["imapserver"])
context = ssl.create_default_context()
try:
self.mailbox.starttls(ssl_context=context)
except:
self.logger.error('StartTLS failed', exc_info=True)
logger.error('StartTLS failed', exc_info=True)
try:
self.mailbox.login(self.config["mail"]["user"], self.config["mail"]["passphrase"])
self.mailbox.login(config["mail"]["user"],
config["mail"]["passphrase"])
except imaplib.IMAP4.error:
self.logger.error("Login to mail server failed", exc_info=True)
logger.error("Login to mail server failed", exc_info=True)
try:
mailer = sendmail.Mailer(config)
mailer = sendmail.Mailer()
mailer.send('', config['web']['contact'],
'Ticketfrei Crash Report',
attachment=config['logging']['logpath'])
except:
self.logger.error('Mail sending failed', exc_info=True)
logger.error('Mail sending failed', exc_info=True)
def repost(self, status):
"""
@ -72,7 +76,7 @@ class Mailbot(object):
try:
rv, data = self.mailbox.select("Inbox")
except imaplib.IMAP4.abort:
self.logger.error("Crawling Mail failed", exc_info=True)
logger.error("Crawling Mail failed", exc_info=True)
rv = False
msgs = []
if rv == 'OK':
@ -83,15 +87,18 @@ class Mailbot(object):
for num in data[0].split():
rv, data = self.mailbox.fetch(num, '(RFC822)')
if rv != 'OK':
self.logger.error("Couldn't fetch mail %s %s" % (rv, str(data)))
logger.error("Couldn't fetch mail %s %s" % (rv, str(data)))
return msgs
msg = email.message_from_bytes(data[0][1])
if not self.user.get_mail() in msg['From']:
# get a comparable date out of the email
date_tuple = email.utils.parsedate_tz(msg['Date'])
date_tuple = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
date = int((date_tuple - datetime.datetime(1970, 1, 1)).total_seconds())
date_tuple = datetime.datetime.fromtimestamp(
email.utils.mktime_tz(date_tuple)
)
date = int((date_tuple -
datetime.datetime(1970, 1, 1)).total_seconds())
if date > self.user.get_seen_mail():
self.last_mail = date
self.save_last()
@ -108,8 +115,9 @@ class Mailbot(object):
:param status: (report.Report object)
"""
mailer = sendmail.Mailer(self.config)
mailer.send(status.format(), self.mailinglist, "Warnung: Kontrolleure gesehen")
mailer = sendmail.Mailer(config)
mailer.send(status.format(), self.mailinglist,
"Warnung: Kontrolleure gesehen")
def make_report(self, msg):
"""
@ -120,8 +128,10 @@ class Mailbot(object):
"""
# get a comparable date out of the email
date_tuple = email.utils.parsedate_tz(msg['Date'])
date_tuple = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
date = (date_tuple-datetime.datetime(1970,1,1)).total_seconds()
date_tuple = datetime.datetime.fromtimestamp(
email.utils.mktime_tz(date_tuple)
)
date = (date_tuple - datetime.datetime(1970, 1, 1)).total_seconds()
author = msg.get("From") # get mail author from email header
# :todo take only the part before the @
@ -149,32 +159,3 @@ class Mailbot(object):
if trigger.is_ok(msg.get_payload()):
statuses.append(msg)
return statuses
"""
if __name__ == "__main__":
config = prepare.get_config()
# initialise trigger
trigger = trigger.Trigger(config)
# initialise mail bot
m = Mailbot(config)
statuses = []
try:
while 1:
print("Received Reports: " + str(m.flow(trigger, statuses)))
time.sleep(1)
except KeyboardInterrupt:
print("Good bye. Remember to restart the bot!")
except:
m.logger.error('Shutdown', exc_info=True)
m.save_last()
try:
mailer = sendmail.Mailer(config)
mailer.send('', config['mail']['contact'],
'Ticketfrei Crash Report',
attachment=config['logging']['logpath'])
except:
m.logger.error('Mail sending failed', exc_info=True)
"""

View File

@ -1,22 +1,26 @@
#!/usr/bin/env python3
import logging
import mastodon
import re
# import time
# import trigger
# import sendmail
import report
from user import User
logger = logging.getLogger(__name__)
class MastodonBot(object):
def __init__(self, config, logger, uid, db):
self.config = config
self.logger = logger
def __init__(self, uid, db):
self.user = User(db, uid)
client_id, client_secret, access_token, instance_url = self.user.get_masto_credentials()
self.m = mastodon.Mastodon(client_id=client_id, client_secret=client_secret,
access_token=access_token, api_base_url=instance_url)
client_id, client_secret, access_token, instance_url = \
self.user.get_masto_credentials()
self.m = mastodon.Mastodon(
client_id=client_id,
client_secret=client_secret,
access_token=access_token,
api_base_url=instance_url
)
# load state
try:
@ -37,16 +41,19 @@ class MastodonBot(object):
try:
notifications = self.m.notifications()
except: # mastodon.Mastodon.MastodonAPIError is unfortunately not in __init__.py
self.logger.error("Unknown Mastodon API Error.", exc_info=True)
logger.error("Unknown Mastodon API Error.", exc_info=True)
return mentions
for status in notifications:
if status['type'] == 'mention' and status['status']['id'] > self.seen_toots:
if (status['type'] == 'mention' and
status['status']['id'] > self.seen_toots):
# save state
self.seen_toots = status['status']['id']
self.save_last()
# add mention to mentions
text = re.sub(r'<[^>]*>', '', status['status']['content'])
text = re.sub("(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)", "", text)
text = re.sub(
"(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)",
"", text)
mentions.append(report.Report(status['account']['acct'],
"mastodon",
text,
@ -60,7 +67,7 @@ class MastodonBot(object):
:param mention: (report.Report object)
"""
self.logger.info('Boosting toot from %s' % (
logger.info('Boosting toot from %s' % (
mention.format()))
self.m.status_reblog(mention.id)
@ -88,27 +95,3 @@ class MastodonBot(object):
# return mentions for mirroring
return retoots
"""
if __name__ == '__main__':
config = backend.get_config()
trigger = trigger.Trigger(config)
bot = MastodonBot(config)
try:
while True:
bot.flow(trigger)
time.sleep(1)
except KeyboardInterrupt:
print("Good bye. Remember to restart the bot!")
except:
bot.logger.error('Shutdown', exc_info=True)
try:
mailer = sendmail.Mailer(config)
mailer.send('', config['mail']['contact'],
'Ticketfrei Crash Report',
attachment=config['logging']['logpath'])
except:
bot.logger.error('Mail sending failed', exc_info=True)
"""

View File

@ -1,19 +0,0 @@
import logging
import pytoml as toml
def get_logger(config):
logpath = config['logging']['logpath']
logger = logging.getLogger()
fh = logging.FileHandler(logpath)
fh.setLevel(logging.DEBUG)
logger.addHandler(fh)
return logger
def get_config():
# read config in TOML format (https://github.com/toml-lang/toml#toml)
with open('config.toml') as configfile:
config = toml.load(configfile)
return config

View File

@ -1,42 +1,58 @@
% rebase('template/wrapper.tpl')
% include('template/login-plain.tpl')
<h1>Features</h1>
<p>sum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard
dummy text ever since the 1500s, when an unknown printer
took a galley of type and scrambled it to make a type
specimen book. It has survived not only five centuries,
but also the leap into electronic typesetting, remaining
essentially unchanged. It was popularised in the 1960s
with the release of Letraset sheets containing Lorem
Ipsum passages, and more recently with desktop publishing
software like Aldus PageMaker including versions of Lorem
Ipsum.</p>
<p>
Don't pay for public transport. Instead, warn each other
from ticket controllers! With Ticketfrei, you can turn
your city into a paradise for fare dodgers.
</p>
<p>
Ticketfrei is a Twitter, Mastodon, and E-Mail bot. Users
can help each other by tweeting, tooting, or mailing,
when and where they spot a ticket controller.
</p>
<p>
Ticketfrei automatically retweets, boosts, and remails
those controller reports, so others can see them. If there
are ticket controllers around, they can still buy a ticket
- but if the coast is clear, they can save the money.
</p>
<h2>How to get Ticketfrei to my city?</h2>
<p>sum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard
dummy text ever since the 1500s, when an unknown printer
took a galley of type and scrambled it to make a type
specimen book. It has survived not only five centuries,
but also the leap into electronic typesetting, remaining
essentially unchanged. It was popularised in the 1960s
with the release of Letraset sheets containing Lorem
Ipsum passages, and more recently with desktop publishing
software like Aldus PageMaker including versions of Lorem
Ipsum.</p>
<p>
We try to make it as easy as possible to spread Ticketfrei
to other citys. There are four basic steps:
</p>
<ul>
<li>Create a Twitter and/or a Mastodon account.</li>
<li>Register on this website to create a bot for your city.</li>
<li>Log in with the social media accounts you want to
use for Ticketfrei.</li>
<li>Promote the service! Ticketfrei only works if there is
a community for it. Fortunately, we prepared some material
you can use:
<a href="https://github.com/b3yond/ticketfrei/tree/master/promotion" target="_blank">https://github.com/b3yond/ticketfrei/tree/master/promotion</a></li>
</ul>
% include('template/register-plain.tpl')
<h2>Our Mission</h2>
<p>Contrary to popular belief, Lorem Ipsum is not simply random
text. It has roots in a piece of classical Latin literature
from 45 BC, making it over 2000 years old. Richard
McClintock, a Latin professor at Hampden-Sydney College in
Virginia, looked up one of the more obscure Latin words,
consectetur, from a Lorem Ipsum passage, and going through
the cites of the word in classical literature, discovered
the undoubtable source. Lorem Ipsum comes from sections
1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum"
(The Extremes of Good and Evil) by Cicero, written in 45
BC. This book is a treatise on the theory of ethics, very
popular during the Renaissance. The first line of Lorem
Ipsum, "Lorem ipsum dolor sit amet..", comes from a line
in section 1.10.32.</p>
<p>
Public transportation is meant to provide an easy and
time-saving way to move within a region while being
affordable for everybody. Unfortunately, this is not the
case. Ticketfrei's approach is to enable people to
reclaim public transportation.
</p>
<p>
On short term we want to do this by helping users to avoid
controllers and fines - on long term by pressuring public
transportation companies to offer their services free of
charge, financed by the public.
</p>
<p>
Because with Ticketfrei you're able to use trains and
subways for free anyway. Take part and create a new
understanding of what public transportation should look
like!
</p>

View File

@ -1,5 +1,7 @@
#!/usr/bin/env python3
from config import config
import logging
import tweepy
import re
import requests
@ -8,6 +10,9 @@ import report
from user import User
logger = logging.getLogger(__name__)
class TwitterBot(object):
"""
This bot retweets all tweets which
@ -19,7 +24,7 @@ class TwitterBot(object):
last_mention: the ID of the last tweet which mentioned you
"""
def __init__(self, config, logger, uid, db):
def __init__(self, uid, db):
"""
Initializes the bot and loads all the necessary data.
@ -27,8 +32,6 @@ class TwitterBot(object):
:param history_path: Path to the file with ID of the last retweeted
Tweet
"""
self.config = config
self.logger = logger
self.db = db
self.user = User(db, uid)
@ -58,7 +61,8 @@ class TwitterBot(object):
:return: keys: list of these 4 strings.
"""
keys = [self.config['twitter']['consumer_key'], self.config['twitter']['consumer_secret']]
keys = [config['twitter']['consumer_key'],
config['twitter']['consumer_secret']]
row = self.user.get_twitter_token()
keys.append(row[0])
keys.append(row[1])
@ -91,9 +95,12 @@ class TwitterBot(object):
if self.last_mention == 0:
mentions = self.api.mentions_timeline()
else:
mentions = self.api.mentions_timeline(since_id=self.last_mention)
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)
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,
@ -102,13 +109,14 @@ class TwitterBot(object):
self.save_last()
return reports
except tweepy.RateLimitError:
self.logger.error("Twitter API Error: Rate Limit Exceeded", exc_info=True)
logger.error("Twitter API Error: Rate Limit Exceeded",
exc_info=True)
self.waitcounter += 60*15 + 1
except requests.exceptions.ConnectionError:
self.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:
self.logger.error("Twitter API Error: General Error", exc_info=True)
logger.error("Twitter API Error: General Error", exc_info=True)
return []
def repost(self, status):
@ -121,17 +129,18 @@ class TwitterBot(object):
while 1:
try:
self.api.retweet(status.id)
self.logger.info("Retweeted: " + status.format())
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:
self.logger.error("Twitter API Error: Bad Connection", exc_info=True)
logger.error("Twitter API Error: Bad Connection",
exc_info=True)
sleep(10)
# maybe one day we get rid of this error:
except tweepy.TweepError:
self.logger.error("Twitter Error", exc_info=True)
logger.error("Twitter Error", exc_info=True)
if status.id > self.last_mention:
self.last_mention = status.id
self.save_last()
@ -151,7 +160,8 @@ class TwitterBot(object):
self.api.update_status(status=text)
return
except requests.exceptions.ConnectionError:
self.logger.error("Twitter API Error: Bad Connection", exc_info=True)
logger.error("Twitter API Error: Bad Connection",
exc_info=True)
sleep(10)
def flow(self, trigger, to_tweet=()):
@ -181,32 +191,3 @@ class TwitterBot(object):
# Return Retweets for posting on other bots
return all_tweets
"""
if __name__ == "__main__":
config = backend.get_config()
# initialise trigger
trigger = trigger.Trigger(config)
# initialise twitter bot
bot = TwitterBot(config)
try:
while True:
# :todo separate into small functions
bot.flow(trigger)
sleep(60)
except KeyboardInterrupt:
print("Good bye. Remember to restart the bot!")
except:
bot.logger.error('Shutdown', exc_info=True)
bot.save_last()
try:
mailer = sendmail.Mailer(config)
mailer.send('', config['mail']['contact'],
'Ticketfrei Crash Report',
attachment=config['logging']['logpath'])
except:
bot.logger.error('Mail sending failed', exc_info=True)
"""