Compare commits

..

No commits in common. "c5f5b151bd0a614762b0becc9ce1f801ebc49a76" and "6f9da6f761bbc26715cfbb3a86a676695c1f3874" have entirely different histories.

7 changed files with 88 additions and 229 deletions

View File

@ -1,8 +0,0 @@
FROM python:3.6-alpine3.7
ADD [".", "/app"]
WORKDIR /app
RUN apk add --update g++ gfortran openblas-dev libpng-dev musl-dev freetype-dev
RUN pip install -r requirements.txt --no-cache-dir
CMD ["python", "bot.py"]

130
bot.py
View File

@ -1,76 +1,116 @@
import argparse import argparse
import datetime
import json import json
import logging
import time import time
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from collections import namedtuple import logging
import requests
import schedule import schedule
from plot import get_plot STATUS_URL = "https://isfswiaiopen.wiai.de?json"
from sources import IsFsWIAIopen MESSAGE_URL = "https://api.telegram.org/bot{token}/sendMessage"
from targets import Client, MatrixClient, TelegramClient IMAGE_URL = "https://api.telegram.org/bot{token}/sendPhoto"
logging.basicConfig(format='%(asctime)s %(levelname)s %(name)s:%(message)s', level=logging.DEBUG, datefmt="%Y.%m.%d %H:%M:%S") logging.basicConfig(format='%(asctime)s %(levelname)s %(name)s:%(message)s', level=logging.DEBUG, datefmt="%Y.%m.%d %H:%M:%S")
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
Config = namedtuple("Config", ['sleep', 'plot_interval', 'clients', 'source', 'loop']) def parse_time(string):
return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
def publish(clients, fn, **kwargs): def get_status():
for client in clients: status = requests.get(STATUS_URL).json()
fn(client, **kwargs) status["timestamp"] = parse_time(status['timestamp'])
return status
def post_plot(config): def get_status_text(config, src=get_status):
with NamedTemporaryFile() as target: return config["texts"][str(get_status()["doorstate"])]
plot_file, last = get_plot(target)
plot_file.seek(0) def post(chats, text, token):
publish(config.clients, Client.post_image, file=plot_file, name="") url = MESSAGE_URL.format(token=token)
for chat in chats:
response = requests.post(url, data={'chat_id': chats[chat], "text": text})
log.info("post message: %s", response.status_code)
def has_argument(args, key): def has_argument(args, key):
return key in args and args[key] return key in args and args[key]
def get_config(args): def get_config(args):
settings = json.load(open(args['config'])) config = json.load(open(args['config']))
clients = [] if has_argument(args, "interval"):
for client in (MatrixClient, TelegramClient): log.info("Overwrite sleep value by argument…")
if client.key in settings['groups']: config["sleep"] = args["interval"]
clients.append(client(**settings['groups'][client.key]))
config = Config(
sleep=args['interval'] if has_argument(args, 'interval') else settings['sleep'],
plot_interval=settings['plot_interval'],
clients=clients,
source=IsFsWIAIopen(texts=settings['texts']),
loop=args['loop'])
return config return config
def setup(config): def main(args={"config": "settings.json"}):
schedule.every(config.plot_interval).seconds.do(lambda: post_plot(config)) log.info("run once")
config = get_config(args)
def main(args={'config': "settings.json"}): text = get_status_text(config)
post(config['groups'], text, config['token'])
post_plot(config)
def loop(args={"config": "settings.json"}):
log.info("prepare loop")
config = get_config(args) config = get_config(args)
status = config.source.get_status()
publish(config.clients, Client.post_text, text=status.text)
setup(config) setup(config)
while True:
while config.loop:
try: try:
schedule.run_pending() do_loop(config)
new_status = config.source.get_status()
if not new_status.doorstate == status.doorstate:
status = new_status
publish(config.clients, Client.post_text, text=status.text)
time.sleep(config.sleep)
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
if not config.loop: time.sleep(config['sleep'])
post_plot(config)
def do_loop(config):
last_state = None
while True:
log.info("enter loop")
changed = has_changed(last_state)
if changed:
last_state = update(new_state)
log.info("run pending tasks")
schedule.run_pending()
log.info("sleep")
time.sleep(config['sleep'])
def has_changed(last_state):
changed = False
new_state = get_status()
if last_state is None:
changed = True
elif not last_state["doorstate"] == new_state["doorstate"]:
changed = True
return changed
def update(state):
text = get_status_text(config, lambda: state)
post(config["groups"], text, config["token"])
return state
def post_plot(config):
from plot import get_plot
with NamedTemporaryFile() as target:
image_url = IMAGE_URL.format(token=config['token'])
photo, last = get_plot(target)
files = {'photo': photo}
if last + datetime.timedelta(days=1) < datetime.datetime.today():
log.info("skipping image, no new updates...")
return
for chat in config['groups']:
files['photo'].seek(0)
values = {"chat_id": config['groups'][chat]}
r = requests.post(image_url, files=files, data=values)
log.info("post photo: %s", r.status_code)
def setup(config):
schedule.every(config['photo_interval']).seconds.do(lambda: post_plot(config))
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="DoorStateBot") parser = argparse.ArgumentParser(description="DoorStateBot")
parser.add_argument("--config", "-c", default="settings.json", help="Configuration file") parser.add_argument("--config", "-c", default="settings.json", help="Configuration file")
parser.add_argument("--loop", "-l", action="store_false", help="Loop") parser.add_argument("--loop", "-l", action="store_true", help="Loop")
parser.add_argument("--interval", "-i", help="Interval") parser.add_argument("--interval", "-i", help="Interval")
args = parser.parse_args() args = parser.parse_args()
main(vars(args)) if args.loop:
loop(vars(args))
else:
main(vars(args))

