Merge pull request #77 from ticketfrei/master

2.1.0 Release
This commit is contained in:
b3yond 2019-01-12 00:25:19 +01:00 committed by GitHub
commit 9d7cca6a0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 188 additions and 39 deletions

27
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
---
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual Behavior**
A clear and concise description of what happens.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Ticketfrei Version**
See the commit on which Ticketfrei is running at example.org/version.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View file

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -0,0 +1,7 @@
---
name: Something else
about: Other ideas?
---
*If your suggestion is neither a bug report nor a feature request, this is the right place. Just describe what you have in mind.*

View file

@ -1,6 +1,6 @@
Copyright (c) 2017 Thomas L <tom@dl6tom.de> Copyright (c) 2017 Thomas L <tom@dl6tom.de>
Copyright (c) 2017 b3yond <b3yond@riseup.net> Copyright (c) 2017 b3yond <b3yond@riseup.net>
Copyright (c) 2018 sid Copyright (c) 2018 sid <sid-sid@riseup.net>
Permission to use, copy, modify, and distribute this software for any Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above

View file

@ -3,6 +3,23 @@
Ticketfrei is a mastodon/twitter/mail bot to dodge ticket controllers in public Ticketfrei is a mastodon/twitter/mail bot to dodge ticket controllers in public
transport systems. transport systems.
## Mission
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 yet 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 could
look like!
## How It Works
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 This leads to a community which evolves around it. If you see ticket
@ -91,7 +108,7 @@ virtualenv -p python3 .
Install the dependencies: Install the dependencies:
```shell ```shell
pip install tweepy pytoml Mastodon.py bottle pyjwt pylibscrypt Markdown twx pip install tweepy pytoml Mastodon.py bottle pyjwt pylibscrypt Markdown twx gitpython
``` ```
Configure the bot: Configure the bot:

View file

@ -19,7 +19,11 @@ class Mailbot(Bot):
def crawl(self, user): def crawl(self, user):
reports = [] reports = []
# todo: adjust to actual mailbox # todo: adjust to actual mailbox
try:
mails = mailbox.mbox("/var/mail/" + config['mail']['mbox_user']) mails = mailbox.mbox("/var/mail/" + config['mail']['mbox_user'])
except FileNotFoundError:
logger.error("No mbox file found.")
return reports
for msg in mails: for msg in mails:
if get_date_from_header(msg['Date']) > user.get_seen_mail(): if get_date_from_header(msg['Date']) > user.get_seen_mail():
if user.get_city().lower() in msg['To'].lower(): if user.get_city().lower() in msg['To'].lower():

View file

@ -21,9 +21,14 @@ class TelegramBot(Bot):
return reports return reports
for update in updates: for update in updates:
# return when telegram returns an error code # return when telegram returns an error code
if update in [303, 404, 420, 500]: if update in [303, 404, 420, 500, 502]:
return reports return reports
elif isinstance(update, int): if isinstance(update, int):
try:
logger.error("City " + str(user.uid) +
": Unknown Telegram error code: " +
str(update) + " - " + str(updates[1]))
except TypeError:
logger.error("Unknown Telegram error code: " + str(update)) logger.error("Unknown Telegram error code: " + str(update))
return reports return reports
user.save_seen_tg(update.update_id) user.save_seen_tg(update.update_id)
@ -42,19 +47,24 @@ class TelegramBot(Bot):
elif update.message.text.lower() == "/help": elif update.message.text.lower() == "/help":
tb.send_message( tb.send_message(
update.message.sender.id, update.message.sender.id,
"Send reports here to share them with other users. Use /start and /stop to get reports or not.") "Send reports here to share them with other users. "
"Use /start and /stop to get reports or not.")
# TODO: /help message should be set in frontend # TODO: /help message should be set in frontend
else: else:
reports.append(Report(update.message.sender.username, self, # set report.author to "" to avoid mailbot crash
update.message.text, None, sender_name = update.message.sender.username
update.message.date)) if sender_name is None:
sender_name = ""
reports.append(Report(sender_name, self, update.message.text,
None, update.message.date))
return reports return reports
def post(self, user, report): def post(self, user, report):
tb = Telegram(user.get_telegram_credentials()) tb = Telegram(user.get_telegram_credentials())
text = report.text text = report.text
if len(text) > 4096: if len(text) > 4096:
text = text[:4096 - 4] + u' ...' text = text[:4096 - 2] + " \N{Horizontal ellipsis}"
try: try:
for subscriber_id in user.get_telegram_subscribers(): for subscriber_id in user.get_telegram_subscribers():
tb.send_message(subscriber_id, text).wait() tb.send_message(subscriber_id, text).wait()

