forked from ticketfrei/ticketfrei
remove all twitter references
This commit is contained in:
parent
4d00cc7aef
commit
d73a1e5c5f
17
README.md
17
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Ticketfrei social bot
|
||||
|
||||
Ticketfrei is a mastodon/twitter/mail bot to dodge ticket controllers in public
|
||||
Ticketfrei is a mastodon/mail bot to dodge ticket controllers in public
|
||||
transport systems.
|
||||
|
||||
## Mission
|
||||
|
|
@ -28,9 +28,9 @@ your tweet and others can read the info and think twice whether they want to
|
|||
buy a ticket or not. If enough people, a critical mass, participate for the bot
|
||||
to become reliable, you have positive self-reinforcing dynamics.
|
||||
|
||||
Today, you can use a Twitter, Mastodon, Telegram, and Mail with the account.
|
||||
Today, you can use a Mastodon, Telegram, and Mail with the account.
|
||||
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 and Mastodon users will also see the message. And vice versa.
|
||||
|
||||
In version 2, this repository contains a web application. On this website,
|
||||
people can register an own bot for their city - the website manages multiple
|
||||
|
|
@ -51,7 +51,6 @@ running.
|
|||
|
||||
* Register on the ticketfrei site
|
||||
* Optionally: register bots:
|
||||
* Register a Twitter account
|
||||
* Register a Mastodon account
|
||||
* Register a Telegram bot
|
||||
* Configure account
|
||||
|
|
@ -119,7 +118,7 @@ vim config.toml
|
|||
```
|
||||
|
||||
This configuration is only for the admin. Moderators can log into
|
||||
twitter/mastodon/mail and configure their personal bot on the settings page.
|
||||
mastodon/mail and configure their personal bot on the settings page.
|
||||
|
||||
Set up LetsEncrypt:
|
||||
|
||||
|
|
@ -221,7 +220,7 @@ less /var/log/exim4/mainlog
|
|||
|
||||
### Development Install
|
||||
|
||||
If you want to install it locally to develop on it, note that twitter and mail
|
||||
If you want to install it locally to develop on it, note that mail
|
||||
will probably not work. You should test them on a server instead.
|
||||
|
||||
```shell
|
||||
|
|
@ -252,7 +251,7 @@ vim config.toml
|
|||
```
|
||||
|
||||
This configuration is only for the admin. Users can log into
|
||||
twitter/mastodon/mail and configure their personal bot on the settings page.
|
||||
mastodon/mail and configure their personal bot on the settings page.
|
||||
|
||||
```shell
|
||||
# create folder for socket & database
|
||||
|
|
@ -272,7 +271,7 @@ sudo chown $USER:$USER -R /var/log/ticketfrei
|
|||
## Version 1
|
||||
|
||||
- more of less hacked together during a mate-fueled weekend
|
||||
- backend-only, twitter & mastodon
|
||||
- backend-only, mastodon
|
||||
- just a script, which crawled & retweeted tweets, if they match a whitelist & blocklist
|
||||
- whitelist & blocklist were just 2 files
|
||||
|
||||
|
|
@ -281,7 +280,7 @@ sudo chown $USER:$USER -R /var/log/ticketfrei
|
|||
Reasons for the rewrite:
|
||||
- user management: Users should be able to run a Ticketfrei bot in their city
|
||||
- without needing a server, without needing command line skills
|
||||
- more networks; not only Twitter & Mastodon, also Email & Telegram
|
||||
- more networks; not only Mastodon, also Email & Telegram
|
||||
|
||||
2 processes: backend & frontend.
|
||||
The two Processes talk via a database.
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import logging
|
||||
import tweepy
|
||||
import re
|
||||
import requests
|
||||
from time import time
|
||||
import report
|
||||
from bot import Bot
|
||||
|
||||
|
||||
logger = logging.getLogger("main")
|
||||
|
||||
|
||||
class TwitterBot(Bot):
|
||||
|
||||
def get_api(self, user):
|
||||
keys = user.get_twitter_credentials()
|
||||
auth = tweepy.OAuthHandler(consumer_key=keys[0],
|
||||
consumer_secret=keys[1])
|
||||
auth.set_access_token(keys[2], # access_token_key
|
||||
keys[3]) # access_token_secret
|
||||
return tweepy.API(auth, wait_on_rate_limit=True)
|
||||
|
||||
def crawl(self, user):
|
||||
"""
|
||||
crawls all Tweets which mention the bot from the twitter rest API.
|
||||
|
||||
:return: reports: (list of report.Report objects)
|
||||
"""
|
||||
reports = []
|
||||
try:
|
||||
if user.get_last_twitter_request() + 60 > time():
|
||||
return reports
|
||||
except TypeError:
|
||||
user.set_last_twitter_request(time())
|
||||
try:
|
||||
api = self.get_api(user)
|
||||
except TypeError:
|
||||
# When there is no twitter account for this bot, we want to
|
||||
# seamlessly continue.
|
||||
#logger.error("Error Authenticating Twitter", exc_info=True)
|
||||
return reports
|
||||
last_mention = user.get_seen_tweet()
|
||||
try:
|
||||
if last_mention == 0:
|
||||
mentions = api.mentions_timeline()
|
||||
else:
|
||||
mentions = api.mentions_timeline(since_id=last_mention)
|
||||
user.set_last_twitter_request(time())
|
||||
for status in mentions:
|
||||
if status._json['in_reply_to_status_id'] == None:
|
||||
text = re.sub(
|
||||
"(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)",
|
||||
"", status.text)
|
||||
reports.append(report.Report(status.author.screen_name,
|
||||
self,
|
||||
text,
|
||||
status.id,
|
||||
status.created_at))
|
||||
user.save_seen_tweet(status.id)
|
||||
return reports
|
||||
except tweepy.RateLimitError:
|
||||
logger.error("Twitter API Error: Rate Limit Exceeded",
|
||||
exc_info=True)
|
||||
# :todo implement rate limiting
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
||||
except tweepy.TweepError:
|
||||
logger.error("Twitter API Error: General Error. User: " + str(user.uid), exc_info=True)
|
||||
return []
|
||||
|
||||
def post(self, user, report):
|
||||
try:
|
||||
api = self.get_api(user)
|
||||
except TypeError:
|
||||
return # no twitter account for this user.
|
||||
try:
|
||||
if report.source == self:
|
||||
api.retweet(report.id)
|
||||
else:
|
||||
text = report.text
|
||||
if len(text) > 280:
|
||||
text = text[:280 - 4] + u' ...'
|
||||
api.update_status(status=text)
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.error("Twitter API Error: Bad Connection",
|
||||
exc_info=True)
|
||||
except tweepy.error.TweepError:
|
||||
logger.error("Twitter API Error", exc_info=True)
|
||||
12
config.py
12
config.py
|
|
@ -12,18 +12,6 @@ def load_env():
|
|||
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']
|
||||
|
|
|
|||
|
|
@ -1,9 +1,3 @@
|
|||
[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
|
||||
|
|
|
|||
38
db.py
38
db.py
|
|
@ -91,21 +91,6 @@ class DB(object):
|
|||
tg_id INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS twitter_request_tokens (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER,
|
||||
request_token TEXT,
|
||||
request_token_secret TEXT,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS twitter_accounts (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER,
|
||||
client_id TEXT,
|
||||
client_secret TEXT,
|
||||
active INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS telegram_accounts (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER,
|
||||
|
|
@ -113,21 +98,6 @@ class DB(object):
|
|||
active INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS seen_tweets (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER,
|
||||
tweet_id INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS seen_dms (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER,
|
||||
twitter_accounts_id INTEGER,
|
||||
message_id TEXT,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
||||
FOREIGN KEY(twitter_accounts_id)
|
||||
REFERENCES twitter_accounts(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS telegram_subscribers (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER,
|
||||
|
|
@ -147,12 +117,6 @@ class DB(object):
|
|||
mail_date REAL,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS twitter_last_request (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER,
|
||||
date INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS cities (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER,
|
||||
|
|
@ -269,8 +233,6 @@ u\d\d?"""
|
|||
active) VALUES(?, ?, ?);""", (uid, "", 1))
|
||||
self.execute("INSERT INTO seen_telegrams (user_id, tg_id) VALUES (?, ?);", (uid, 0))
|
||||
self.execute("INSERT INTO seen_mail (user_id, mail_date) VALUES (?, ?);", (uid, 0))
|
||||
self.execute("INSERT INTO seen_tweets (user_id, tweet_id) VALUES (?, ?)", (uid, 0))
|
||||
self.execute("INSERT INTO twitter_last_request (user_id, date) VALUES (?, ?)", (uid, 0))
|
||||
self.commit()
|
||||
user = User(uid)
|
||||
user.set_city(city)
|
||||
|
|
|
|||
40
frontend.py
40
frontend.py
|
|
@ -4,7 +4,6 @@ from bottle import get, post, redirect, request, response, view
|
|||
from config import config
|
||||
from db import db
|
||||
import logging
|
||||
import tweepy
|
||||
from sendmail import sendmail
|
||||
from session import SessionPlugin
|
||||
from mastodon import Mastodon
|
||||
|
|
@ -202,45 +201,6 @@ def logout():
|
|||
# :todo show info "Logout successful."
|
||||
redirect('/')
|
||||
|
||||
|
||||
@get('/login/twitter')
|
||||
def login_twitter(user):
|
||||
"""
|
||||
Starts the twitter OAuth authentication process.
|
||||
:return: redirect to twitter.
|
||||
"""
|
||||
consumer_key = config["twitter"]["consumer_key"]
|
||||
consumer_secret = config["twitter"]["consumer_secret"]
|
||||
callback_url = url("login/twitter/callback")
|
||||
auth = tweepy.OAuthHandler(consumer_key, consumer_secret, callback_url)
|
||||
try:
|
||||
redirect_url = auth.get_authorization_url()
|
||||
except tweepy.TweepError:
|
||||
logger.error('Twitter OAuth Error: Failed to get request token.',
|
||||
exc_info=True)
|
||||
return dict(error="Failed to get request token.")
|
||||
user.save_request_token(auth.request_token)
|
||||
redirect(redirect_url)
|
||||
|
||||
|
||||
@get('/login/twitter/callback')
|
||||
def twitter_callback(user):
|
||||
"""
|
||||
Gets the callback
|
||||
:return:
|
||||
"""
|
||||
# twitter passes the verifier/oauth token secret in a GET request.
|
||||
verifier = request.query['oauth_verifier']
|
||||
consumer_key = config["twitter"]["consumer_key"]
|
||||
consumer_secret = config["twitter"]["consumer_secret"]
|
||||
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
|
||||
request_token = user.get_request_token()
|
||||
auth.request_token = request_token
|
||||
auth.get_access_token(verifier)
|
||||
user.save_twitter_token(auth.access_token, auth.access_token_secret)
|
||||
redirect("/settings")
|
||||
|
||||
|
||||
@post('/login/mastodon')
|
||||
def login_mastodon(user):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Report(object):
|
|||
Constructor of a ticketfrei report
|
||||
|
||||
:param author: username of the author
|
||||
:param source: mastodon, twitter, or email bot object
|
||||
:param source: mastodon or email bot object
|
||||
:param text: the text of the report
|
||||
:param id: id in the network
|
||||
:param timestamp: time of the report
|
||||
|
|
|
|||
|
|
@ -13,15 +13,6 @@
|
|||
|
||||
<div id="enablebutton" style="float: right; padding: 2em;">asdf</div>
|
||||
|
||||
<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="" />
|
||||
</picture>
|
||||
Log in with Twitter
|
||||
</a>
|
||||
|
||||
<section style="padding: 1.5em;">
|
||||
<h2>Log in with Mastodon</h2>
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -7,15 +7,6 @@
|
|||
<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'>
|
||||
|
|
|
|||
76
user.py
76
user.py
|
|
@ -107,16 +107,6 @@ schlitz
|
|||
logger.info("Valid report: " + report.text + " | username: " + report.author)
|
||||
return True
|
||||
|
||||
def get_last_twitter_request(self):
|
||||
db.execute("SELECT date FROM twitter_last_request WHERE user_id = ?;",
|
||||
(self.uid,))
|
||||
return db.cur.fetchone()[0]
|
||||
|
||||
def set_last_twitter_request(self, date):
|
||||
db.execute("UPDATE twitter_last_request SET date = ? WHERE user_id = ?;",
|
||||
(date, self.uid))
|
||||
db.commit()
|
||||
|
||||
def get_telegram_credentials(self):
|
||||
db.execute("""SELECT apikey
|
||||
FROM telegram_accounts
|
||||
|
|
@ -170,27 +160,6 @@ schlitz
|
|||
(toot_uri, self.uid))
|
||||
db.commit()
|
||||
|
||||
def get_seen_tweet(self):
|
||||
db.execute("SELECT tweet_id FROM seen_tweets WHERE user_id = ?;",
|
||||
(self.uid,))
|
||||
return db.cur.fetchone()[0]
|
||||
|
||||
def save_seen_tweet(self, tweet_id):
|
||||
if tweet_id > self.get_seen_tweet():
|
||||
db.execute("UPDATE seen_tweets SET tweet_id = ? WHERE user_id = ?;",
|
||||
(tweet_id, self.uid))
|
||||
db.commit()
|
||||
|
||||
def get_seen_dm(self):
|
||||
db.execute("SELECT message_id FROM seen_dms WHERE user_id = ?;",
|
||||
(self.uid,))
|
||||
return db.cur.fetchone()
|
||||
|
||||
def save_seen_dm(self, tweet_id):
|
||||
db.execute("UPDATE seen_dms SET message_id = ? WHERE user_id = ?;",
|
||||
(tweet_id, self.uid))
|
||||
db.commit()
|
||||
|
||||
def get_seen_tg(self):
|
||||
db.execute("SELECT tg_id FROM seen_telegrams WHERE user_id = ?;",
|
||||
(self.uid,))
|
||||
|
|
@ -250,7 +219,6 @@ schlitz
|
|||
# - goodlist
|
||||
# - blocklist
|
||||
# - csrf
|
||||
# - logged in with twitter?
|
||||
# - logged in with mastodon?
|
||||
# - enabled?
|
||||
citydict = db.user_facing_properties(self.get_city())
|
||||
|
|
@ -262,48 +230,6 @@ schlitz
|
|||
enabled=self.enabled,
|
||||
csrf=self.get_csrf())
|
||||
|
||||
def save_request_token(self, token):
|
||||
db.execute("""INSERT INTO
|
||||
twitter_request_tokens(
|
||||
user_id, request_token, request_token_secret
|
||||
) VALUES(?, ?, ?);""",
|
||||
(self.uid, token["oauth_token"],
|
||||
token["oauth_token_secret"]))
|
||||
db.commit()
|
||||
|
||||
def get_request_token(self):
|
||||
db.execute("""SELECT request_token, request_token_secret
|
||||
FROM twitter_request_tokens
|
||||
WHERE user_id = ?;""", (self.uid,))
|
||||
request_token = db.cur.fetchone()
|
||||
db.execute("""DELETE FROM twitter_request_tokens
|
||||
WHERE user_id = ?;""", (self.uid,))
|
||||
db.commit()
|
||||
return {"oauth_token": request_token[0],
|
||||
"oauth_token_secret": request_token[1]}
|
||||
|
||||
def save_twitter_token(self, access_token, access_token_secret):
|
||||
db.execute("""INSERT INTO twitter_accounts(
|
||||
user_id, client_id, client_secret
|
||||
) VALUES(?, ?, ?);""",
|
||||
(self.uid, access_token, access_token_secret))
|
||||
db.execute("""INSERT INTO seen_tweets(user_id, tweet_id) VALUES (?, ?);""",
|
||||
(self.uid, 0))
|
||||
db.commit()
|
||||
|
||||
def get_twitter_token(self):
|
||||
db.execute("SELECT client_id, client_secret FROM twitter_accounts WHERE user_id = ?;",
|
||||
(self.uid, ))
|
||||
return db.cur.fetchone()
|
||||
|
||||
def get_twitter_credentials(self):
|
||||
keys = [config['twitter']['consumer_key'],
|
||||
config['twitter']['consumer_secret']]
|
||||
row = self.get_twitter_token()
|
||||
keys.append(row[0])
|
||||
keys.append(row[1])
|
||||
return keys
|
||||
|
||||
def update_telegram_key(self, apikey):
|
||||
db.execute("UPDATE telegram_accounts SET apikey = ? WHERE user_id = ?;", (apikey, self.uid))
|
||||
db.commit()
|
||||
|
|
@ -353,7 +279,6 @@ schlitz
|
|||
|
||||
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?
|
||||
|
||||
|
|
@ -395,7 +320,6 @@ Kontrolleur sieht:
|
|||
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 + """)
|
||||
* über Telegram an [@ticketfrei_""" + city + "_bot](https://t.me/ticketfrei_" \
|
||||
+ city + """_bot)
|
||||
* Oder per Mail an [""" + mailinglist + "](mailto:" + mailinglist + """), wenn
|
||||
|
|
|
|||
Loading…
Reference in a new issue