refactoring complete :)

Beware: semantics of `-l` options have changed: Now it prevents looping
matrix
agp8x 2018-04-11 21:38:57 +02:00
parent 40a08bfb15
commit 4b962c4bb5
4 changed files with 167 additions and 119 deletions

122
bot.py
View File

@ -1,116 +1,76 @@
import argparse import argparse
import datetime
import json import json
import time
from tempfile import NamedTemporaryFile
import logging import logging
import time
from tempfile import NamedTemporaryFile
from collections import namedtuple
import requests
import schedule import schedule
STATUS_URL = "https://isfswiaiopen.wiai.de?json" from plot import get_plot
MESSAGE_URL = "https://api.telegram.org/bot{token}/sendMessage" from sources import IsFsWIAIopen
IMAGE_URL = "https://api.telegram.org/bot{token}/sendPhoto" from targets import Client, MatrixClient, TelegramClient
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__)
def parse_time(string): Config = namedtuple("Config", ['sleep', 'plot_interval', 'clients', 'source', 'loop'])
return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
def get_status(): def publish(clients, fn, **kwargs):
status = requests.get(STATUS_URL).json() for client in clients:
status["timestamp"] = parse_time(status['timestamp']) fn(client, **kwargs)
return status
def get_status_text(config, src=get_status):
return config["texts"][str(get_status()["doorstate"])]
def post(chats, text, token):
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):
config = json.load(open(args['config'])) settings = json.load(open(args['config']))
if has_argument(args, "interval"): clients = []
log.info("Overwrite sleep value by argument…") for client in (MatrixClient, TelegramClient):
config["sleep"] = args["interval"] if client.key in settings['groups']:
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 main(args={"config": "settings.json"}): def main(args={'config': "settings.json"}):
log.info("run once")
config = get_config(args) config = get_config(args)
text = get_status_text(config)
post(config['groups'], text, config['token'])
post_plot(config)
def loop(args={"config": "settings.json"}): status = config.source.get_status()
log.info("prepare loop") publish(config.clients, Client.post_text, text=status.text)
config = get_config(args)
setup(config) setup(config)
while True:
while config.loop:
try: try:
do_loop(config) schedule.run_pending()
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)
time.sleep(config['sleep']) if not config.loop:
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): def post_plot(config):
from plot import get_plot
with NamedTemporaryFile() as target: with NamedTemporaryFile() as target:
image_url = IMAGE_URL.format(token=config['token']) plot_file, last = get_plot(target)
photo, last = get_plot(target) plot_file.seek(0)
files = {'photo': photo} publish(config.clients, Client.post_image, file=plot_file, name="")
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): def setup(config):
schedule.every(config['photo_interval']).seconds.do(lambda: post_plot(config)) schedule.every(config.plot_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_true", help="Loop") parser.add_argument("--loop", "-l", action="store_false", help="Loop")
parser.add_argument("--interval", "-i", help="Interval") parser.add_argument("--interval", "-i", help="Interval")
args = parser.parse_args() args = parser.parse_args()
if args.loop: main(vars(args))
loop(vars(args))
else:
main(vars(args))

View File

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

35
sources.py Normal file
View File

@ -0,0 +1,35 @@
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

@ -5,20 +5,54 @@ import requests
from matrix_client.client import MatrixClient as MatrixApiClient from matrix_client.client import MatrixClient as MatrixApiClient
from matrix_client.errors import MatrixError from matrix_client.errors import MatrixError
class Client: class Client:
def send_image(self, target, path, name, content_type="image/png"): def post_image(self, file, name, content_type="image/png", targets=None):
raise NotImplementedError() """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): def send_text(self, target, text):
raise NotImplementedError() """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): class MatrixClient(Client):
key = "matrix"
def __init__(self, host, username, password): def __init__(self, host, username, password, **kwargs):
self.client = MatrixApiClient(host) self.client = MatrixApiClient(host)
self.client.login_with_password_no_sync(username=username, password=password) self.client.login_with_password_no_sync(username=username, password=password)
self.log = logging.getLogger(__name__) self.log = logging.getLogger(__name__)
self.targets = kwargs.values()
def _get_room(self, target): def _get_room(self, target):
if target not in self.client.rooms: if target not in self.client.rooms:
@ -30,47 +64,58 @@ class MatrixClient(Client):
return None return None
return self.client.rooms[target] return self.client.rooms[target]
def send_image(self, target, path, name, content_type="image/png"): def _upload_image(self, file, content_type):
"""Send an image to a room
Args:
target (str): The internal room id to post into
path (str/Path-like): The path to the image file
name (str): The name for the file in the room
content_type (str): Content-type of the image
"""
with open(path, "rb") as src:
data = src.read()
try: try:
mxc = self.client.api.media_upload(data, content_type) data = file.read()
file.seek(0)
return self.client.api.media_upload(data, content_type)
except MatrixError as e: except MatrixError as e:
self.log.exception(e) self.log.exception(e)
return return None
room = self._get_room(target)
if room: 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) room.send_image(mxc['content_uri'], name)
def send_text(self, target, text): def post_text(self, text, targets=None):
room = self._get_room(target) targets = self._get_targets(targets)
if room: for target in targets:
room.send_text(text) room = self._get_room(target)
if room:
room.send_text(text)
class TelegramClient(Client): class TelegramClient(Client):
key = "telegram"
MESSAGE_URL = "https://api.telegram.org/bot{token}/sendMessage" MESSAGE_URL = "https://api.telegram.org/bot{token}/sendMessage"
IMAGE_URL = "https://api.telegram.org/bot{token}/sendPhoto" IMAGE_URL = "https://api.telegram.org/bot{token}/sendPhoto"
def __init__(self, token): def __init__(self, token, **kwargs):
self.text_url = self.MESSAGE_URL.format(token=token) self.text_url = self.MESSAGE_URL.format(token=token)
self.image_url = self.IMAGE_URL.format(token=token) self.image_url = self.IMAGE_URL.format(token=token)
self.log = logging.getLogger(__name__) self.log = logging.getLogger(__name__)
self.targets = kwargs.values()
def send_image(self, target, path, name, content_type="image/png"): def post_image(self, file, name, content_type="image/png", targets=None):
files = {'photo': open(path, "rb")} targets = self._get_targets(targets)
values = {"chat_id": target} for target in targets:
r = requests.post(self.image_url, files=files, data=values) files = {'photo': file}
self.log.info("post photo: %s", r.status_code) 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 send_text(self, target, text): def post_text(self, text, targets=None):
response = requests.post(self.text_url, data={'chat_id': target, "text": text}) targets = self._get_targets(targets)
self.log.info("post message: %s", response.status_code) 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)