Compare commits
No commits in common. "c5f5b151bd0a614762b0becc9ce1f801ebc49a76" and "6f9da6f761bbc26715cfbb3a86a676695c1f3874" have entirely different histories.
c5f5b151bd
...
6f9da6f761
|
|
@ -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
130
bot.py
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
|
||||||
doorbot:
|
|
||||||
build: .
|
|
||||||
image: fswiai/doorbot:0.2
|
|
||||||
volumes:
|
|
||||||
- ./settings.json:/app/settings.json
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
35
sources.py
35
sources.py
|
|
@ -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()
|
|
||||||
121
targets.py
121
targets.py
|
|
@ -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)
|
|
||||||
Loading…
Reference in New Issue