you can now set goodlist & blacklist in settings. render city page #18. fixed #24 and #25.

master
b3yond 2018-05-25 15:57:20 +02:00
parent 6b05686379
commit d633506c83
6 changed files with 188 additions and 108 deletions

View File

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

31
db.py
View File

@ -42,13 +42,13 @@ class DB(object):
CREATE TABLE IF NOT EXISTS triggerpatterns ( CREATE TABLE IF NOT EXISTS triggerpatterns (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER, user_id INTEGER,
pattern TEXT, patterns TEXT,
FOREIGN KEY(user_id) REFERENCES user(id) FOREIGN KEY(user_id) REFERENCES user(id)
); );
CREATE TABLE IF NOT EXISTS badwords ( CREATE TABLE IF NOT EXISTS badwords (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER, user_id INTEGER,
word TEXT, words TEXT,
FOREIGN KEY(user_id) REFERENCES user(id) FOREIGN KEY(user_id) REFERENCES user(id)
); );
CREATE TABLE IF NOT EXISTS mastodon_instances ( CREATE TABLE IF NOT EXISTS mastodon_instances (
@ -146,10 +146,29 @@ class DB(object):
self.execute("INSERT INTO user (passhash) VALUES(?);", self.execute("INSERT INTO user (passhash) VALUES(?);",
(json['passhash'], )) (json['passhash'], ))
uid = self.cur.lastrowid uid = self.cur.lastrowid
self.execute(""" default_triggerpatterns = """
INSERT INTO triggerpatterns (user_id, pattern) kontroll?e
VALUES(?, ?); konti
""", (uid, '.*')) 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, ""))
else: else:
uid = json['uid'] uid = json['uid']
self.execute("INSERT INTO email (user_id, email) VALUES(?, ?);", self.execute("INSERT INTO email (user_id, email) VALUES(?, ?);",

View File

@ -38,7 +38,7 @@ def register_post():
return dict(error='Email address already in use.') return dict(error='Email address already in use.')
# send confirmation mail # send confirmation mail
try: try:
# print(url('confirm/' + city + '/%s' % db.user_token(email, password))) # only for local testing print(url('confirm/' + city + '/%s' % db.user_token(email, password))) # only for local testing
sendmail( sendmail(
email, email,
"Confirm your account", "Confirm your account",
@ -93,6 +93,20 @@ def settings(user):
return user.state() 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') @get('/api/state')
def api_enable(user): def api_enable(user):
return user.state() return user.state()

View File

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

View File

@ -1,7 +1,11 @@
% rebase('template/wrapper.tpl') % rebase('template/wrapper.tpl')
<a href="/logout/"><button>Logout</button></a> <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"> <a class='button' style="padding: 1.5em;" href="/login/twitter">
<picture> <picture>
@ -72,7 +76,7 @@
suggest: suggest:
</p> </p>
<form action="/settings/goodlist" method="post"> <form action="/settings/goodlist" method="post">
<textarea id="markdown" rows="20" cols="70" name="goodlist" wrap="physical">$markdown</textarea> <textarea id="markdown" rows="20" cols="70" name="goodlist" wrap="physical">{{markdown}}</textarea>
<input name='confirm' value='Save' type='submit'/> <input name='confirm' value='Save' type='submit'/>
</form> </form>
</div> </div>
@ -86,7 +90,7 @@
</p> </p>
<form action="/settings/goodlist" method="post"> <form action="/settings/goodlist" method="post">
<!-- find a way to display current good list. js which reads from a cookie? template? --> <!-- 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">$triggerwords</textarea> <textarea id="goodlist" rows="8" cols="70" name="goodlist" wrap="physical">{{triggerwords}}</textarea>
<input name='confirm' value='Submit' type='submit'/> <input name='confirm' value='Submit' type='submit'/>
</form> </form>
</div> </div>
@ -100,7 +104,7 @@
</p> </p>
<form action="/settings/blacklist" method="post"> <form action="/settings/blacklist" method="post">
<!-- find a way to display current blacklist. js which reads from a cookie? template? --> <!-- 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">$badwords</textarea> <textarea id="blacklist" rows="8" cols="70" name="blacklist" wrap="physical">{{badwords}}</textarea>
<input name='confirm' value='Submit' type='submit'/> <input name='confirm' value='Submit' type='submit'/>
</form> </form>
</div> </div>

228
user.py
View File

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