View file

@ -1,5 +1,70 @@
import pytoml as toml import pytoml as toml
import os
def load_env():
"""
load environment variables from the environment. If empty, use default
values from config.toml.example.
:return: config dictionary of dictionaries.
"""
with open('config.toml.example') as defaultconf:
configdict = toml.load(defaultconf)
try:
if os.environ['CONSUMER_KEY'] != "":
configdict['twitter']['consumer_key'] = os.environ['CONSUMER_KEY']
except KeyError:
pass
try:
if os.environ['CONSUMER_SECRET'] != "":
configdict['twitter']['consumer_secret'] = os.environ['CONSUMER_SECRET']
except KeyError:
pass
try:
if os.environ['HOST'] != "":
configdict['web']['host'] = os.environ['HOST']
except KeyError:
pass
try:
if os.environ['PORT'] != "":
configdict['web']['port'] = os.environ['PORT']
except KeyError:
pass
try:
if os.environ['CONTACT'] != "":
configdict['web']['contact'] = os.environ['CONTACT']
except KeyError:
pass
try:
if os.environ['MBOX_USER'] != "":
configdict['mail']['mbox_user'] = os.environ['MBOX_USER']
except KeyError:
pass
try:
if os.environ['DB_PATH'] != "":
configdict['database']['db_path'] = os.environ['DB_PATH']
except KeyError:
pass
return configdict
# read config in TOML format (https://github.com/toml-lang/toml#toml) # read config in TOML format (https://github.com/toml-lang/toml#toml)
try:
with open('config.toml') as configfile: with open('config.toml') as configfile:
config = toml.load(configfile) config = toml.load(configfile)
except FileNotFoundError:
config = load_env()
if __name__ == "__main__":
for category in config:
for key in config[category]:
print(key + "=" + str(config[category][key]))

View file

@ -10,10 +10,7 @@ port = 80
contact = "b3yond@riseup.net" contact = "b3yond@riseup.net"
[mail] [mail]
mailserver = "smtp.riseup.net" mbox_user = "root"
user = "user"
passphrase = "sup3rs3cur3"
mbox = "root"
[database] [database]
db_path = "/var/ticketfrei/db.sqlite" db_path = "/var/ticketfrei/db.sqlite"

16
db.py
View file

@ -14,7 +14,6 @@ class DB(object):
self.conn = sqlite3.connect(dbfile) self.conn = sqlite3.connect(dbfile)
self.cur = self.conn.cursor() self.cur = self.conn.cursor()
self.create() self.create()
self.secret = self.get_secret()
def execute(self, *args, **kwargs): def execute(self, *args, **kwargs):
return self.cur.execute(*args, **kwargs) return self.cur.execute(*args, **kwargs)
@ -115,13 +114,6 @@ class DB(object):
FOREIGN KEY(twitter_accounts_id) FOREIGN KEY(twitter_accounts_id)
REFERENCES twitter_accounts(id) REFERENCES twitter_accounts(id)
); );
CREATE TABLE IF NOT EXISTS telegram_accounts (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
api_token TEXT,
active INTEGER,
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS telegram_subscribers ( CREATE TABLE IF NOT EXISTS telegram_subscribers (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER, user_id INTEGER,
@ -196,7 +188,7 @@ class DB(object):
'passhash': scrypt_mcf( 'passhash': scrypt_mcf(
password.encode('utf-8') password.encode('utf-8')
).decode('ascii') ).decode('ascii')
}, self.secret).decode('ascii') }, self.get_secret()).decode('ascii')
def mail_subscription_token(self, email, city): def mail_subscription_token(self, email, city):
""" """
@ -210,17 +202,17 @@ class DB(object):
token = jwt.encode({ token = jwt.encode({
'email': email, 'email': email,
'city': city 'city': city
}, self.secret).decode('ascii') }, self.get_secret()).decode('ascii')
return token return token
def confirm_subscription(self, token): def confirm_subscription(self, token):
json = jwt.decode(token, self.secret) json = jwt.decode(token, self.get_secret())
return json['email'], json['city'] return json['email'], json['city']
def confirm(self, token, city): def confirm(self, token, city):
from user import User from user import User
try: try:
json = jwt.decode(token, self.secret) json = jwt.decode(token, self.get_secret())
except jwt.DecodeError: except jwt.DecodeError:
return None # invalid token return None # invalid token
if 'passhash' in json.keys(): if 'passhash' in json.keys():

