diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..045eb1c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b2d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -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. diff --git a/.github/ISSUE_TEMPLATE/something-else.md b/.github/ISSUE_TEMPLATE/something-else.md new file mode 100644 index 0000000..3d188cf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/something-else.md @@ -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.* diff --git a/LICENSE b/LICENSE index 6b24afd..2249ac4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Copyright (c) 2017 Thomas L Copyright (c) 2017 b3yond -Copyright (c) 2018 sid +Copyright (c) 2018 sid Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/README.md b/README.md index 560d899..0a68e78 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,23 @@ Ticketfrei is a mastodon/twitter/mail bot to dodge ticket controllers in public 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. This leads to a community which evolves around it. If you see ticket @@ -91,7 +108,7 @@ virtualenv -p python3 . Install the dependencies: ```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: diff --git a/active_bots/mailbot.py b/active_bots/mailbot.py index 7c8fcb5..fbcd02b 100644 --- a/active_bots/mailbot.py +++ b/active_bots/mailbot.py @@ -19,7 +19,11 @@ class Mailbot(Bot): def crawl(self, user): reports = [] # todo: adjust to actual mailbox - mails = mailbox.mbox("/var/mail/" + config['mail']['mbox_user']) + try: + mails = mailbox.mbox("/var/mail/" + config['mail']['mbox_user']) + except FileNotFoundError: + logger.error("No mbox file found.") + return reports for msg in mails: if get_date_from_header(msg['Date']) > user.get_seen_mail(): if user.get_city().lower() in msg['To'].lower(): diff --git a/active_bots/telegrambot.py b/active_bots/telegrambot.py index 0bb2866..9fdbd66 100644 --- a/active_bots/telegrambot.py +++ b/active_bots/telegrambot.py @@ -21,10 +21,15 @@ class TelegramBot(Bot): return reports for update in updates: # return when telegram returns an error code - if update in [303, 404, 420, 500]: + if update in [303, 404, 420, 500, 502]: return reports - elif isinstance(update, int): - logger.error("Unknown Telegram error code: " + str(update)) + 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)) return reports user.save_seen_tg(update.update_id) if update.message.text.lower() == "/start": @@ -42,19 +47,24 @@ class TelegramBot(Bot): elif update.message.text.lower() == "/help": tb.send_message( 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 else: - reports.append(Report(update.message.sender.username, self, - update.message.text, None, - update.message.date)) + # set report.author to "" to avoid mailbot crash + sender_name = update.message.sender.username + if sender_name is None: + sender_name = "" + + reports.append(Report(sender_name, self, update.message.text, + None, update.message.date)) return reports def post(self, user, report): tb = Telegram(user.get_telegram_credentials()) text = report.text if len(text) > 4096: - text = text[:4096 - 4] + u' ...' + text = text[:4096 - 2] + " \N{Horizontal ellipsis}" try: for subscriber_id in user.get_telegram_subscribers(): tb.send_message(subscriber_id, text).wait() diff --git a/config.py b/config.py index fa01302..bc7baaf 100755 --- a/config.py +++ b/config.py @@ -1,5 +1,70 @@ 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) -with open('config.toml') as configfile: - config = toml.load(configfile) +try: + with open('config.toml') as 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])) diff --git a/config.toml.example b/config.toml.example index d93c333..eb519db 100644 --- a/config.toml.example +++ b/config.toml.example @@ -10,10 +10,7 @@ port = 80 contact = "b3yond@riseup.net" [mail] -mailserver = "smtp.riseup.net" -user = "user" -passphrase = "sup3rs3cur3" -mbox = "root" +mbox_user = "root" [database] db_path = "/var/ticketfrei/db.sqlite" diff --git a/db.py b/db.py index 8212560..61100ae 100644 --- a/db.py +++ b/db.py @@ -14,7 +14,6 @@ class DB(object): self.conn = sqlite3.connect(dbfile) self.cur = self.conn.cursor() self.create() - self.secret = self.get_secret() def execute(self, *args, **kwargs): return self.cur.execute(*args, **kwargs) @@ -115,13 +114,6 @@ class DB(object): FOREIGN KEY(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 ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, @@ -196,7 +188,7 @@ class DB(object): 'passhash': scrypt_mcf( password.encode('utf-8') ).decode('ascii') - }, self.secret).decode('ascii') + }, self.get_secret()).decode('ascii') def mail_subscription_token(self, email, city): """ @@ -210,17 +202,17 @@ class DB(object): token = jwt.encode({ 'email': email, 'city': city - }, self.secret).decode('ascii') + }, self.get_secret()).decode('ascii') return token def confirm_subscription(self, token): - json = jwt.decode(token, self.secret) + json = jwt.decode(token, self.get_secret()) return json['email'], json['city'] def confirm(self, token, city): from user import User try: - json = jwt.decode(token, self.secret) + json = jwt.decode(token, self.get_secret()) except jwt.DecodeError: return None # invalid token if 'passhash' in json.keys(): diff --git a/frontend.py b/frontend.py index 37144d3..452769f 100755 --- a/frontend.py +++ b/frontend.py @@ -56,11 +56,22 @@ def register_post(): @get('/confirm//') @view('template/propaganda.tpl') 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 if db.confirm(token, city): # :todo show info "Account creation successful." 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') @@ -259,7 +270,6 @@ application = bottle.default_app() bottle.install(SessionPlugin('/')) if __name__ == '__main__': - # testing only - bottle.run(host=config["web"]["host"], port=config["web"]["port"]) + bottle.run(host="0.0.0.0", port=config["web"]["port"]) else: application.catchall = False diff --git a/sendmail.py b/sendmail.py index dec722d..3c6e9a6 100755 --- a/sendmail.py +++ b/sendmail.py @@ -27,5 +27,5 @@ def sendmail(to, subject, city=None, body=''): # For testing: if __name__ == '__main__': - sendmail(config['mail']['contact'], "Test Mail", + sendmail(config['web']['contact'], "Test Mail", body="This is a test mail.") diff --git a/user.py b/user.py index dad9b1c..3158568 100644 --- a/user.py +++ b/user.py @@ -361,10 +361,13 @@ Aber je mehr Leute mitmachen, desto eher kannst du dir sicher sein, dass wir sie finden, bevor sie uns finden. Wenn du immer direkt gewarnt werden willst, kannst du auch die -Benachrichtigungen über E-Mail oder Telegram aktivieren. Gib -einfach hier deine -E-Mail-Adresse an oder subscribe dem Telegram-Bot [@ticketfrei_""" + city + \ - "_bot](https://t.me/ticketfrei_" + city + """_bot) +Benachrichtigungen über E-Mail, Telegram, oder den Mastodon RSS +feed aktivieren. Entweder: +* Gibt hier [deine E-Mail-Adresse an](/city/mail/""" + city + """) +* Subscribe dem Telegram-Bot [@ticketfrei_""" + city + \ +"_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 Kontrolleur sieht: @@ -388,9 +391,9 @@ mentioned, und gib an 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 anderen Netzwerken.