View File

@ -1,8 +0,0 @@
version: "2"
services:
doorbot:
build: .
image: fswiai/doorbot:0.2
volumes:
- ./settings.json:/app/settings.json

View File

@ -2,4 +2,3 @@ requests==2.18.4
schedule==0.4.3 schedule==0.4.3
matplotlib==2.0.2 matplotlib==2.0.2
numpy==1.13.1 numpy==1.13.1
matrix_client==0.1.0

View File

@ -1,17 +1,9 @@
{ {
"token": "BOTID:TOKEN",
"sleep": 30, "sleep": 30,
"plot_interval": 30, "photo_interval": 30,
"groups":{ "groups":{
"matrix": { "wiaidoor": -12357567
"host": "…",
"username": "…",
"password": "…",
"doorstate": "..."
},
"telegram": {
"token": "BOTID:TOKEN",
"wiaidoor": -12357567
}
}, },
"disabled":{ "disabled":{
"WIAIdoorTest": -234502, "WIAIdoorTest": -234502,

View File

@ -1,35 +0,0 @@
import datetime
import requests
from collections import namedtuple
Status = namedtuple("Status", ['doorstate', 'timestamp', 'text'])
class Source:
def get_status(self):
raise NotImplementedError()
def is_recent(self, status, **kwargs):
raise NotImplementedError()
class IsFsWIAIopen(Source):
url = "https://isfswiaiopen.wiai.de?json"
def __init__(self, texts=None):
self.texts = texts
def _parse_time(self, string):
return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
def _get_text(self, state):
return self.texts[state] if self.texts else ""
def get_status(self):
status = requests.get(self.url).json()
return Status(
doorstate=str(status['doorstate']),
timestamp=self._parse_time(status['timestamp']),
text=self._get_text(str(status['doorstate'])))
def is_recent(self, status, **kwargs):
return status.timestamp + datetime.timedelta(days=1) < datetime.datetime.today()

View File

@ -1,121 +0,0 @@
import logging
import requests
from matrix_client.client import MatrixClient as MatrixApiClient
from matrix_client.errors import MatrixError
class Client:
def post_image(self, file, name, content_type="image/png", targets=None):
"""Push to all targets"""
self.post_image(file, name, content_type, targets)
#raise NotImplementedError()
def send_image(self, target, file, name, content_type="image/png"):
"""Send an image to a room
Args:
target (str): The internal room id to post into
path (file): The image file object
name (str): The name for the file in the room
content_type (str): Content-type of the image
"""
self.post_image(path, name, content_type, targets=[target])
def post_text(self, text, targets=None):
"""Push to all targets"""
self.post_text(text, targets)
#raise NotImplementedError()
def send_text(self, target, text):
"""Send a text to a room
Args:
target (str): The internal room id to post into
text (str): The text to post
"""
self.post_text(text, targets=[target])
def _get_targets(self, targets):
if not targets:
targets = self.targets
if not targets:
self.log.info("no targets…")
targets = []
return targets
class MatrixClient(Client):
key = "matrix"
def __init__(self, host, username, password, **kwargs):
self.client = MatrixApiClient(host)
self.client.login_with_password_no_sync(username=username, password=password)
self.log = logging.getLogger(__name__)
self.targets = kwargs.values()
def _get_room(self, target):
if target not in self.client.rooms:
try:
self.client.join_room(target)
except MatrixError as e:
self.log.error("could not join room '" + target + "'")
self.log.exception(e)
return None
return self.client.rooms[target]
def _upload_image(self, file, content_type):
try:
data = file.read()
file.seek(0)
return self.client.api.media_upload(data, content_type)
except MatrixError as e:
self.log.exception(e)
return None
def post_image(self, file, name, content_type="image/png", targets=None):
targets = self._get_targets(targets)
mxc = None
for target in targets:
room = self._get_room(target)
if not room:
self.log.error("could not get room '" + target + "'")
continue
if not mxc:
mxc = self._upload_image(file, content_type)
if not mxc:
return
room.send_image(mxc['content_uri'], name)
def post_text(self, text, targets=None):
targets = self._get_targets(targets)
for target in targets:
room = self._get_room(target)
if room:
room.send_text(text)
class TelegramClient(Client):
key = "telegram"
MESSAGE_URL = "https://api.telegram.org/bot{token}/sendMessage"
IMAGE_URL = "https://api.telegram.org/bot{token}/sendPhoto"
def __init__(self, token, **kwargs):
self.text_url = self.MESSAGE_URL.format(token=token)
self.image_url = self.IMAGE_URL.format(token=token)
self.log = logging.getLogger(__name__)
self.targets = kwargs.values()
def post_image(self, file, name, content_type="image/png", targets=None):
targets = self._get_targets(targets)
for target in targets:
files = {'photo': file}
values = {'chat_id': target}
response = requests.post(self.image_url, files=files, data=values)
self.log.info("post message: %s -- %s", response.status_code, response.text)
file.seek(0)
def post_text(self, text, targets=None):
targets = self._get_targets(targets)
for target in targets:
response = requests.post(self.text_url, data={'chat_id': target, 'text': text})
self.log.info("post message: %s -- %s", response.status_code, response.text)