Merge branch 'multi-deployment' into multi-deployment

master
sid 2018-05-28 21:18:22 +02:00 committed by GitHub
commit c9b5fd1d5c
9 changed files with 256 additions and 150 deletions

View File

@ -91,7 +91,7 @@ virtualenv -p python3 .
Install the dependencies:
```shell
pip install tweepy pytoml Mastodon.py bottle pyjwt pylibscrypt
pip install tweepy pytoml Mastodon.py bottle pyjwt pylibscrypt Markdown
```
Configure the bot:

43
db.py
View File

@ -42,13 +42,13 @@ class DB(object):
CREATE TABLE IF NOT EXISTS triggerpatterns (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
pattern TEXT,
patterns TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS badwords (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
word TEXT,
words TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS mastodon_instances (
@ -137,7 +137,8 @@ class DB(object):
markdown TEXT,
masto_link TEXT,
twit_link TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
FOREIGN KEY(user_id) REFERENCES user(id),
UNIQUE(user_id, city) ON CONFLICT IGNORE
);
''')
@ -149,7 +150,7 @@ class DB(object):
).decode('ascii')
}, self.secret).decode('ascii')
def confirm(self, token):
def confirm(self, token, city):
from user import User
try:
json = jwt.decode(token, self.secret)
@ -160,16 +161,37 @@ class DB(object):
self.execute("INSERT INTO user (passhash) VALUES(?);",
(json['passhash'], ))
uid = self.cur.lastrowid
self.execute("""
INSERT INTO triggerpatterns (user_id, pattern)
VALUES(?, ?);
""", (uid, '.*'))
default_triggerpatterns = """
kontroll?e
konti
db
vgn
vag
zivil
sicherheit
uniform
station
bus
bahn
tram
linie
nuernberg
nürnberg
s\d
u\d\d?
"""
self.execute("""INSERT INTO triggerpatterns (user_id, patterns)
VALUES(?, ?); """, (uid, default_triggerpatterns))
self.execute("INSERT INTO badwords (user_id, words) VALUES(?, ?);",
(uid, "bastard"))
else:
uid = json['uid']
self.execute("INSERT INTO email (user_id, email) VALUES(?, ?);",
(uid, json['email']))
self.commit()
return User(uid)
user = User(uid)
user.set_city(city)
return user
def by_email(self, email):
from user import User
@ -189,7 +211,8 @@ class DB(object):
return dict(city=city,
markdown=markdown,
masto_link=masto_link,
twit_link=twit_link)
twit_link=twit_link,
mailinglist=city + "@" + config["web"]["host"])
except TypeError:
return None

View File

@ -29,6 +29,7 @@ def register_post():
email = request.forms['email']
password = request.forms['pass']
password_repeat = request.forms['pass-repeat']
city = request.forms['city']
except KeyError:
return dict(error='Please, fill the form.')
if password != password_repeat:
@ -37,11 +38,12 @@ def register_post():
return dict(error='Email address already in use.')
# send confirmation mail
try:
print(url('confirm/' + city + '/%s' % db.user_token(email, password))) # only for local testing
sendmail(
email,
"Confirm your account",
"Complete your registration here: %s" % (
url('confirm/%s' % db.user_token(email, password))
url('confirm/' + city + '/%s' % db.user_token(email, password))
)
)
return dict(info='Confirmation mail sent.')
@ -50,11 +52,11 @@ def register_post():
return dict(error='Could not send confirmation mail.')
@get('/confirm/<token>')
@get('/confirm/<city>/<token>')
@view('template/propaganda.tpl')
def confirm(token):
def confirm(city, token):
# create db-entry
if db.confirm(token):
if db.confirm(token, city):
# :todo show info "Account creation successful."
redirect('/settings')
return dict(error='Email confirmation failed.')
@ -76,9 +78,13 @@ def login_post():
@get('/city/<city>')
@view('template/user-facing.tpl')
@view('template/city.tpl')
def city_page(city):
return db.user_facing_properties(city)
citydict = db.user_facing_properties(city)
if citydict is not None:
return citydict
redirect('/')
return dict(info='There is no Ticketfrei bot in your city yet. Create one yourself!')
@get('/settings')
@ -87,6 +93,27 @@ def settings(user):
return user.state()
@post('/settings/markdown')
@view('template/settings.tpl')
def update_markdown(user):
user.set_markdown(request.forms['markdown'])
return user.state()
@post('/settings/goodlist')
@view('template/settings.tpl')
def update_trigger_patterns(user):
user.set_trigger_words(request.forms['goodlist'])
return user.state()
@post('/settings/blacklist')
@view('template/settings.tpl')
def update_badwords(user):
user.set_badwords(request.forms['blacklist'])
return user.state()
@get('/api/state')
def api_enable(user):
return user.state()

BIN
promotion/vag-zeitung.xcf Normal file

Binary file not shown.

9
template/city.tpl Normal file
View File

@ -0,0 +1,9 @@
% rebase('template/wrapper.tpl')
<%
import markdown as md
html = md.markdown(markdown)
%>
{{!html}}

View File

@ -2,6 +2,9 @@
<label for="email">Email</label>
<input type="text" placeholder="Enter Email" name="email" id="email" required>
<label for="city">City</label>
<input type='text' name='city' placeholder='Barcelona'/>
<label for="pass">Password</label>
<input type="password" placeholder="Enter Password" name="pass" id="pass" required>

View File

@ -1,13 +1,17 @@
% rebase('template/wrapper.tpl')
<a href="/logout/"><button>Logout</button></a>
<div id="enablebutton" style="float: right; padding: 2em;">asdf</div>
% 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://codl.forget.fr/static-cb/1517673283/twitter-20.png" alt="" />
<img src="https://patriciaannbridewell.files.wordpress.com/2014/04/official-twitter-logo-tile.png" alt="" />
</picture>
Log in with Twitter
</a>
@ -63,10 +67,22 @@
</p>
</section>
<!-- offer mailing list creation button -->
<div style="float: left; padding: 1.5em;">
<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. <b>You should definitely adjust the Social Media profile links.</b> This is just 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='confirm' value='Save' type='submit'/>
</form>
</div>
<div style="float: left; padding: 1.5em;">
<!-- good list entry field -->
<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.
@ -74,13 +90,13 @@
</p>
<form action="/settings/goodlist" method="post">
<!-- find a way to display current good list. js which reads from a cookie? template? -->
<textarea id="goodlist" rows="8" cols="70" name="goodlist" wrap="physical"></textarea>
<textarea id="goodlist" rows="8" cols="70" name="goodlist" wrap="physical">{{triggerwords}}</textarea>
<input name='confirm' value='Submit' type='submit'/>
</form>
</div>
<!-- blacklist entry field -->
<div style="float:right; padding: 1.5em;">
<h2>Edit the blacklist</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.
@ -88,7 +104,7 @@
</p>
<form action="/settings/blacklist" method="post">
<!-- find a way to display current blacklist. js which reads from a cookie? template? -->
<textarea id="blacklist" rows="8" cols="70" name="blacklist" wrap="physical"></textarea>
<textarea id="blacklist" rows="8" cols="70" name="blacklist" wrap="physical">{{badwords}}</textarea>
<input name='confirm' value='Submit' type='submit'/>
</form>
</div>

View File

@ -1,115 +0,0 @@
% rebase('template/wrapper.tpl')
<h1>Wie funktioniert Ticketfrei?</h1>
<p>
Willst du mithelfen, Ticketkontrolleure zu überwachen? Willst du einen
Fahrscheinfreien ÖPNV erkämpfen?
</p>
<h2>Ist es gerade sicher, schwarz zu fahren?</h2>
<p>
Schau dir einfach das Profil unseres Bots an:
<a class="https" href="https://twitter.com/nbg_ticketfrei">https://twitter.com/nbg_ticketfrei</a>
</p>
<p>Hat jemand vor kurzem etwas über Kontrolleur*innen gepostet?</p>
<ul>
<li>
Wenn ja, dann kauf dir vllt lieber ein Ticket. In Nürnberg haben wir
die Erfahrung gemacht, dass Kontis normalerweile ungefähr ne Woche
aktiv sind, ein paar Stunden am Tag. Wenn es also in den letzten
Stunden einen Bericht gab, pass lieber auf.
</li>
<li>
Wenn nicht, ist es wahrscheinlich kein Problem :)
</li>
</ul>
<p>
Also, wenn du weniger Glück hast, und der erste bist, der einen Kontrolleur
sieht:
</p>
<h2>Was mache ich, wenn ich Kontis sehe?</h2>
<p>Ganz einfach, du schreibst es den anderen. Das geht entweder</p>
<ul>
<li>
mit Mastodon
</li>
<li>
über Twitter
</li>
<li>
oder per Mail, falls ihr kein Social Media habt.
</li>
</ul>
<p>
Schreibe einfach einen Toot oder einen Tweet, der den Bot mentioned, und
gib an
</p>
<ul>
<li>
Wo du die Kontis gesehen hast
</li>
<li>
Welche Linie sie benutzen und in welche Richtung sie fahren.
</li>
</ul>
<p>Zum Beispiel so:</p>
<img src="/guides/tooting_screenshot.png" alt="Screenshot of writing a toot" />
<img src="/guides/toot_screenshot.png" alt="A toot ready to be boosted" />
<p>
Der Bot wird die Nachricht dann weiterverbreiten, auch zu den anderen
Netzwerken. Dann können andere Leute das lesen und sicher vor Kontis sein.
</p>
<p>Danke, dass du mithilfst, öffentlichen Verkehr für alle sicherzustellen!</p>
<h2>
Kann ich darauf vertrauen, was random stranger from the Internet mir da
erzählen?
</h2>
<p>Aber natürlich! Wir haben Katzenbilder!</p>
<img src="https://lorempixel.com/550/300/cats" />
<p>
Glaubt besser nicht, wenn jemand postet, dass die Luft da und da gerade
rein ist. Das ist vielleicht sogar gut gemeint - aber klar könnte die VAG
sich hinsetzen und einfach lauter Falschmeldungen posten.
</p>
<p>
Aber Falschmeldungen darüber, dass gerade Kontis i-wo unterwegs sind?
Das macht keinen Sinn. Im schlimmsten Fall kauft jmd mal eine Fahrkarte
mehr - aber kann sonst immer schwarz fahren.</p>
<p>
Also ja - es macht Sinn, uns zu vertrauen, wenn wir sagen, wo gerade Kontis
sind.
</p>
<h2>Was ist Mastodon und warum sollte ich es benutzen?</h2>
<p>
Mastodon ist ein dezentrales soziales Netzwerk - so wie Twitter, nur ohne
Monopol und Zentralismus. Ihr könnt Kurznachrichten (Toots) über alles
mögliche schreiben, und euch mit anderen austauschen.
</p>
<p>
Mastodon ist Open Source, Privatsphäre-freundlich und relativ sicher vor
Zensur.
</p>
<p>
Um Mastodon zu benutzen, könnt ihr euch einen Account zB bei einer dieser
Websiten besorgen:
</p>
<ul>
<li>
<a href="https://queer.party/about">https://queer.party/about</a>
</li>
<li>
<a href="https://soc.ialis.me/about">https://soc.ialis.me/about</a>
</li>
<li>
<a href="https://kitty.town/about">https://kitty.town/about</a>
</li>
<li>
<a class="https" href="https://social.coop/about">https://social.coop/about</a>
</li>
<li>
<a class="https" href="https://awoo.space/about">https://awoo.space/about</a>
</li>
</ul>

165
user.py
View File

@ -28,7 +28,7 @@ class User(object):
@property
def enabled(self):
db.execute("SELECT enabled FROM user WHERE user_id=?;", (self.uid,))
db.execute("SELECT enabled FROM user WHERE id=?;", (self.uid, ))
return bool(db.cur.fetchone()[0])
@enabled.setter
@ -59,17 +59,35 @@ class User(object):
def is_appropriate(self, report):
db.execute("SELECT pattern FROM triggerpatterns WHERE user_id=?;",
(self.uid,))
for pattern, in db.cur.fetchall():
(self.uid, ))
patterns = db.cur.fetchone()
for pattern in patterns.splitlines():
if pattern.search(report.text) is not None:
break
else:
# no pattern matched
return False
default_badwords = """
bitch
whore
hitler
slut
hure
jude
schwuchtel
fag
faggot
nigger
neger
schlitz
"""
db.execute("SELECT word FROM badwords WHERE user_id=?;",
(self.uid,))
badwords = [word.lower() for word, in db.cur.fetchall()]
for word in report.text.lower().split():
(self.uid, ))
badwords = db.cur.fetchone()
for word in report.text.lower().splitlines():
if word in badwords:
return False
for word in default_badwords.splitlines():
if word in badwords:
return False
return True
@ -174,13 +192,39 @@ class User(object):
db.execute("UPDATE seen_mail SET mail_date = ? WHERE user_id = ?;",
(mail_date, self.uid))
def get_trigger_words(self, table):
db.execute("""SELECT words
FROM ? WHERE user_id = ?;""", (table, self.uid,))
def set_trigger_words(self, patterns):
db.execute("UPDATE triggerpatterns SET patterns = ? WHERE user_id = ?;",
(patterns, self.uid))
def get_trigger_words(self):
db.execute("SELECT patterns FROM triggerpatterns WHERE user_id = ?;",
(self.uid,))
return db.cur.fetchone()[0]
def set_badwords(self, words):
db.execute("UPDATE badwords SET words = ? WHERE user_id = ?;",
(words, self.uid))
def get_badwords(self):
db.execute("SELECT words FROM badwords WHERE user_id = ?;",
(self.uid,))
return db.cur.fetchone()[0]
def state(self):
return dict(foo='bar')
# necessary:
# - city
# - markdown
# - goodlist
# - blacklist
# - logged in with twitter?
# - logged in with mastodon?
# - enabled?
citydict = db.user_facing_properties(self.get_city())
return dict(city=citydict['city'],
markdown=citydict['markdown'],
triggerwords=self.get_trigger_words(),
badwords=self.get_badwords(),
enabled=self.enabled)
def save_request_token(self, token):
db.execute("""INSERT INTO
@ -244,6 +288,105 @@ class User(object):
"VALUES(?, ?, ?, ?);", (self.uid, access_token, instance_id, 1))
db.commit()
def set_markdown(self, markdown):
db.execute("UPDATE cities SET markdown = ? WHERE user_id = ?;",
(markdown, self.uid))
def get_city(self):
db.execute("SELECT city FROM user WHERE id == ?;", (self.uid,))
db.execute("SELECT city FROM cities WHERE user_id == ?;", (self.uid, ))
return db.cur.fetchone()[0]
def set_city(self, city):
masto_link = "https://example.mastodon.social/@" + city # get masto_link
twit_link = "https://example.twitter.com/" + city # get twit_link
mailinglist = city + "@" + config['web']['host']
markdown = """# Wie funktioniert Ticketfrei?
Willst du mithelfen, Ticketkontrolleur\*innen zu überwachen?
Willst du einen Fahrscheinfreien ÖPNV erkämpfen?
## Ist es gerade sicher, schwarz zu fahren?
Schau einfach auf das Profil unseres Bots: """ + twit_link + """
Hat jemand vor kurzem etwas über Kontrolleur\*innen gepostet?
* Wenn ja, dann kauf dir vllt lieber ein Ticket. In Nürnberg
haben wir die Erfahrung gemacht, dass Kontis normalerweile
ungefähr ne Woche aktiv sind, ein paar Stunden am Tag. Wenn es
also in den letzten Stunden einen Bericht gab, pass lieber
auf.
* Wenn nicht, ist es wahrscheinlich kein Problem :)
Wir können natürlich nicht garantieren, dass es sicher ist,
also pass trotzdem auf, wer auf dem Bahnsteig steht.
Aber je mehr Leute mitmachen, desto eher kannst du dir sicher
sein, dass wir sie finden, bevor sie uns finden.
Also, wenn du weniger Glück hast, und der erste bist, der einen
Kontrolleur sieht:
## Was mache ich, wenn ich Kontis sehe?
Ganz einfach, du schreibst es den anderen. Das geht entweder
* mit Mastodon [Link zu unserem Profil](""" + masto_link + """)
* über Twitter: [Link zu unserem Profil](""" + twit_link + """)
* Oder per Mail an [""" + mailinglist + "](mailto:" + mailinglist + """), wenn
ihr kein Social Media benutzen wollt.
Schreibe einfach einen Toot oder einen Tweet, der den Bot
mentioned, und gib an
* Wo du die Kontis gesehen hast
* Welche Linie sie benutzen und in welche Richtung sie fahren.
Zum Beispiel so:
![Screenshot of writing a Toot](https://github.com/b3yond/ticketfrei/raw/master/guides/tooting_screenshot.png)
![A toot ready to be shared](https://github.com/b3yond/ticketfrei/raw/master/guides/toot_screenshot.png)
Der Bot wird die Nachricht dann weiterverbreiten, auch zu den
anderen Netzwerken.
Dann können andere Leute das lesen und sicher vor Kontis sein.
Danke, dass du mithilfst, öffentlichen Verkehr für alle
sicherzustellen!
## Kann ich darauf vertrauen, was random stranger from the Internet mir da erzählen?
Aber natürlich! Wir haben Katzenbilder!
![Katzenbilder!](https://lorempixel.com/550/300/cats)
Glaubt besser nicht, wenn jemand postet, dass die Luft da und
da gerade rein ist.
Das ist vielleicht sogar gut gemeint - aber klar könnte die
VAG sich hinsetzen und einfach lauter Falschmeldungen posten.
Aber Falschmeldungen darüber, dass gerade Kontis i-wo unterwegs
sind?
Das macht keinen Sinn.
Im schlimmsten Fall kauft jmd mal eine Fahrkarte mehr - aber
kann sonst immer schwarz fahren.
Also ja - es macht Sinn, uns zu vertrauen, wenn wir sagen, wo
gerade Kontis sind.
## Was ist Mastodon und warum sollte ich es benutzen?
Mastodon ist ein dezentrales soziales Netzwerk - so wie
Twitter, nur ohne Monopol und Zentralismus.
Ihr könnt Kurznachrichten (Toots) über alles mögliche
schreiben, und euch mit anderen austauschen.
Mastodon ist Open Source, Privatsphäre-freundlich und relativ
sicher vor Zensur.
Um Mastodon zu benutzen, besucht diese Seite:
[https://joinmastodon.org/](https://joinmastodon.org/)
"""
db.execute("""INSERT INTO cities(user_id, city, markdown, masto_link,
twit_link) VALUES(?,?,?,?,?)""",
(self.uid, city, markdown, masto_link, twit_link))
db.commit()