View file

@ -56,11 +56,22 @@ def register_post():
@get('/confirm/<city>/<token>') @get('/confirm/<city>/<token>')
@view('template/propaganda.tpl') @view('template/propaganda.tpl')
def confirm(city, token): def confirm(city, token):
# check whether city already exists
if db.by_city(city):
return dict(error='This Account was already confirmed, please try '
'signing in.')
# create db-entry # create db-entry
if db.confirm(token, city): if db.confirm(token, city):
# :todo show info "Account creation successful." # :todo show info "Account creation successful."
redirect('/settings') redirect('/settings')
return dict(error='Email confirmation failed.') return dict(error='Account creation failed. Please try to register again.')
@get('/version')
def version():
import git
repo = git.Repo(search_parent_directories=True)
return repo.head.object.hexsha
@post('/login') @post('/login')
@ -259,7 +270,6 @@ application = bottle.default_app()
bottle.install(SessionPlugin('/')) bottle.install(SessionPlugin('/'))
if __name__ == '__main__': if __name__ == '__main__':
# testing only bottle.run(host="0.0.0.0", port=config["web"]["port"])
bottle.run(host=config["web"]["host"], port=config["web"]["port"])
else: else:
application.catchall = False application.catchall = False

View file

@ -27,5 +27,5 @@ def sendmail(to, subject, city=None, body=''):
# For testing: # For testing:
if __name__ == '__main__': if __name__ == '__main__':
sendmail(config['mail']['contact'], "Test Mail", sendmail(config['web']['contact'], "Test Mail",
body="This is a test mail.") body="This is a test mail.")

13
user.py
View file

@ -361,10 +361,13 @@ Aber je mehr Leute mitmachen, desto eher kannst du dir sicher
sein, dass wir sie finden, bevor sie uns finden. sein, dass wir sie finden, bevor sie uns finden.
Wenn du immer direkt gewarnt werden willst, kannst du auch die Wenn du immer direkt gewarnt werden willst, kannst du auch die
Benachrichtigungen über E-Mail oder Telegram aktivieren. Gib Benachrichtigungen über E-Mail, Telegram, oder den Mastodon RSS
einfach <a href="/city/mail/""" + city + """"/">hier</a> deine feed aktivieren. Entweder:
E-Mail-Adresse an oder subscribe dem Telegram-Bot [@ticketfrei_""" + city + \ * Gibt hier [deine E-Mail-Adresse an](/city/mail/""" + city + """)
* Subscribe dem Telegram-Bot [@ticketfrei_""" + city + \
"_bot](https://t.me/ticketfrei_" + city + """_bot) "_bot](https://t.me/ticketfrei_" + city + """_bot)
* oder subscribe dem RSS feed von [""" + city + """](""" + masto_link + \
""".atom?replies=false&boosts=true)
Also, wenn du weniger Glück hast, und der erste bist, der einen Also, wenn du weniger Glück hast, und der erste bist, der einen
Kontrolleur sieht: Kontrolleur sieht:
@ -388,9 +391,9 @@ mentioned, und gib an
Zum Beispiel so: Zum Beispiel so:
![Screenshot of writing a Toot](https://github.com/b3yond/ticketfrei/raw/master/guides/tooting_screenshot.png) ![Screenshot of writing a Toot](https://github.com/b3yond/ticketfrei/raw/stable1/guides/tooting_screenshot.png)
![A toot ready to be shared](https://github.com/b3yond/ticketfrei/raw/master/guides/toot_screenshot.png) ![A toot ready to be shared](https://github.com/b3yond/ticketfrei/raw/stable1/guides/toot_screenshot.png)
Der Bot wird die Nachricht dann weiterverbreiten, auch zu den Der Bot wird die Nachricht dann weiterverbreiten, auch zu den
anderen Netzwerken. anderen Netzwerken.