Merge branch 'multi-deployment' of https://github.com/b3yond/ticketfrei into multi-deployment
This commit is contained in:
commit
dde4e6af7b
157
README.md
157
README.md
|
@ -1,114 +1,107 @@
|
||||||
# Ticketfrei social bot
|
# Ticketfrei social bot
|
||||||
|
|
||||||
Version: 1.0
|
Version: 2.0beta
|
||||||
|
|
||||||
Ticketfrei is a mastodon/twitter/mail bot to dodge ticket controllers
|
Ticketfrei is a mastodon/twitter/mail bot to dodge ticket controllers in public
|
||||||
in public transport systems.
|
transport systems.
|
||||||
|
|
||||||
The functionality is simple: it retweets every tweet where it is
|
The functionality is simple: it retweets every tweet where it is mentioned.
|
||||||
mentioned.
|
|
||||||
|
|
||||||
This leads to a community which evolves around it; if you see ticket
|
This leads to a community which evolves around it; if you see ticket
|
||||||
controllers, you tweet their location and mention the bot. The bot
|
controllers, you tweet their location and mention the bot. The bot then
|
||||||
then retweets your tweet and others can read the info and think twice
|
retweets your tweet and others can read the info and think twice if they want
|
||||||
if they want to buy a ticket. If enough people, a critical mass,
|
to buy a ticket. If enough people, a critical mass, participate for the bot to
|
||||||
participate for the bot to become reliable, you have positive
|
become reliable, you have positive self-reinforcing dynamics.
|
||||||
self-reinforcing dynamics.
|
|
||||||
|
|
||||||
In the promotion folder, you will find some promotion material you
|
Today, you can use a Twitter, a Mastodon, and Mail with the account. They will
|
||||||
can use to build up such a community in your city. It is in german
|
communicate with each other; if someone warns others via Mail, Twitter and
|
||||||
though =/
|
Mastodon users will also see the message. And vice versa.
|
||||||
|
|
||||||
Website: https://wiki.links-tech.org/IT/Ticketfrei
|
In version 2, this bot has received a frontend website. On this website, 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.
|
||||||
|
|
||||||
## Install
|
In the promotion folder, you will find some (german) promotion material you can
|
||||||
|
use to build up such a community in your city.
|
||||||
|
|
||||||
Setting up a ticketfrei bot for your city is quite easy. Here are the
|
Website: ticketfrei.links-tech.org
|
||||||
few steps:
|
|
||||||
|
|
||||||
First you need to install python3 and virtualenv with your favourite
|
More information: https://wiki.links-tech.org/IT/Ticketfrei
|
||||||
package manager.
|
|
||||||
|
|
||||||
Create and activate virtualenv:
|
## Do you want Ticketfrei in your city?
|
||||||
|
|
||||||
```shell
|
Just got to ticketfrei.links-tech.org or another website where this software is
|
||||||
sudo apt install python3 virtualenv uwsgi uwsgi-plugin-python nginx
|
running.
|
||||||
virtualenv -p python3 .
|
|
||||||
. bin/activate
|
|
||||||
```
|
|
||||||
|
|
||||||
Install the dependencies:
|
* Register a twitter account
|
||||||
```shell
|
* Register a Mastodon account
|
||||||
pip install tweepy pytoml requests Mastodon.py bottle pyjwt
|
* Register on the ticketfrei site
|
||||||
```
|
* Configure account
|
||||||
|
* The hard part: do the promotion! You need a community.
|
||||||
|
|
||||||
Configure the bot:
|
### Maintaining
|
||||||
```shell
|
|
||||||
cp config.toml.example config.toml
|
|
||||||
vim config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use a Twitter, a Mastodon, and Mail with the account. They
|
There is one security hole: people could start mentioning the bot with useless
|
||||||
will communicate with each other; if someone warns others via Mail,
|
information, turning it into a spammer. That's why it has to be maintained; if
|
||||||
Twitter and Mastodon users will also see the message. And vice versa.
|
someone spams the bot, mute them and undo the retweet. So it won't retweet
|
||||||
|
their future tweets and the useless retweet is deleted if someone tries to
|
||||||
You have to configure all of the accounts via config.toml; it should
|
check if something was retweeted in the last hour or something.
|
||||||
be fairly intuitive to enter the right values.
|
|
||||||
|
|
||||||
## Maintaining
|
|
||||||
|
|
||||||
There is one security hole: people could start mentioning the bot
|
|
||||||
with useless information, turning it into a spammer. That's why it
|
|
||||||
has to be maintained; if someone spams the bot, mute them and undo
|
|
||||||
the retweet. So it won't retweet their future tweets and the useless
|
|
||||||
retweet is deleted if someone tries to check if something was
|
|
||||||
retweeted in the last hour or something.
|
|
||||||
|
|
||||||
To this date, we have never heard of this happening though.
|
To this date, we have never heard of this happening though.
|
||||||
|
|
||||||
### blacklisting
|
### blacklisting
|
||||||
|
|
||||||
You also need to edit the goodlist and the blacklist. They are in the
|
You also need to edit the goodlist and the blacklist. You can do this on the
|
||||||
"goodlists" and "blacklists" folders. All text files in those
|
website, in the settings of your bot.
|
||||||
directories will be used, so you should delete our templates; but
|
|
||||||
feel free to use them as an orientation.
|
|
||||||
|
|
||||||
Just add the words to the goodlist, which you want to require. A
|
Just add the words to the goodlist, which you want to require. A report is only
|
||||||
report is only spread, if it contains at least one of them. If you
|
spread, if it contains at least one of them. If you want to RT everything, just
|
||||||
want to RT everything, just add a ```*```.
|
add a ```*```.
|
||||||
|
|
||||||
There is also a blacklist, which you can use to automatically sort
|
There is also a blacklist, which you can use to automatically sort out
|
||||||
out malicious tweets. Be careful though, our filter can't read the
|
malicious tweets. Be careful though, our filter can't read the intention with
|
||||||
intention with which a word was used. Maybe you wanted it there.
|
which a word was used. Maybe you wanted it there.
|
||||||
|
|
||||||
### screen
|
## Do you want to offer a Ticketfrei website to others?
|
||||||
|
|
||||||
To keep the bots running when you are logged out of the shell, you
|
If you want to offer this website to others, feel free to do so. If you have questions, just open
|
||||||
can use screen:
|
a GitHub issue or write to tech@lists.links-tech.org, we are happy to help and share best practices.
|
||||||
|
|
||||||
|
We wrote these installation notes, so you can set up the website easily:
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
To Do:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo apt-get install screen
|
sudo apt install python3 virtualenv uwsgi uwsgi-plugin-python nginx
|
||||||
echo "if [ -z "$STY" ]; then screen -RR; fi" >> ~/.bash_login
|
|
||||||
screen
|
|
||||||
python3 ticketfrei.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To log out of the screen session, press "ctrl+a", and then "d".
|
* set up nginx
|
||||||
|
* set up LetsEncrypt https://certbot.eff.org/
|
||||||
|
* set up mariadb
|
||||||
|
* set up uwsgi
|
||||||
|
|
||||||
### Manually creating the database
|
Install the necessary packages, create and activate virtualenv:
|
||||||
|
|
||||||
Unfortunately, if you want to help developing, you have to create the
|
```shell
|
||||||
database manually for now.
|
virtualenv -p python3 .
|
||||||
|
. bin/activate
|
||||||
At the moment, we use a SQLITE3 database. If you are in the repo
|
|
||||||
directory, just open it with ```sqlitebrowser ticketfrei.sqlite```.
|
|
||||||
Then execute following SQL to create the tables:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE `user` (
|
|
||||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
`email` TEXT,
|
|
||||||
`pass_hashed` TEXT,
|
|
||||||
`enabled` INTEGER
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Install the dependencies:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install tweepy pytoml requests Mastodon.py bottle pyjwt
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure the bot:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cp config.toml.example config.toml
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
25
backend.py
25
backend.py
|
@ -1,10 +1,11 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import prepare
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import sendmail
|
import sendmail
|
||||||
from db import DB
|
from db import DB
|
||||||
|
from config import config
|
||||||
|
|
||||||
from mastodonbot import MastodonBot
|
from mastodonbot import MastodonBot
|
||||||
from twitterbot import TwitterBot
|
from twitterbot import TwitterBot
|
||||||
|
@ -20,18 +21,22 @@ def get_users(db):
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
|
||||||
def init_bots(config, logger, db, users):
|
def init_bots(config, db, users):
|
||||||
for uid in users:
|
for uid in users:
|
||||||
users[uid].append(Trigger(config, uid, db))
|
users[uid].append(Trigger(config, uid, db))
|
||||||
users[uid].append(MastodonBot(config, logger, uid, db))
|
users[uid].append(MastodonBot(config, uid, db))
|
||||||
users[uid].append(TwitterBot(config, logger, uid, db))
|
users[uid].append(TwitterBot(config, uid, db))
|
||||||
users[uid].append(Mailbot(config, logger, uid, db))
|
users[uid].append(Mailbot(config, uid, db))
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
|
||||||
def run():
|
if __name__ == '__main__':
|
||||||
config = prepare.get_config()
|
logpath = config['logging']['logpath']
|
||||||
logger = prepare.get_logger(config)
|
logger = logging.getLogger()
|
||||||
|
fh = logging.FileHandler(logpath)
|
||||||
|
fh.setLevel(logging.DEBUG)
|
||||||
|
logger.addHandler(fh)
|
||||||
|
|
||||||
db = DB()
|
db = DB()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -68,7 +73,3 @@ def run():
|
||||||
attachment=config['logging']['logpath'])
|
attachment=config['logging']['logpath'])
|
||||||
except:
|
except:
|
||||||
logger.error('Mail sending failed', exc_info=True)
|
logger.error('Mail sending failed', exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run()
|
|
||||||
|
|
5
config.py
Normal file
5
config.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import pytoml as toml
|
||||||
|
|
||||||
|
# read config in TOML format (https://github.com/toml-lang/toml#toml)
|
||||||
|
with open('config.toml') as configfile:
|
||||||
|
config = toml.load(configfile)
|
|
@ -10,8 +10,8 @@ contact = "b3yond@riseup.net"
|
||||||
|
|
||||||
[mail]
|
[mail]
|
||||||
mailserver = "smtp.riseup.net"
|
mailserver = "smtp.riseup.net"
|
||||||
user = "nbgticketfrei"
|
user = "user"
|
||||||
passphrase = "5MUIGw,GmSj)t@xW!jixq=b0U+@SJ{K{"
|
passphrase = "sup3rs3cur3"
|
||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
# The directory where logs should be stored.
|
# The directory where logs should be stored.
|
||||||
|
|
17
db.py
17
db.py
|
@ -2,19 +2,18 @@ from bottle import redirect, request
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from inspect import Signature
|
from inspect import Signature
|
||||||
import jwt
|
import jwt
|
||||||
from os import path, urandom
|
import logging
|
||||||
|
from os import urandom
|
||||||
from pylibscrypt import scrypt_mcf, scrypt_mcf_check
|
from pylibscrypt import scrypt_mcf, scrypt_mcf_check
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import prepare
|
|
||||||
from user import User
|
from user import User
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DB(object):
|
class DB(object):
|
||||||
def __init__(self):
|
def __init__(self, dbfile):
|
||||||
self.config = prepare.get_config()
|
|
||||||
self.logger = prepare.get_logger(self.config)
|
|
||||||
dbfile = path.join(path.dirname(path.abspath(__file__)),
|
|
||||||
self.config['database']['db_path'])
|
|
||||||
self.conn = sqlite3.connect(dbfile)
|
self.conn = sqlite3.connect(dbfile)
|
||||||
self.cur = self.conn.cursor()
|
self.cur = self.conn.cursor()
|
||||||
self.create()
|
self.create()
|
||||||
|
@ -149,8 +148,8 @@ class DBPlugin(object):
|
||||||
name = 'DBPlugin'
|
name = 'DBPlugin'
|
||||||
api = 2
|
api = 2
|
||||||
|
|
||||||
def __init__(self, loginpage):
|
def __init__(self, dbfile, loginpage):
|
||||||
self.db = DB()
|
self.db = DB(dbfile)
|
||||||
self.loginpage = loginpage
|
self.loginpage = loginpage
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
|
16
deployment/ticketfrei-web.service
Normal file
16
deployment/ticketfrei-web.service
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Ticketfrei Web Application
|
||||||
|
After=syslog.target network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/uwsgi --ini /srv/ticketfrei/deployment/uwsgi.ini
|
||||||
|
# Requires systemd version 211 or newer
|
||||||
|
RuntimeDirectory=uwsgi
|
||||||
|
Restart=always
|
||||||
|
KillSignal=SIGQUIT
|
||||||
|
Type=notify
|
||||||
|
StandardError=syslog
|
||||||
|
NotifyAccess=all
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
11
deployment/uwsgi.ini
Normal file
11
deployment/uwsgi.ini
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[uwsgi]
|
||||||
|
plugins = python3
|
||||||
|
master = true
|
||||||
|
uid = www-data
|
||||||
|
gid = www-data
|
||||||
|
processes = 1
|
||||||
|
# logto = /var/log/ticketfrei.log
|
||||||
|
socket = /var/run/ticketfrei/ticketfrei.sock
|
||||||
|
chmod-socket = 660
|
||||||
|
wsgi-file = /srv/ticketfrei/frontend.py
|
||||||
|
virtualenv = /srv/ticketfrei/venv
|
55
frontend.py
55
frontend.py
|
@ -1,11 +1,15 @@
|
||||||
import bottle
|
import bottle
|
||||||
from bottle import get, post, redirect, request, response, view
|
from bottle import get, post, redirect, request, response, view
|
||||||
|
from config import config
|
||||||
from db import DBPlugin
|
from db import DBPlugin
|
||||||
|
import logging
|
||||||
import tweepy
|
import tweepy
|
||||||
import sendmail
|
import sendmail
|
||||||
import smtplib
|
import smtplib
|
||||||
from mastodon import Mastodon
|
from mastodon import Mastodon
|
||||||
import prepare
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@get('/')
|
@get('/')
|
||||||
|
@ -26,14 +30,15 @@ def register_post(db):
|
||||||
return dict(error='Email address already in use.')
|
return dict(error='Email address already in use.')
|
||||||
# send confirmation mail
|
# send confirmation mail
|
||||||
confirm_link = request.url + "/../confirm/" + db.token(email, password)
|
confirm_link = request.url + "/../confirm/" + db.token(email, password)
|
||||||
send_confirmation_mail(db.config, confirm_link, email)
|
send_confirmation_mail(confirm_link, email)
|
||||||
return dict(info='Confirmation mail sent.')
|
return dict(info='Confirmation mail sent.')
|
||||||
|
|
||||||
|
|
||||||
def send_confirmation_mail(config, confirm_link, email):
|
def send_confirmation_mail(confirm_link, email):
|
||||||
m = sendmail.Mailer(config)
|
m = sendmail.Mailer()
|
||||||
try:
|
try:
|
||||||
m.send("Complete your registration here: " + confirm_link, email, "[Ticketfrei] Confirm your account")
|
m.send("Complete your registration here: " + confirm_link, email,
|
||||||
|
"[Ticketfrei] Confirm your account")
|
||||||
except smtplib.SMTPRecipientsRefused:
|
except smtplib.SMTPRecipientsRefused:
|
||||||
return "Please enter a valid E-Mail address."
|
return "Please enter a valid E-Mail address."
|
||||||
|
|
||||||
|
@ -88,14 +93,15 @@ def login_twitter(user):
|
||||||
Starts the twitter OAuth authentication process.
|
Starts the twitter OAuth authentication process.
|
||||||
:return: redirect to twitter.
|
:return: redirect to twitter.
|
||||||
"""
|
"""
|
||||||
consumer_key = user.db.config["tapp"]["consumer_key"]
|
consumer_key = config["tapp"]["consumer_key"]
|
||||||
consumer_secret = user.db.config["tapp"]["consumer_secret"]
|
consumer_secret = config["tapp"]["consumer_secret"]
|
||||||
callback_url = bottle.request.get_header('host') + "/login/twitter/callback"
|
callback_url = request.get_header('host') + "/login/twitter/callback"
|
||||||
auth = tweepy.OAuthHandler(consumer_key, consumer_secret, callback_url)
|
auth = tweepy.OAuthHandler(consumer_key, consumer_secret, callback_url)
|
||||||
try:
|
try:
|
||||||
redirect_url = auth.get_authorization_url()
|
redirect_url = auth.get_authorization_url()
|
||||||
except tweepy.TweepError:
|
except tweepy.TweepError:
|
||||||
user.db.logger.error('Twitter OAuth Error: Failed to get request token.', exc_info=True)
|
logger.error('Twitter OAuth Error: Failed to get request token.',
|
||||||
|
exc_info=True)
|
||||||
return dict(error="Failed to get request token.")
|
return dict(error="Failed to get request token.")
|
||||||
user.save_request_token(auth.request_token)
|
user.save_request_token(auth.request_token)
|
||||||
return bottle.redirect(redirect_url)
|
return bottle.redirect(redirect_url)
|
||||||
|
@ -108,9 +114,9 @@ def twitter_callback(user):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# twitter passes the verifier/oauth token secret in a GET request.
|
# twitter passes the verifier/oauth token secret in a GET request.
|
||||||
verifier = bottle.request.query('oauth_verifier')
|
verifier = request.query('oauth_verifier')
|
||||||
consumer_key = user.db.config["tapp"]["consumer_key"]
|
consumer_key = config["twitter"]["consumer_key"]
|
||||||
consumer_secret = user.db.config["tapp"]["consumer_secret"]
|
consumer_secret = config["twitter"]["consumer_secret"]
|
||||||
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
|
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
|
||||||
request_token = user.get_request_token
|
request_token = user.get_request_token
|
||||||
auth.request_token = {"oauth_token": request_token,
|
auth.request_token = {"oauth_token": request_token,
|
||||||
|
@ -127,22 +133,29 @@ def login_mastodon(user):
|
||||||
:return: redirect to twitter.
|
:return: redirect to twitter.
|
||||||
"""
|
"""
|
||||||
# get app tokens
|
# get app tokens
|
||||||
instance_url = bottle.request.forms.get('instance_url')
|
instance_url = request.forms.get('instance_url')
|
||||||
masto_email = bottle.request.forms.get('email')
|
masto_email = request.forms.get('email')
|
||||||
print(masto_email)
|
print(masto_email)
|
||||||
masto_pass = bottle.request.forms.get('pass')
|
masto_pass = request.forms.get('pass')
|
||||||
print(masto_pass)
|
print(masto_pass)
|
||||||
client_id, client_secret = user.get_mastodon_app_keys(instance_url)
|
client_id, client_secret = user.get_mastodon_app_keys(instance_url)
|
||||||
m = Mastodon(client_id=client_id, client_secret=client_secret, api_base_url=instance_url)
|
m = Mastodon(client_id=client_id, client_secret=client_secret,
|
||||||
|
api_base_url=instance_url)
|
||||||
try:
|
try:
|
||||||
access_token = m.log_in(masto_email, masto_pass)
|
access_token = m.log_in(masto_email, masto_pass)
|
||||||
user.save_masto_token(access_token, instance_url)
|
user.save_masto_token(access_token, instance_url)
|
||||||
return dict(info='Thanks for supporting decentralized social networks!')
|
return dict(
|
||||||
|
info='Thanks for supporting decentralized social networks!'
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
user.db.logger.error('Login to Mastodon failed.', exc_info=True)
|
logger.error('Login to Mastodon failed.', exc_info=True)
|
||||||
return dict(error='Login to Mastodon failed.')
|
return dict(error='Login to Mastodon failed.')
|
||||||
|
|
||||||
|
|
||||||
config = prepare.get_config()
|
if __name__ == '__main__':
|
||||||
bottle.install(DBPlugin('/'))
|
# testing only
|
||||||
bottle.run(host=config['web']['host'], port=8080)
|
bottle.install(DBPlugin(':memory:', '/'))
|
||||||
|
bottle.run(host='localhost', port=8080)
|
||||||
|
else:
|
||||||
|
bottle.install(DBPlugin(config['database']['db_path'], '/'))
|
||||||
|
application = bottle.default_app()
|
||||||
|
|
71
mailbot.py
71
mailbot.py
|
@ -1,5 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from config import config
|
||||||
|
import logging
|
||||||
import sendmail
|
import sendmail
|
||||||
import ssl
|
import ssl
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -9,21 +11,22 @@ import report
|
||||||
from user import User
|
from user import User
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Mailbot(object):
|
class Mailbot(object):
|
||||||
"""
|
"""
|
||||||
Bot which sends Mails if mentioned via twitter/mastodon, and tells
|
Bot which sends Mails if mentioned via twitter/mastodon, and tells
|
||||||
other bots that it received mails.
|
other bots that it received mails.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config, logger, uid, db):
|
def __init__(self, uid, db):
|
||||||
"""
|
"""
|
||||||
Creates a Bot who listens to mails and forwards them to other
|
Creates a Bot who listens to mails and forwards them to other
|
||||||
bots.
|
bots.
|
||||||
|
|
||||||
:param config: (dictionary) config.toml as a dictionary of dictionaries
|
:param config: (dictionary) config.toml as a dictionary of dictionaries
|
||||||
"""
|
"""
|
||||||
self.config = config
|
|
||||||
self.logger = logger
|
|
||||||
self.user = User(db, uid)
|
self.user = User(db, uid)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -36,23 +39,24 @@ class Mailbot(object):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.mailinglist = None
|
self.mailinglist = None
|
||||||
|
|
||||||
self.mailbox = imaplib.IMAP4_SSL(self.config["mail"]["imapserver"])
|
self.mailbox = imaplib.IMAP4_SSL(config["mail"]["imapserver"])
|
||||||
context = ssl.create_default_context()
|
context = ssl.create_default_context()
|
||||||
try:
|
try:
|
||||||
self.mailbox.starttls(ssl_context=context)
|
self.mailbox.starttls(ssl_context=context)
|
||||||
except:
|
except:
|
||||||
self.logger.error('StartTLS failed', exc_info=True)
|
logger.error('StartTLS failed', exc_info=True)
|
||||||
try:
|
try:
|
||||||
self.mailbox.login(self.config["mail"]["user"], self.config["mail"]["passphrase"])
|
self.mailbox.login(config["mail"]["user"],
|
||||||
|
config["mail"]["passphrase"])
|
||||||
except imaplib.IMAP4.error:
|
except imaplib.IMAP4.error:
|
||||||
self.logger.error("Login to mail server failed", exc_info=True)
|
logger.error("Login to mail server failed", exc_info=True)
|
||||||
try:
|
try:
|
||||||
mailer = sendmail.Mailer(config)
|
mailer = sendmail.Mailer()
|
||||||
mailer.send('', config['web']['contact'],
|
mailer.send('', config['web']['contact'],
|
||||||
'Ticketfrei Crash Report',
|
'Ticketfrei Crash Report',
|
||||||
attachment=config['logging']['logpath'])
|
attachment=config['logging']['logpath'])
|
||||||
except:
|
except:
|
||||||
self.logger.error('Mail sending failed', exc_info=True)
|
logger.error('Mail sending failed', exc_info=True)
|
||||||
|
|
||||||
def repost(self, status):
|
def repost(self, status):
|
||||||
"""
|
"""
|
||||||
|
@ -72,7 +76,7 @@ class Mailbot(object):
|
||||||
try:
|
try:
|
||||||
rv, data = self.mailbox.select("Inbox")
|
rv, data = self.mailbox.select("Inbox")
|
||||||
except imaplib.IMAP4.abort:
|
except imaplib.IMAP4.abort:
|
||||||
self.logger.error("Crawling Mail failed", exc_info=True)
|
logger.error("Crawling Mail failed", exc_info=True)
|
||||||
rv = False
|
rv = False
|
||||||
msgs = []
|
msgs = []
|
||||||
if rv == 'OK':
|
if rv == 'OK':
|
||||||
|
@ -83,15 +87,18 @@ class Mailbot(object):
|
||||||
for num in data[0].split():
|
for num in data[0].split():
|
||||||
rv, data = self.mailbox.fetch(num, '(RFC822)')
|
rv, data = self.mailbox.fetch(num, '(RFC822)')
|
||||||
if rv != 'OK':
|
if rv != 'OK':
|
||||||
self.logger.error("Couldn't fetch mail %s %s" % (rv, str(data)))
|
logger.error("Couldn't fetch mail %s %s" % (rv, str(data)))
|
||||||
return msgs
|
return msgs
|
||||||
msg = email.message_from_bytes(data[0][1])
|
msg = email.message_from_bytes(data[0][1])
|
||||||
|
|
||||||
if not self.user.get_mail() in msg['From']:
|
if not self.user.get_mail() in msg['From']:
|
||||||
# get a comparable date out of the email
|
# get a comparable date out of the email
|
||||||
date_tuple = email.utils.parsedate_tz(msg['Date'])
|
date_tuple = email.utils.parsedate_tz(msg['Date'])
|
||||||
date_tuple = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
|
date_tuple = datetime.datetime.fromtimestamp(
|
||||||
date = int((date_tuple - datetime.datetime(1970, 1, 1)).total_seconds())
|
email.utils.mktime_tz(date_tuple)
|
||||||
|
)
|
||||||
|
date = int((date_tuple -
|
||||||
|
datetime.datetime(1970, 1, 1)).total_seconds())
|
||||||
if date > self.user.get_seen_mail():
|
if date > self.user.get_seen_mail():
|
||||||
self.last_mail = date
|
self.last_mail = date
|
||||||
self.save_last()
|
self.save_last()
|
||||||
|
@ -108,8 +115,9 @@ class Mailbot(object):
|
||||||
|
|
||||||
:param status: (report.Report object)
|
:param status: (report.Report object)
|
||||||
"""
|
"""
|
||||||
mailer = sendmail.Mailer(self.config)
|
mailer = sendmail.Mailer(config)
|
||||||
mailer.send(status.format(), self.mailinglist, "Warnung: Kontrolleure gesehen")
|
mailer.send(status.format(), self.mailinglist,
|
||||||
|
"Warnung: Kontrolleure gesehen")
|
||||||
|
|
||||||
def make_report(self, msg):
|
def make_report(self, msg):
|
||||||
"""
|
"""
|
||||||
|
@ -120,7 +128,9 @@ class Mailbot(object):
|
||||||
"""
|
"""
|
||||||
# get a comparable date out of the email
|
# get a comparable date out of the email
|
||||||
date_tuple = email.utils.parsedate_tz(msg['Date'])
|
date_tuple = email.utils.parsedate_tz(msg['Date'])
|
||||||
date_tuple = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
|
date_tuple = datetime.datetime.fromtimestamp(
|
||||||
|
email.utils.mktime_tz(date_tuple)
|
||||||
|
)
|
||||||
date = (date_tuple - datetime.datetime(1970, 1, 1)).total_seconds()
|
date = (date_tuple - datetime.datetime(1970, 1, 1)).total_seconds()
|
||||||
|
|
||||||
author = msg.get("From") # get mail author from email header
|
author = msg.get("From") # get mail author from email header
|
||||||
|
@ -149,32 +159,3 @@ class Mailbot(object):
|
||||||
if trigger.is_ok(msg.get_payload()):
|
if trigger.is_ok(msg.get_payload()):
|
||||||
statuses.append(msg)
|
statuses.append(msg)
|
||||||
return statuses
|
return statuses
|
||||||
|
|
||||||
"""
|
|
||||||
if __name__ == "__main__":
|
|
||||||
config = prepare.get_config()
|
|
||||||
|
|
||||||
# initialise trigger
|
|
||||||
trigger = trigger.Trigger(config)
|
|
||||||
|
|
||||||
# initialise mail bot
|
|
||||||
m = Mailbot(config)
|
|
||||||
|
|
||||||
statuses = []
|
|
||||||
try:
|
|
||||||
while 1:
|
|
||||||
print("Received Reports: " + str(m.flow(trigger, statuses)))
|
|
||||||
time.sleep(1)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Good bye. Remember to restart the bot!")
|
|
||||||
except:
|
|
||||||
m.logger.error('Shutdown', exc_info=True)
|
|
||||||
m.save_last()
|
|
||||||
try:
|
|
||||||
mailer = sendmail.Mailer(config)
|
|
||||||
mailer.send('', config['mail']['contact'],
|
|
||||||
'Ticketfrei Crash Report',
|
|
||||||
attachment=config['logging']['logpath'])
|
|
||||||
except:
|
|
||||||
m.logger.error('Mail sending failed', exc_info=True)
|
|
||||||
"""
|
|
|
@ -1,22 +1,26 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import logging
|
||||||
import mastodon
|
import mastodon
|
||||||
import re
|
import re
|
||||||
# import time
|
|
||||||
# import trigger
|
|
||||||
# import sendmail
|
|
||||||
import report
|
import report
|
||||||
from user import User
|
from user import User
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MastodonBot(object):
|
class MastodonBot(object):
|
||||||
def __init__(self, config, logger, uid, db):
|
def __init__(self, uid, db):
|
||||||
self.config = config
|
|
||||||
self.logger = logger
|
|
||||||
self.user = User(db, uid)
|
self.user = User(db, uid)
|
||||||
client_id, client_secret, access_token, instance_url = self.user.get_masto_credentials()
|
client_id, client_secret, access_token, instance_url = \
|
||||||
self.m = mastodon.Mastodon(client_id=client_id, client_secret=client_secret,
|
self.user.get_masto_credentials()
|
||||||
access_token=access_token, api_base_url=instance_url)
|
self.m = mastodon.Mastodon(
|
||||||
|
client_id=client_id,
|
||||||
|
client_secret=client_secret,
|
||||||
|
access_token=access_token,
|
||||||
|
api_base_url=instance_url
|
||||||
|
)
|
||||||
|
|
||||||
# load state
|
# load state
|
||||||
try:
|
try:
|
||||||
|
@ -37,16 +41,19 @@ class MastodonBot(object):
|
||||||
try:
|
try:
|
||||||
notifications = self.m.notifications()
|
notifications = self.m.notifications()
|
||||||
except: # mastodon.Mastodon.MastodonAPIError is unfortunately not in __init__.py
|
except: # mastodon.Mastodon.MastodonAPIError is unfortunately not in __init__.py
|
||||||
self.logger.error("Unknown Mastodon API Error.", exc_info=True)
|
logger.error("Unknown Mastodon API Error.", exc_info=True)
|
||||||
return mentions
|
return mentions
|
||||||
for status in notifications:
|
for status in notifications:
|
||||||
if status['type'] == 'mention' and status['status']['id'] > self.seen_toots:
|
if (status['type'] == 'mention' and
|
||||||
|
status['status']['id'] > self.seen_toots):
|
||||||
# save state
|
# save state
|
||||||
self.seen_toots = status['status']['id']
|
self.seen_toots = status['status']['id']
|
||||||
self.save_last()
|
self.save_last()
|
||||||
# add mention to mentions
|
# add mention to mentions
|
||||||
text = re.sub(r'<[^>]*>', '', status['status']['content'])
|
text = re.sub(r'<[^>]*>', '', status['status']['content'])
|
||||||
text = re.sub("(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)", "", text)
|
text = re.sub(
|
||||||
|
"(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)",
|
||||||
|
"", text)
|
||||||
mentions.append(report.Report(status['account']['acct'],
|
mentions.append(report.Report(status['account']['acct'],
|
||||||
"mastodon",
|
"mastodon",
|
||||||
text,
|
text,
|
||||||
|
@ -60,7 +67,7 @@ class MastodonBot(object):
|
||||||
|
|
||||||
:param mention: (report.Report object)
|
:param mention: (report.Report object)
|
||||||
"""
|
"""
|
||||||
self.logger.info('Boosting toot from %s' % (
|
logger.info('Boosting toot from %s' % (
|
||||||
mention.format()))
|
mention.format()))
|
||||||
self.m.status_reblog(mention.id)
|
self.m.status_reblog(mention.id)
|
||||||
|
|
||||||
|
@ -88,27 +95,3 @@ class MastodonBot(object):
|
||||||
|
|
||||||
# return mentions for mirroring
|
# return mentions for mirroring
|
||||||
return retoots
|
return retoots
|
||||||
|
|
||||||
"""
|
|
||||||
if __name__ == '__main__':
|
|
||||||
config = backend.get_config()
|
|
||||||
|
|
||||||
trigger = trigger.Trigger(config)
|
|
||||||
bot = MastodonBot(config)
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
bot.flow(trigger)
|
|
||||||
time.sleep(1)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Good bye. Remember to restart the bot!")
|
|
||||||
except:
|
|
||||||
bot.logger.error('Shutdown', exc_info=True)
|
|
||||||
try:
|
|
||||||
mailer = sendmail.Mailer(config)
|
|
||||||
mailer.send('', config['mail']['contact'],
|
|
||||||
'Ticketfrei Crash Report',
|
|
||||||
attachment=config['logging']['logpath'])
|
|
||||||
except:
|
|
||||||
bot.logger.error('Mail sending failed', exc_info=True)
|
|
||||||
"""
|
|
19
prepare.py
19
prepare.py
|
@ -1,19 +0,0 @@
|
||||||
import logging
|
|
||||||
import pytoml as toml
|
|
||||||
|
|
||||||
def get_logger(config):
|
|
||||||
logpath = config['logging']['logpath']
|
|
||||||
logger = logging.getLogger()
|
|
||||||
fh = logging.FileHandler(logpath)
|
|
||||||
fh.setLevel(logging.DEBUG)
|
|
||||||
logger.addHandler(fh)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
|
||||||
# read config in TOML format (https://github.com/toml-lang/toml#toml)
|
|
||||||
with open('config.toml') as configfile:
|
|
||||||
config = toml.load(configfile)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
|
@ -1,42 +1,58 @@
|
||||||
% rebase('template/wrapper.tpl')
|
% rebase('template/wrapper.tpl')
|
||||||
% include('template/login-plain.tpl')
|
% include('template/login-plain.tpl')
|
||||||
<h1>Features</h1>
|
<h1>Features</h1>
|
||||||
<p>sum is simply dummy text of the printing and typesetting
|
<p>
|
||||||
industry. Lorem Ipsum has been the industry's standard
|
Don't pay for public transport. Instead, warn each other
|
||||||
dummy text ever since the 1500s, when an unknown printer
|
from ticket controllers! With Ticketfrei, you can turn
|
||||||
took a galley of type and scrambled it to make a type
|
your city into a paradise for fare dodgers.
|
||||||
specimen book. It has survived not only five centuries,
|
</p>
|
||||||
but also the leap into electronic typesetting, remaining
|
<p>
|
||||||
essentially unchanged. It was popularised in the 1960s
|
Ticketfrei is a Twitter, Mastodon, and E-Mail bot. Users
|
||||||
with the release of Letraset sheets containing Lorem
|
can help each other by tweeting, tooting, or mailing,
|
||||||
Ipsum passages, and more recently with desktop publishing
|
when and where they spot a ticket controller.
|
||||||
software like Aldus PageMaker including versions of Lorem
|
</p>
|
||||||
Ipsum.</p>
|
<p>
|
||||||
|
Ticketfrei automatically retweets, boosts, and remails
|
||||||
|
those controller reports, so others can see them. If there
|
||||||
|
are ticket controllers around, they can still buy a ticket
|
||||||
|
- but if the coast is clear, they can save the money.
|
||||||
|
</p>
|
||||||
<h2>How to get Ticketfrei to my city?</h2>
|
<h2>How to get Ticketfrei to my city?</h2>
|
||||||
<p>sum is simply dummy text of the printing and typesetting
|
<p>
|
||||||
industry. Lorem Ipsum has been the industry's standard
|
We try to make it as easy as possible to spread Ticketfrei
|
||||||
dummy text ever since the 1500s, when an unknown printer
|
to other citys. There are four basic steps:
|
||||||
took a galley of type and scrambled it to make a type
|
</p>
|
||||||
specimen book. It has survived not only five centuries,
|
<ul>
|
||||||
but also the leap into electronic typesetting, remaining
|
<li>Create a Twitter and/or a Mastodon account.</li>
|
||||||
essentially unchanged. It was popularised in the 1960s
|
<li>Register on this website to create a bot for your city.</li>
|
||||||
with the release of Letraset sheets containing Lorem
|
<li>Log in with the social media accounts you want to
|
||||||
Ipsum passages, and more recently with desktop publishing
|
use for Ticketfrei.</li>
|
||||||
software like Aldus PageMaker including versions of Lorem
|
<li>Promote the service! Ticketfrei only works if there is
|
||||||
Ipsum.</p>
|
a community for it. Fortunately, we prepared some material
|
||||||
|
you can use:
|
||||||
|
<a href="https://github.com/b3yond/ticketfrei/tree/master/promotion" target="_blank">https://github.com/b3yond/ticketfrei/tree/master/promotion</a></li>
|
||||||
|
</ul>
|
||||||
% include('template/register-plain.tpl')
|
% include('template/register-plain.tpl')
|
||||||
<h2>Our Mission</h2>
|
<h2>Our Mission</h2>
|
||||||
<p>Contrary to popular belief, Lorem Ipsum is not simply random
|
<p>
|
||||||
text. It has roots in a piece of classical Latin literature
|
Public transportation is meant to provide an easy and
|
||||||
from 45 BC, making it over 2000 years old. Richard
|
time-saving way to move within a region while being
|
||||||
McClintock, a Latin professor at Hampden-Sydney College in
|
affordable for everybody. Unfortunately, this is not the
|
||||||
Virginia, looked up one of the more obscure Latin words,
|
case. Ticketfrei's approach is to enable people to
|
||||||
consectetur, from a Lorem Ipsum passage, and going through
|
reclaim public transportation.
|
||||||
the cites of the word in classical literature, discovered
|
</p>
|
||||||
the undoubtable source. Lorem Ipsum comes from sections
|
<p>
|
||||||
1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum"
|
On short term we want to do this by helping users to avoid
|
||||||
(The Extremes of Good and Evil) by Cicero, written in 45
|
controllers and fines - on long term by pressuring public
|
||||||
BC. This book is a treatise on the theory of ethics, very
|
transportation companies to offer their services free of
|
||||||
popular during the Renaissance. The first line of Lorem
|
charge, financed by the public.
|
||||||
Ipsum, "Lorem ipsum dolor sit amet..", comes from a line
|
</p>
|
||||||
in section 1.10.32.</p>
|
<p>
|
||||||
|
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 should look
|
||||||
|
like!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from config import config
|
||||||
|
import logging
|
||||||
import tweepy
|
import tweepy
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
@ -8,6 +10,9 @@ import report
|
||||||
from user import User
|
from user import User
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TwitterBot(object):
|
class TwitterBot(object):
|
||||||
"""
|
"""
|
||||||
This bot retweets all tweets which
|
This bot retweets all tweets which
|
||||||
|
@ -19,7 +24,7 @@ class TwitterBot(object):
|
||||||
last_mention: the ID of the last tweet which mentioned you
|
last_mention: the ID of the last tweet which mentioned you
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config, logger, uid, db):
|
def __init__(self, uid, db):
|
||||||
"""
|
"""
|
||||||
Initializes the bot and loads all the necessary data.
|
Initializes the bot and loads all the necessary data.
|
||||||
|
|
||||||
|
@ -27,8 +32,6 @@ class TwitterBot(object):
|
||||||
:param history_path: Path to the file with ID of the last retweeted
|
:param history_path: Path to the file with ID of the last retweeted
|
||||||
Tweet
|
Tweet
|
||||||
"""
|
"""
|
||||||
self.config = config
|
|
||||||
self.logger = logger
|
|
||||||
self.db = db
|
self.db = db
|
||||||
self.user = User(db, uid)
|
self.user = User(db, uid)
|
||||||
|
|
||||||
|
@ -58,7 +61,8 @@ class TwitterBot(object):
|
||||||
|
|
||||||
:return: keys: list of these 4 strings.
|
:return: keys: list of these 4 strings.
|
||||||
"""
|
"""
|
||||||
keys = [self.config['twitter']['consumer_key'], self.config['twitter']['consumer_secret']]
|
keys = [config['twitter']['consumer_key'],
|
||||||
|
config['twitter']['consumer_secret']]
|
||||||
row = self.user.get_twitter_token()
|
row = self.user.get_twitter_token()
|
||||||
keys.append(row[0])
|
keys.append(row[0])
|
||||||
keys.append(row[1])
|
keys.append(row[1])
|
||||||
|
@ -91,9 +95,12 @@ class TwitterBot(object):
|
||||||
if self.last_mention == 0:
|
if self.last_mention == 0:
|
||||||
mentions = self.api.mentions_timeline()
|
mentions = self.api.mentions_timeline()
|
||||||
else:
|
else:
|
||||||
mentions = self.api.mentions_timeline(since_id=self.last_mention)
|
mentions = self.api.mentions_timeline(
|
||||||
|
since_id=self.last_mention)
|
||||||
for status in mentions:
|
for status in mentions:
|
||||||
text = re.sub("(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)", "", status.text)
|
text = re.sub(
|
||||||
|
"(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-_]+)",
|
||||||
|
"", status.text)
|
||||||
reports.append(report.Report(status.author.screen_name,
|
reports.append(report.Report(status.author.screen_name,
|
||||||
"twitter",
|
"twitter",
|
||||||
text,
|
text,
|
||||||
|
@ -102,13 +109,14 @@ class TwitterBot(object):
|
||||||
self.save_last()
|
self.save_last()
|
||||||
return reports
|
return reports
|
||||||
except tweepy.RateLimitError:
|
except tweepy.RateLimitError:
|
||||||
self.logger.error("Twitter API Error: Rate Limit Exceeded", exc_info=True)
|
logger.error("Twitter API Error: Rate Limit Exceeded",
|
||||||
|
exc_info=True)
|
||||||
self.waitcounter += 60*15 + 1
|
self.waitcounter += 60*15 + 1
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
self.logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
||||||
self.waitcounter += 10
|
self.waitcounter += 10
|
||||||
except tweepy.TweepError:
|
except tweepy.TweepError:
|
||||||
self.logger.error("Twitter API Error: General Error", exc_info=True)
|
logger.error("Twitter API Error: General Error", exc_info=True)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def repost(self, status):
|
def repost(self, status):
|
||||||
|
@ -121,17 +129,18 @@ class TwitterBot(object):
|
||||||
while 1:
|
while 1:
|
||||||
try:
|
try:
|
||||||
self.api.retweet(status.id)
|
self.api.retweet(status.id)
|
||||||
self.logger.info("Retweeted: " + status.format())
|
logger.info("Retweeted: " + status.format())
|
||||||
if status.id > self.last_mention:
|
if status.id > self.last_mention:
|
||||||
self.last_mention = status.id
|
self.last_mention = status.id
|
||||||
self.save_last()
|
self.save_last()
|
||||||
return status.format()
|
return status.format()
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
self.logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
logger.error("Twitter API Error: Bad Connection",
|
||||||
|
exc_info=True)
|
||||||
sleep(10)
|
sleep(10)
|
||||||
# maybe one day we get rid of this error:
|
# maybe one day we get rid of this error:
|
||||||
except tweepy.TweepError:
|
except tweepy.TweepError:
|
||||||
self.logger.error("Twitter Error", exc_info=True)
|
logger.error("Twitter Error", exc_info=True)
|
||||||
if status.id > self.last_mention:
|
if status.id > self.last_mention:
|
||||||
self.last_mention = status.id
|
self.last_mention = status.id
|
||||||
self.save_last()
|
self.save_last()
|
||||||
|
@ -151,7 +160,8 @@ class TwitterBot(object):
|
||||||
self.api.update_status(status=text)
|
self.api.update_status(status=text)
|
||||||
return
|
return
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
self.logger.error("Twitter API Error: Bad Connection", exc_info=True)
|
logger.error("Twitter API Error: Bad Connection",
|
||||||
|
exc_info=True)
|
||||||
sleep(10)
|
sleep(10)
|
||||||
|
|
||||||
def flow(self, trigger, to_tweet=()):
|
def flow(self, trigger, to_tweet=()):
|
||||||
|
@ -181,32 +191,3 @@ class TwitterBot(object):
|
||||||
|
|
||||||
# Return Retweets for posting on other bots
|
# Return Retweets for posting on other bots
|
||||||
return all_tweets
|
return all_tweets
|
||||||
|
|
||||||
"""
|
|
||||||
if __name__ == "__main__":
|
|
||||||
config = backend.get_config()
|
|
||||||
|
|
||||||
# initialise trigger
|
|
||||||
trigger = trigger.Trigger(config)
|
|
||||||
|
|
||||||
# initialise twitter bot
|
|
||||||
bot = TwitterBot(config)
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
# :todo separate into small functions
|
|
||||||
bot.flow(trigger)
|
|
||||||
sleep(60)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Good bye. Remember to restart the bot!")
|
|
||||||
except:
|
|
||||||
bot.logger.error('Shutdown', exc_info=True)
|
|
||||||
bot.save_last()
|
|
||||||
try:
|
|
||||||
mailer = sendmail.Mailer(config)
|
|
||||||
mailer.send('', config['mail']['contact'],
|
|
||||||
'Ticketfrei Crash Report',
|
|
||||||
attachment=config['logging']['logpath'])
|
|
||||||
except:
|
|
||||||
bot.logger.error('Mail sending failed', exc_info=True)
|
|
||||||
"""
|
|
Loading…
Reference in a new issue