Compare commits
20 commits
csrf
...
refactorin
Author | SHA1 | Date | |
---|---|---|---|
b3yond | 833be826e9 | ||
7ef8c3e197 | |||
768a4350bd | |||
9906346691 | |||
f5f741a5c2 | |||
eabc43cdba | |||
e4d0fc3b89 | |||
5c844157ce | |||
050e641bb0 | |||
fa3dda1c60 | |||
5535d3535f | |||
668157c552 | |||
b3yond | 39cf3bd070 | ||
b3yond | 382532cf5c | ||
b3yond | cfb9eabee9 | ||
b3yond | a27d47eb8b | ||
b3yond | e34944fcaa | ||
b3yond | d6db1879f9 | ||
b3yond | 02f117a864 | ||
b3yond | 4882930516 |
3
.gitignore
vendored
|
@ -1,5 +1,7 @@
|
||||||
*.swp
|
*.swp
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.egg-info/
|
||||||
|
*.eggs
|
||||||
.idea/
|
.idea/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
last_mention
|
last_mention
|
||||||
|
@ -17,4 +19,3 @@ include/
|
||||||
lib/
|
lib/
|
||||||
share/
|
share/
|
||||||
local/
|
local/
|
||||||
venv/
|
|
||||||
|
|
12
README.md
|
@ -32,13 +32,15 @@ Today, you can use a Twitter, Mastodon, Telegram, and Mail with the account.
|
||||||
They will communicate with each other; if someone warns others via Mail,
|
They will communicate with each other; if someone warns others via Mail,
|
||||||
Telegram, Twitter and Mastodon users will also see the message. And vice versa.
|
Telegram, Twitter and Mastodon users will also see the message. And vice versa.
|
||||||
|
|
||||||
In version 2, this repository contains a web application. On this website,
|
In version 3, this repository contains a web application. On this website,
|
||||||
people can register an own bot for their city - the website manages multiple
|
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.
|
bots for multiple citys, which run in parallel. This way, you do not have to
|
||||||
|
host it yourself, if you lack the know-how. But it is easily possible to do so
|
||||||
|
with docker: http://github.com/ticketfrei/docker-ticketfrei/
|
||||||
|
|
||||||
In the promotion folder, you'll find some promotion material you can use to
|
In https://github.com/ticketfrei/promotion, you'll find some promotion material
|
||||||
build up such a community in your city. Unfortunately it is in german - but
|
you can use to build up such a community in your city. Unfortunately it is in
|
||||||
it's editable, feel free to translate it!
|
german - but it's editable, feel free to translate it!
|
||||||
|
|
||||||
Website (our flagship instance): https://ticketfrei.links-tech.org
|
Website (our flagship instance): https://ticketfrei.links-tech.org
|
||||||
|
|
||||||
|
|
37
setup.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from setuptools import setup
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
PACKAGE_NAME = "ticketfrei"
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), PACKAGE_NAME))
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name=PACKAGE_NAME,
|
||||||
|
version='',
|
||||||
|
packages=[
|
||||||
|
PACKAGE_NAME
|
||||||
|
],
|
||||||
|
url='https://github.com/ticketfrei/ticketfrei',
|
||||||
|
license='ISC',
|
||||||
|
author='',
|
||||||
|
author_email='',
|
||||||
|
description='',
|
||||||
|
setup_requires=[
|
||||||
|
'pytest-runner',
|
||||||
|
],
|
||||||
|
install_requires=[
|
||||||
|
'bottle',
|
||||||
|
'gitpython',
|
||||||
|
'pyjwt',
|
||||||
|
'Markdown',
|
||||||
|
'Mastodon.py',
|
||||||
|
'pylibscrypt',
|
||||||
|
'pytoml',
|
||||||
|
'tweepy',
|
||||||
|
'twx',
|
||||||
|
],
|
||||||
|
tests_require=[
|
||||||
|
'pytest',
|
||||||
|
],
|
||||||
|
)
|
|
@ -1,2 +0,0 @@
|
||||||
% rebase('template/wrapper.tpl', title='Login')
|
|
||||||
% include('template/login-plain.tpl')
|
|
|
@ -1,164 +0,0 @@
|
||||||
% rebase('template/wrapper.tpl')
|
|
||||||
<a href="/logout/"><button>Logout</button></a>
|
|
||||||
|
|
||||||
% if enabled:
|
|
||||||
<div id="enablebutton" style="float: right; padding: 2em;">Disable</div>
|
|
||||||
% else:
|
|
||||||
<div id="enablebutton" style="float: right; padding: 2em;" color="red">Enable</div>
|
|
||||||
% end
|
|
||||||
|
|
||||||
<a class='button' style="padding: 1.5em;" href="/login/twitter">
|
|
||||||
<picture>
|
|
||||||
<source type='image/webp' sizes='20px' srcset="/static-cb/1517673283/twitter-20.webp 20w,/static-cb/1517673283/twitter-40.webp 40w,/static-cb/1517673283/twitter-80.webp 80w,"/>
|
|
||||||
<source type='image/png' sizes='20px' srcset="/static-cb/1517673283/twitter-20.png 20w,/static-cb/1517673283/twitter-40.png 40w,/static-cb/1517673283/twitter-80.png 80w,"/>
|
|
||||||
<img src="https://patriciaannbridewell.files.wordpress.com/2014/04/official-twitter-logo-tile.png" alt="" />
|
|
||||||
</picture>
|
|
||||||
Log in with Twitter
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Log in with Mastodon</h2>
|
|
||||||
<form action="/login/mastodon" method='post'>
|
|
||||||
<label for="email">E-Mail of your Mastodon-Account</label>
|
|
||||||
<input type="text" placeholder="Enter Email" name="email" id="email" required>
|
|
||||||
|
|
||||||
<label for="pass">Mastodon Password</label>
|
|
||||||
<input type="password" placeholder="Enter Password" name="pass" id="pass" required>
|
|
||||||
|
|
||||||
<label>Mastodon instance:
|
|
||||||
<input type='text' name='instance_url' list='instances' placeholder='social.example.net'/>
|
|
||||||
</label>
|
|
||||||
<datalist id='instances'>
|
|
||||||
<option value=''>
|
|
||||||
<option value='anticapitalist.party'>
|
|
||||||
<option value='awoo.space'>
|
|
||||||
<option value='cybre.space'>
|
|
||||||
<option value='mastodon.social'>
|
|
||||||
<option value='glitch.social'>
|
|
||||||
<option value='botsin.space'>
|
|
||||||
<option value='witches.town'>
|
|
||||||
<option value='social.wxcafe.net'>
|
|
||||||
<option value='monsterpit.net'>
|
|
||||||
<option value='mastodon.xyz'>
|
|
||||||
<option value='a.weirder.earth'>
|
|
||||||
<option value='chitter.xyz'>
|
|
||||||
<option value='sins.center'>
|
|
||||||
<option value='dev.glitch.social'>
|
|
||||||
<option value='computerfairi.es'>
|
|
||||||
<option value='niu.moe'>
|
|
||||||
<option value='icosahedron.website'>
|
|
||||||
<option value='hostux.social'>
|
|
||||||
<option value='hyenas.space'>
|
|
||||||
<option value='instance.business'>
|
|
||||||
<option value='mastodon.sdf.org'>
|
|
||||||
<option value='pawoo.net'>
|
|
||||||
<option value='pouet.it'>
|
|
||||||
<option value='scalie.business'>
|
|
||||||
<option value='sleeping.town'>
|
|
||||||
<option value='social.koyu.space'>
|
|
||||||
<option value='sunshinegardens.org'>
|
|
||||||
<option value='vcity.network'>
|
|
||||||
<option value='octodon.social'>
|
|
||||||
<option value='soc.ialis.me'>
|
|
||||||
</datalist>
|
|
||||||
<input name='csrf' value='{{csrf}}' type='hidden' />
|
|
||||||
<input name='confirm' value='Log in' type='submit'/>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<%
|
|
||||||
# todo: hide this part, if there is already a telegram bot connected.
|
|
||||||
%>
|
|
||||||
<div>
|
|
||||||
<h2>Connect with Telegram</h2>
|
|
||||||
<p>
|
|
||||||
If you have a Telegram account, you can register a bot there. Just
|
|
||||||
write to @botfather. There are detailed instructions on
|
|
||||||
<a href="https://botsfortelegram.com/project/the-bot-father/" target="_blank">
|
|
||||||
Bots for Telegram</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The botfather will give you an API key - with the API key, Ticketfrei
|
|
||||||
can use the Telegram bot. Enter it here:
|
|
||||||
</p>
|
|
||||||
<form action="/settings/telegram" method="post">
|
|
||||||
<input type="text" name="apikey" placeholder="Telegram bot API key" id="apikey">
|
|
||||||
<input name='csrf' value='{{csrf}}' type='hidden' />
|
|
||||||
<input name='confirm' value='Login with Telegram' type='submit'/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Edit your city page</h2>
|
|
||||||
<p>
|
|
||||||
With your bot, we generated you a page, which you can use for promotion:
|
|
||||||
<a href="/city/{{city}}" target="_blank">Ticketfrei {{city}}</a> You
|
|
||||||
can change what your users will read there, and adjust it to your
|
|
||||||
needs.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b>You should definitely adjust the Social Media, E-Mail, and Telegram
|
|
||||||
profile links.</b>
|
|
||||||
Also consider adding this link to the text: <a href="/city/mail/{{city}}"
|
|
||||||
target="_blank">Link to the mail subscription page</a>. Your readers
|
|
||||||
can use this to subscribe to mail notifications.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
So this is the default text we suggest:
|
|
||||||
</p>
|
|
||||||
<form action="/settings/markdown" method="post">
|
|
||||||
<textarea id="markdown" rows="20" cols="70" name="markdown" wrap="physical">{{markdown}}</textarea>
|
|
||||||
<input name='csrf' value='{{csrf}}' type='hidden' />
|
|
||||||
<input name='confirm' value='Save' type='submit'/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Edit your mail subscription page</h2>
|
|
||||||
<p>
|
|
||||||
There is also a page where users can subscribe to mail notifications:
|
|
||||||
<a href="/city/mail/{{city}}" target="_blank">Ticketfrei {{city}}</a>.
|
|
||||||
You can change what your users will read there, and adjust it to your
|
|
||||||
needs.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
So this is the default text we suggest:
|
|
||||||
</p>
|
|
||||||
<form action="/settings/mail_md" method="post">
|
|
||||||
<textarea id="mail_md" rows="20" cols="70" name="mail_md" wrap="physical">{{mail_md}}</textarea>
|
|
||||||
<input name='csrf' value='{{csrf}}' type='hidden' />
|
|
||||||
<input name='confirm' value='Save' type='submit'/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Edit your trigger patterns</h2>
|
|
||||||
<p>
|
|
||||||
These words have to be contained in a report. If none of these
|
|
||||||
expressions is in the report, it will be ignored by the bot. You can
|
|
||||||
use the defaults, or enter some expressions specific to your city and
|
|
||||||
language.
|
|
||||||
</p>
|
|
||||||
<form action="/settings/goodlist" method="post">
|
|
||||||
<textarea id="goodlist" rows="8" cols="70" name="goodlist" wrap="physical">{{triggerwords}}</textarea>
|
|
||||||
<input name='csrf' value='{{csrf}}' type='hidden' />
|
|
||||||
<input name='confirm' value='Submit' type='submit'/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Edit the blocklist</h2>
|
|
||||||
<p>
|
|
||||||
These words are not allowed in reports. If you encounter spam, you can
|
|
||||||
add more here - the bot will ignore reports which use such words.
|
|
||||||
There are words which you can't exclude from the blocklist, e.g.
|
|
||||||
certain racist, sexist, or antisemitic slurs.
|
|
||||||
</p>
|
|
||||||
<form action="/settings/blocklist" method="post">
|
|
||||||
<textarea id="blocklist" rows="8" cols="70" name="blocklist" wrap="physical">{{badwords}}</textarea>
|
|
||||||
<input name='csrf' value='{{csrf}}' type='hidden' />
|
|
||||||
<input name='confirm' value='Submit' type='submit'/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
0
ticketfrei/__init__.py
Normal file
|
@ -17,7 +17,7 @@ def shutdown():
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
fh = logging.FileHandler('/var/log/ticketfrei/backend.log')
|
fh = logging.FileHandler(config["log"]["log_backend"])
|
||||||
fh.setLevel(logging.DEBUG)
|
fh.setLevel(logging.DEBUG)
|
||||||
logger.addHandler(fh)
|
logger.addHandler(fh)
|
||||||
|
|
19
ticketfrei/bots/mail/settings.tpl
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Edit your mail subscription page</h2>
|
||||||
|
<p>
|
||||||
|
There is also a page where users can subscribe to mail notifications:
|
||||||
|
<a href="/city/mail/{{city}}" target="_blank">Ticketfrei {{city}}</a>.
|
||||||
|
You can change what your users will read there, and adjust it to your
|
||||||
|
needs.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
So this is the default text we suggest:
|
||||||
|
</p>
|
||||||
|
<form action="/settings/mail_md" method="post">
|
||||||
|
<textarea id="mail_md" rows="20" cols="70" name="mail_md" wrap="physical">{{mail_md}}</textarea>
|
||||||
|
<input name='csrf' value='{{csrf}}' type='hidden' />
|
||||||
|
<input name='confirm' value='Save' type='submit'/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
51
ticketfrei/bots/mastodon/settings.tpl
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Log in with Mastodon</h2>
|
||||||
|
<form action="/login/mastodon" method='post'>
|
||||||
|
<label for="email">E-Mail of your Mastodon-Account</label>
|
||||||
|
<input type="text" placeholder="Enter Email" name="email" id="email" required>
|
||||||
|
|
||||||
|
<label for="pass">Mastodon Password</label>
|
||||||
|
<input type="password" placeholder="Enter Password" name="pass" id="pass" required>
|
||||||
|
|
||||||
|
<label>Mastodon instance:
|
||||||
|
<input type='text' name='instance_url' list='instances' placeholder='social.example.net'/>
|
||||||
|
</label>
|
||||||
|
<datalist id='instances'>
|
||||||
|
<option value=''>
|
||||||
|
<option value='anticapitalist.party'>
|
||||||
|
<option value='awoo.space'>
|
||||||
|
<option value='cybre.space'>
|
||||||
|
<option value='mastodon.social'>
|
||||||
|
<option value='glitch.social'>
|
||||||
|
<option value='botsin.space'>
|
||||||
|
<option value='witches.town'>
|
||||||
|
<option value='social.wxcafe.net'>
|
||||||
|
<option value='monsterpit.net'>
|
||||||
|
<option value='mastodon.xyz'>
|
||||||
|
<option value='a.weirder.earth'>
|
||||||
|
<option value='chitter.xyz'>
|
||||||
|
<option value='sins.center'>
|
||||||
|
<option value='dev.glitch.social'>
|
||||||
|
<option value='computerfairi.es'>
|
||||||
|
<option value='niu.moe'>
|
||||||
|
<option value='icosahedron.website'>
|
||||||
|
<option value='hostux.social'>
|
||||||
|
<option value='hyenas.space'>
|
||||||
|
<option value='instance.business'>
|
||||||
|
<option value='mastodon.sdf.org'>
|
||||||
|
<option value='pawoo.net'>
|
||||||
|
<option value='pouet.it'>
|
||||||
|
<option value='scalie.business'>
|
||||||
|
<option value='sleeping.town'>
|
||||||
|
<option value='social.koyu.space'>
|
||||||
|
<option value='sunshinegardens.org'>
|
||||||
|
<option value='vcity.network'>
|
||||||
|
<option value='octodon.social'>
|
||||||
|
<option value='soc.ialis.me'>
|
||||||
|
</datalist>
|
||||||
|
<input name='csrf' value='{{csrf}}' type='hidden' />
|
||||||
|
<input name='confirm' value='Log in' type='submit'/>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
23
ticketfrei/bots/telegram/settings.tpl
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
<%
|
||||||
|
# todo: hide this part, if there is already a telegram bot connected.
|
||||||
|
%>
|
||||||
|
<div>
|
||||||
|
<h2>Connect with Telegram</h2>
|
||||||
|
<p>
|
||||||
|
If you have a Telegram account, you can register a bot there. Just
|
||||||
|
write to @botfather. There are detailed instructions on
|
||||||
|
<a href="https://botsfortelegram.com/project/the-bot-father/" target="_blank">
|
||||||
|
Bots for Telegram</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The botfather will give you an API key - with the API key, Ticketfrei
|
||||||
|
can use the Telegram bot. Enter it here:
|
||||||
|
</p>
|
||||||
|
<form action="/settings/telegram" method="post">
|
||||||
|
<input type="text" name="apikey" placeholder="Telegram bot API key" id="apikey">
|
||||||
|
<input name='csrf' value='{{csrf}}' type='hidden' />
|
||||||
|
<input name='confirm' value='Login with Telegram' type='submit'/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
10
ticketfrei/bots/twitter/settings.tpl
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
<a class='button' style="padding: 1.5em;" href="/login/twitter">
|
||||||
|
<picture>
|
||||||
|
<source type='image/webp' sizes='20px' srcset="/static-cb/1517673283/twitter-20.webp 20w,/static-cb/1517673283/twitter-40.webp 40w,/static-cb/1517673283/twitter-80.webp 80w,"/>
|
||||||
|
<source type='image/png' sizes='20px' srcset="/static-cb/1517673283/twitter-20.png 20w,/static-cb/1517673283/twitter-40.png 40w,/static-cb/1517673283/twitter-80.png 80w,"/>
|
||||||
|
<img src="https://patriciaannbridewell.files.wordpress.com/2014/04/official-twitter-logo-tile.png" alt="" />
|
||||||
|
</picture>
|
||||||
|
Log in with Twitter
|
||||||
|
</a>
|
||||||
|
|
0
ticketfrei/bots/twitterDMs/settings.tpl
Normal file
|
@ -1,6 +1,10 @@
|
||||||
import pytoml as toml
|
import pytoml as toml
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
TEMPLATE_DIR = os.path.join(ROOT_DIR, 'template', '')
|
||||||
|
STATIC_DIR = os.path.join(ROOT_DIR, 'static', '')
|
||||||
|
BOT_DIR = os.path.join(ROOT_DIR, 'bots')
|
||||||
|
|
||||||
def load_env():
|
def load_env():
|
||||||
"""
|
"""
|
||||||
|
@ -9,7 +13,7 @@ def load_env():
|
||||||
|
|
||||||
:return: config dictionary of dictionaries.
|
:return: config dictionary of dictionaries.
|
||||||
"""
|
"""
|
||||||
with open('config.toml.example') as defaultconf:
|
with open(os.path.join(ROOT_DIR, 'config.toml.example')) as defaultconf:
|
||||||
configdict = toml.load(defaultconf)
|
configdict = toml.load(defaultconf)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -54,12 +58,24 @@ def load_env():
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.environ['LOG_FRONTEND'] != "":
|
||||||
|
configdict['log']['log_frontend'] = os.environ['LOG_FRONTEND']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.environ['LOG_BACKEND'] != "":
|
||||||
|
configdict['log']['log_backend'] = os.environ['LOG_BACKEND']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
return configdict
|
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:
|
try:
|
||||||
with open('config.toml') as configfile:
|
with open(os.path.join(ROOT_DIR, 'config.toml')) as configfile:
|
||||||
config = toml.load(configfile)
|
config = toml.load(configfile)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
config = load_env()
|
config = load_env()
|
|
@ -11,6 +11,11 @@ contact = "b3yond@riseup.net"
|
||||||
|
|
||||||
[mail]
|
[mail]
|
||||||
mbox_user = "root"
|
mbox_user = "root"
|
||||||
|
aliases_path = "/etc/aliases"
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
db_path = "/var/ticketfrei/db.sqlite"
|
db_path = "/var/ticketfrei/db.sqlite"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
log_frontend = "/var/ticketfrei/frontend.log"
|
||||||
|
log_backend = "/var/log/ticketfrei/backend.log"
|
|
@ -243,7 +243,7 @@ u\d\d?"""
|
||||||
(uid, "bastard"))
|
(uid, "bastard"))
|
||||||
else:
|
else:
|
||||||
uid = json['uid']
|
uid = json['uid']
|
||||||
with open("/etc/aliases", "a+") as f:
|
with open(config['mail']['aliases_path'], "a+") as f:
|
||||||
f.write(city + ": " + config["mail"]["mbox_user"] + "\n")
|
f.write(city + ": " + config["mail"]["mbox_user"] + "\n")
|
||||||
self.execute("INSERT INTO email (user_id, email) VALUES(?, ?);",
|
self.execute("INSERT INTO email (user_id, email) VALUES(?, ?);",
|
||||||
(uid, json['email']))
|
(uid, json['email']))
|
|
@ -1,7 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import bottle
|
import bottle
|
||||||
|
from os import listdir, path
|
||||||
from bottle import get, post, redirect, request, response, view
|
from bottle import get, post, redirect, request, response, view
|
||||||
from config import config
|
from config import config, STATIC_DIR, TEMPLATE_DIR
|
||||||
from db import db
|
from db import db
|
||||||
import logging
|
import logging
|
||||||
import tweepy
|
import tweepy
|
||||||
|
@ -18,13 +19,13 @@ def url(route):
|
||||||
|
|
||||||
|
|
||||||
@get('/')
|
@get('/')
|
||||||
@view('template/propaganda.tpl')
|
@view('propaganda.tpl')
|
||||||
def propaganda():
|
def propaganda():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@post('/register')
|
@post('/register')
|
||||||
@view('template/register.tpl')
|
@view('register.tpl')
|
||||||
def register_post():
|
def register_post():
|
||||||
try:
|
try:
|
||||||
email = request.forms['email']
|
email = request.forms['email']
|
||||||
|
@ -54,7 +55,7 @@ def register_post():
|
||||||
|
|
||||||
|
|
||||||
@get('/confirm/<city>/<token>')
|
@get('/confirm/<city>/<token>')
|
||||||
@view('template/propaganda.tpl')
|
@view('propaganda.tpl')
|
||||||
def confirm(city, token):
|
def confirm(city, token):
|
||||||
# check whether city already exists
|
# check whether city already exists
|
||||||
if db.by_city(city):
|
if db.by_city(city):
|
||||||
|
@ -75,7 +76,7 @@ def version():
|
||||||
|
|
||||||
|
|
||||||
@post('/login')
|
@post('/login')
|
||||||
@view('template/login.tpl')
|
@view('login.tpl')
|
||||||
def login_post():
|
def login_post():
|
||||||
# check login
|
# check login
|
||||||
try:
|
try:
|
||||||
|
@ -94,14 +95,14 @@ def city_page(city, info=None):
|
||||||
citydict = db.user_facing_properties(city)
|
citydict = db.user_facing_properties(city)
|
||||||
if citydict is not None:
|
if citydict is not None:
|
||||||
citydict['info'] = info
|
citydict['info'] = info
|
||||||
return bottle.template('template/city.tpl', **citydict)
|
return bottle.template('city.tpl', **citydict)
|
||||||
return bottle.template('template/propaganda.tpl',
|
return bottle.template('propaganda.tpl',
|
||||||
**dict(info='There is no Ticketfrei bot in your city'
|
**dict(info='There is no Ticketfrei bot in your city'
|
||||||
' yet. Create one yourself!'))
|
' yet. Create one yourself!'))
|
||||||
|
|
||||||
|
|
||||||
@get('/city/mail/<city>')
|
@get('/city/mail/<city>')
|
||||||
@view('template/mail.tpl')
|
@view('mail.tpl')
|
||||||
def display_mail_page(city):
|
def display_mail_page(city):
|
||||||
user = db.by_city(city)
|
user = db.by_city(city)
|
||||||
return user.state()
|
return user.state()
|
||||||
|
@ -138,34 +139,34 @@ def unsubscribe(token):
|
||||||
|
|
||||||
|
|
||||||
@get('/settings')
|
@get('/settings')
|
||||||
@view('template/settings.tpl')
|
@view('settings.tpl')
|
||||||
def settings(user):
|
def settings(user):
|
||||||
return user.state()
|
return user.state()
|
||||||
|
|
||||||
|
|
||||||
@post('/settings/markdown')
|
@post('/settings/markdown')
|
||||||
@view('template/settings.tpl')
|
@view('settings.tpl')
|
||||||
def update_markdown(user):
|
def update_markdown(user):
|
||||||
user.set_markdown(request.forms['markdown'])
|
user.set_markdown(request.forms['markdown'])
|
||||||
return user.state()
|
return user.state()
|
||||||
|
|
||||||
|
|
||||||
@post('/settings/mail_md')
|
@post('/settings/mail_md')
|
||||||
@view('template/settings.tpl')
|
@view('settings.tpl')
|
||||||
def update_mail_md(user):
|
def update_mail_md(user):
|
||||||
user.set_mail_md(request.forms['mail_md'])
|
user.set_mail_md(request.forms['mail_md'])
|
||||||
return user.state()
|
return user.state()
|
||||||
|
|
||||||
|
|
||||||
@post('/settings/goodlist')
|
@post('/settings/goodlist')
|
||||||
@view('template/settings.tpl')
|
@view('settings.tpl')
|
||||||
def update_trigger_patterns(user):
|
def update_trigger_patterns(user):
|
||||||
user.set_trigger_words(request.forms['goodlist'])
|
user.set_trigger_words(request.forms['goodlist'])
|
||||||
return user.state()
|
return user.state()
|
||||||
|
|
||||||
|
|
||||||
@post('/settings/blocklist')
|
@post('/settings/blocklist')
|
||||||
@view('template/settings.tpl')
|
@view('settings.tpl')
|
||||||
def update_badwords(user):
|
def update_badwords(user):
|
||||||
user.set_badwords(request.forms['blocklist'])
|
user.set_badwords(request.forms['blocklist'])
|
||||||
return user.state()
|
return user.state()
|
||||||
|
@ -186,12 +187,12 @@ def register_telegram(user):
|
||||||
|
|
||||||
@get('/static/<filename:path>')
|
@get('/static/<filename:path>')
|
||||||
def static(filename):
|
def static(filename):
|
||||||
return bottle.static_file(filename, root='static')
|
return bottle.static_file(filename, root=STATIC_DIR)
|
||||||
|
|
||||||
|
# IS THIS USED?
|
||||||
@get('/guides/<filename:path>')
|
#@get('/guides/<filename:path>')
|
||||||
def guides(filename):
|
#def guides(filename):
|
||||||
return bottle.static_file(filename, root='guides')
|
# return bottle.static_file(filename, root='guides')
|
||||||
|
|
||||||
|
|
||||||
@get('/logout/')
|
@get('/logout/')
|
||||||
|
@ -264,10 +265,12 @@ def login_mastodon(user):
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
fh = logging.FileHandler('/var/log/ticketfrei/error.log')
|
fh = logging.FileHandler(config['log']['log_frontend'])
|
||||||
fh.setLevel(logging.DEBUG)
|
fh.setLevel(logging.DEBUG)
|
||||||
logger.addHandler(fh)
|
logger.addHandler(fh)
|
||||||
|
|
||||||
|
# TODO change TEMPLATE_PATH to BOTS_DIR after refactoring
|
||||||
|
bottle.TEMPLATE_PATH.insert(0, TEMPLATE_DIR)
|
||||||
application = bottle.default_app()
|
application = bottle.default_app()
|
||||||
bottle.install(SessionPlugin('/'))
|
bottle.install(SessionPlugin('/'))
|
||||||
|
|
Before Width: | Height: | Size: 628 KiB After Width: | Height: | Size: 628 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
@ -1,4 +1,4 @@
|
||||||
% rebase('template/wrapper.tpl')
|
% rebase('wrapper.tpl')
|
||||||
|
|
||||||
<%
|
<%
|
||||||
import markdown as md
|
import markdown as md
|
2
ticketfrei/template/login.tpl
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
% rebase('wrapper.tpl', title='Login')
|
||||||
|
% include('login-plain.tpl')
|
|
@ -1,4 +1,4 @@
|
||||||
% rebase('template/wrapper.tpl')
|
% rebase('wrapper.tpl')
|
||||||
|
|
||||||
<%
|
<%
|
||||||
import markdown as md
|
import markdown as md
|
|
@ -1,4 +1,4 @@
|
||||||
% rebase('template/wrapper.tpl')
|
% rebase('wrapper.tpl')
|
||||||
% if defined('info'):
|
% if defined('info'):
|
||||||
<div class="ui-widget">
|
<div class="ui-widget">
|
||||||
<div class="ui-state-highlight ui-corner-all" style="padding: 0.7em;">
|
<div class="ui-state-highlight ui-corner-all" style="padding: 0.7em;">
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
% end
|
% end
|
||||||
% include('template/login-plain.tpl')
|
% include('login-plain.tpl')
|
||||||
<h1>Features</h1>
|
<h1>Features</h1>
|
||||||
<p>
|
<p>
|
||||||
Don't pay for public transport. Instead, warn each other
|
Don't pay for public transport. Instead, warn each other
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
share it with us, so others can use it, too!</li>
|
share it with us, so others can use it, too!</li>
|
||||||
</ul></li>
|
</ul></li>
|
||||||
</ul>
|
</ul>
|
||||||
% include('template/register-plain.tpl')
|
% include('register-plain.tpl')
|
||||||
<h2>Our Mission</h2>
|
<h2>Our Mission</h2>
|
||||||
<p>
|
<p>
|
||||||
Public transportation is meant to provide an easy and
|
Public transportation is meant to provide an easy and
|
|
@ -1,4 +1,4 @@
|
||||||
% rebase('template/wrapper.tpl', title='Register')
|
% rebase('wrapper.tpl', title='Register')
|
||||||
% if defined('info'):
|
% if defined('info'):
|
||||||
<div class="ui-widget">
|
<div class="ui-widget">
|
||||||
<div class="ui-state-highlight ui-corner-all" style="padding: 0.7em;">
|
<div class="ui-state-highlight ui-corner-all" style="padding: 0.7em;">
|
||||||
|
@ -6,5 +6,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
% else:
|
% else:
|
||||||
% include('template/register-plain.tpl')
|
% include('register-plain.tpl')
|
||||||
% end
|
% end
|
76
ticketfrei/template/settings.tpl
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
% rebase('wrapper.tpl')
|
||||||
|
<a href="/logout/"><button>Logout</button></a>
|
||||||
|
|
||||||
|
% if enabled:
|
||||||
|
<div id="enablebutton" style="float: right; padding: 2em;">Disable</div>
|
||||||
|
% else:
|
||||||
|
<div id="enablebutton" style="float: right; padding: 2em;" color="red">Enable</div>
|
||||||
|
% end
|
||||||
|
|
||||||
|
<%
|
||||||
|
# import all the settings templates from bots/*/settings.tpl
|
||||||
|
from config import BOT_DIR
|
||||||
|
import os
|
||||||
|
bots = os.listdir(BOT_DIR)
|
||||||
|
|
||||||
|
for bot in bots:
|
||||||
|
include(os.path.join(BOT_DIR, bot, 'settings.tpl'), csrf=csrf, city=city)
|
||||||
|
end
|
||||||
|
%>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Edit your city page</h2>
|
||||||
|
<p>
|
||||||
|
With your bot, we generated you a page, which you can use for promotion:
|
||||||
|
<a href="/city/{{city}}" target="_blank">Ticketfrei {{city}}</a> You
|
||||||
|
can change what your users will read there, and adjust it to your
|
||||||
|
needs.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>You should definitely adjust the Social Media, E-Mail, and Telegram
|
||||||
|
profile links.</b>
|
||||||
|
Also consider adding this link to the text: <a href="/city/mail/{{city}}"
|
||||||
|
target="_blank">Link to the mail subscription page</a>. Your readers
|
||||||
|
can use this to subscribe to mail notifications.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
So this is the default text we suggest:
|
||||||
|
</p>
|
||||||
|
<form action="/settings/markdown" method="post">
|
||||||
|
<textarea id="markdown" rows="20" cols="70" name="markdown" wrap="physical">{{markdown}}</textarea>
|
||||||
|
<input name='csrf' value='{{csrf}}' type='hidden' />
|
||||||
|
<input name='confirm' value='Save' type='submit'/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Edit your trigger patterns</h2>
|
||||||
|
<p>
|
||||||
|
These words have to be contained in a report. If none of these
|
||||||
|
expressions is in the report, it will be ignored by the bot. You can
|
||||||
|
use the defaults, or enter some expressions specific to your city and
|
||||||
|
language.
|
||||||
|
</p>
|
||||||
|
<form action="/settings/goodlist" method="post">
|
||||||
|
<textarea id="goodlist" rows="8" cols="70" name="goodlist" wrap="physical">{{triggerwords}}</textarea>
|
||||||
|
<input name='csrf' value='{{csrf}}' type='hidden' />
|
||||||
|
<input name='confirm' value='Submit' type='submit'/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Edit the blocklist</h2>
|
||||||
|
<p>
|
||||||
|
These words are not allowed in reports. If you encounter spam, you can
|
||||||
|
add more here - the bot will ignore reports which use such words.
|
||||||
|
There are words which you can't exclude from the blocklist, e.g.
|
||||||
|
certain racist, sexist, or antisemitic slurs.
|
||||||
|
</p>
|
||||||
|
<form action="/settings/blocklist" method="post">
|
||||||
|
<textarea id="blocklist" rows="8" cols="70" name="blocklist" wrap="physical">{{badwords}}</textarea>
|
||||||
|
<input name='csrf' value='{{csrf}}' type='hidden' />
|
||||||
|
<input name='confirm' value='Submit' type='submit'/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
20
ticketfrei/tests/configs/webapptestconfig.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[twitter]
|
||||||
|
# You get those keys when you follow these steps:
|
||||||
|
# https://developer.twitter.com/en/docs/basics/authentication/guides/access-tokens
|
||||||
|
consumer_key = "your_consumer_key"
|
||||||
|
consumer_secret = "your_consumer_secret"
|
||||||
|
|
||||||
|
[web]
|
||||||
|
host = "0.0.0.0" # will be used by bottle as a host.
|
||||||
|
port = 80
|
||||||
|
contact = "b3yond@riseup.net"
|
||||||
|
|
||||||
|
[mail]
|
||||||
|
mbox_user = "root"
|
||||||
|
|
||||||
|
[database]
|
||||||
|
db_path = "/var/ticketfrei/db.sqlite"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
log_frontend = "/var/ticketfrei/frontend.log"
|
||||||
|
log_backend = "/var/log/ticketfrei/backend.log"
|
23
ticketfrei/tests/webapptests/test_login.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from webtest import TestApp
|
||||||
|
import unittest
|
||||||
|
import frontend
|
||||||
|
|
||||||
|
app = TestApp(frontend.application)
|
||||||
|
|
||||||
|
class TestLogin(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_login_not_registered(self):
|
||||||
|
request = app.post('/login', {'email': '', 'pass': ''}, expect_errors=True)
|
||||||
|
self.assertEqual(401, request.status_code)
|
||||||
|
|
||||||
|
def test_login_registered(self):
|
||||||
|
request = app.post('/register', {'email': 'foo@abc.de', 'pass': 'bar', 'pass-repeat': 'bar', 'city': 'testcity'}, expect_errors=True)
|
||||||
|
request = app.post('/login', {'email': 'foor@abc.de', 'pass': 'bar'}, expect_errors=False)
|
||||||
|
self.assertEqual(200, request.status_code)
|
||||||
|
|
0
ticketfrei/tests/webapptests/test_logout.py
Normal file
0
ticketfrei/tests/webapptests/test_mailhandling.py
Normal file
16
ticketfrei/tests/webapptests/test_register.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from webtest import TestApp
|
||||||
|
import unittest
|
||||||
|
import frontend
|
||||||
|
|
||||||
|
app = TestApp(frontend.application)
|
||||||
|
|
||||||
|
class TestRegister(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_register(self):
|
||||||
|
request = app.post('/register', {'email': 'foo@abc.de', 'pass': 'bar', 'pass-repeat': 'bar', 'city': 'testcity'}, expect_errors=True)
|
||||||
|
self.assertEqual(200, request.status_code)
|
||||||
|
|
||||||
|
def test_getRoot(self):
|
||||||
|
request = app.get('/')
|
||||||
|
self.assertEqual(200, request.status_code)
|
||||||
|
|