diff --git a/README.md b/README.md index cd65181..2f1287c 100644 --- a/README.md +++ b/README.md @@ -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. + diff --git a/backend.py b/backend.py index 19d23db..ddce2c3 100755 --- a/backend.py +++ b/backend.py @@ -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() diff --git a/config.py b/config.py new file mode 100644 index 0000000..fa01302 --- /dev/null +++ b/config.py @@ -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) diff --git a/config.toml.example b/config.toml.example index ede3336..417b5f0 100644 --- a/config.toml.example +++ b/config.toml.example @@ -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. diff --git a/db.py b/db.py index 86437b3..d9a8fde 100644 --- a/db.py +++ b/db.py @@ -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): diff --git a/deployment/ticketfrei-web.service b/deployment/ticketfrei-web.service new file mode 100644 index 0000000..9ffbd00 --- /dev/null +++ b/deployment/ticketfrei-web.service @@ -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 diff --git a/deployment/uwsgi.ini b/deployment/uwsgi.ini new file mode 100644 index 0000000..bcb8257 --- /dev/null +++ b/deployment/uwsgi.ini @@ -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 diff --git a/frontend.py b/frontend.py index b57ccfe..6da6f30 100755 --- a/frontend.py +++ b/frontend.py @@ -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() diff --git a/mailbot.py b/mailbot.py index 80d8a8d..a98872a 100644 --- a/mailbot.py +++ b/mailbot.py @@ -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) -""" \ No newline at end of file diff --git a/mastodonbot.py b/mastodonbot.py index 152f01e..561a7fb 100755 --- a/mastodonbot.py +++ b/mastodonbot.py @@ -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) -""" \ No newline at end of file diff --git a/prepare.py b/prepare.py deleted file mode 100644 index 58c482a..0000000 --- a/prepare.py +++ /dev/null @@ -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 - - diff --git a/promotion/campaign.md b/promotion/README.md similarity index 100% rename from promotion/campaign.md rename to promotion/README.md diff --git a/template/propaganda.tpl b/template/propaganda.tpl index 479fcf6..4a3b034 100644 --- a/template/propaganda.tpl +++ b/template/propaganda.tpl @@ -1,42 +1,58 @@ % rebase('template/wrapper.tpl') % include('template/login-plain.tpl')

Features

-

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.

+

+ 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. +

+

+ 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. +

+

+ 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. +

How to get Ticketfrei to my city?

-

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.

+

+ We try to make it as easy as possible to spread Ticketfrei + to other citys. There are four basic steps: +

+ % include('template/register-plain.tpl')

Our Mission

-

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.

+

+ 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. +

+

+ 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. +

+

+ 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! +

+ + + diff --git a/twitterbot.py b/twitterbot.py index 9eeb87c..787cdfb 100755 --- a/twitterbot.py +++ b/twitterbot.py @@ -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) -""" \ No newline at end of file