Skip to content

Commit

Permalink
Merge branch 'development' of github.com:nlef/moonraker-telegram-bot …
Browse files Browse the repository at this point in the history
…into development
  • Loading branch information
nlef committed Jul 12, 2024
2 parents 5d8cb96 + 02df373 commit 50c02a5
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm64
34 changes: 31 additions & 3 deletions bot/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,34 @@ def __init__(self, config: configparser.ConfigParser):

class BotConfig(ConfigHelper):
_section = "bot"
_KNOWN_ITEMS = ["bot_token", "chat_id", "user", "password", "api_token", "server", "socks_proxy", "debug", "log_parser", "power_device", "light_device", "upload_path", "services"]
_KNOWN_ITEMS = [
"bot_token",
"chat_id",
"user",
"password",
"api_token",
"server",
"port",
"ssl",
"ssl_validate",
"api_url",
"socks_proxy",
"debug",
"log_parser",
"power_device",
"light_device",
"upload_path",
"services",
]

def __init__(self, config: configparser.ConfigParser):
super().__init__(config)

# Todo: validate server addr have ho port or protocol!
self.host: str = self._get_str("server", default="localhost")
self.protocol: str = "http://"
self.ssl: bool = self._get_boolean("ssl", default=False)
self.ssl_validate: bool = self._get_boolean("ssl_validate", default=True)
self.port: int = self._get_int("port", default=80)
self.api_url: str = self._get_str("api_url", default="https://api.telegram.org/bot")
self.socks_proxy: str = self._get_str("socks_proxy", default="")
self.light_device_name: str = self._get_str("light_device", default="")
Expand All @@ -196,9 +217,16 @@ def __init__(self, config: configparser.ConfigParser):
self.log_path: str = self._get_str("log_path", default="/tmp")
self.log_file: str = self._get_str("log_path", default="/tmp")
self.upload_path: str = self._get_str("upload_path", default="")
self.services: List[str] = self._get_list("services", ["klipper", "moonraker"])
self.services: List[str] = self._get_list("services", default=["klipper", "moonraker"])
self.log_parser: bool = self._get_boolean("log_parser", default=False)

host_parts = self.host.split(":")
if len(host_parts) == 2 and host_parts[1].isdigit():
self.host = host_parts[0]
self.port = int(host_parts[1])
elif len(host_parts) >= 2:
self._parsing_errors.append("Protocol must be specified in other configuration parameters")

@property
def formatted_upload_path(self):
if not self.upload_path:
Expand Down
39 changes: 20 additions & 19 deletions bot/klippy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
from configuration import ConfigWrapper
from power_device import PowerDevice

requests.models.complexjson = orjson # type: ignore

logger = logging.getLogger(__name__)


Expand All @@ -34,7 +32,9 @@ def __init__(
psu_device: PowerDevice,
logging_handler: logging.Handler,
):
self._host: str = f"{config.bot_config.protocol}{config.bot_config.host}"
self._protocol: str = "https" if config.bot_config.ssl else "http"
self._host: str = f"{self._protocol}://{config.bot_config.host}:{config.bot_config.port}"
self._ssl_validate: bool = config.bot_config.ssl_validate
self._hidden_macros: List[str] = config.telegram_ui.hidden_macros + [self._DATA_MACRO]
self._show_private_macros: bool = config.telegram_ui.show_private_macros
self._message_parts: List[str] = config.status_message_content.content
Expand Down Expand Up @@ -145,9 +145,9 @@ def one_shot_token(self) -> str:
if (not self._user and not self._jwt_token) and not self._api_token:
return ""

resp = requests.get(f"{self._host}/access/oneshot_token", headers=self._headers, timeout=15)
resp = requests.get(f"{self._host}/access/oneshot_token", headers=self._headers, timeout=15, verify=self._ssl_validate)
if resp.ok:
res = f"?token={resp.json()['result']}"
res = f"?token={orjson.loads(resp.text)['result']}"
else:
logger.error(resp.reason)
res = ""
Expand All @@ -156,7 +156,7 @@ def one_shot_token(self) -> str:
def _update_printer_objects(self):
resp = self._make_request("GET", "/printer/objects/list")
if resp.ok:
self._objects_list = resp.json()["result"]["objects"]
self._objects_list = orjson.loads(resp.text)["result"]["objects"]

def _reset_file_info(self) -> None:
self.printing_duration = 0.0
Expand Down Expand Up @@ -187,7 +187,7 @@ def printing_filename(self, new_value: str):
# Todo: add response status check!
if not response.ok:
logger.warning("bad response for file request %s", response.reason)
resp = response.json()["result"]
resp = orjson.loads(response.text)["result"]
self._printing_filename = new_value
self.file_estimated_time = resp["estimated_time"] if resp["estimated_time"] else 0.0
self.file_print_start_time = resp["print_start_time"] if resp["print_start_time"] else time.time()
Expand Down Expand Up @@ -223,30 +223,31 @@ def _auth_moonraker(self) -> None:
if not self._user or not self._passwd:
return
# TOdo: add try catch
res = requests.post(f"{self._host}/access/login", json={"username": self._user, "password": self._passwd}, timeout=15)
res = requests.post(f"{self._host}/access/login", json={"username": self._user, "password": self._passwd}, timeout=15, verify=self._ssl_validate)
if res.ok:
self._jwt_token = res.json()["result"]["token"]
self._refresh_token = res.json()["result"]["refresh_token"]
res_result = orjson.loads(res.text)["result"]
self._jwt_token = res_result["token"]
self._refresh_token = res_result["refresh_token"]
else:
logger.error(res.reason)

def _refresh_moonraker_token(self) -> None:
if not self._refresh_token:
return
res = requests.post(f"{self._host}/access/refresh_jwt", json={"refresh_token": self._refresh_token}, timeout=15)
res = requests.post(f"{self._host}/access/refresh_jwt", data=orjson.dumps({"refresh_token": self._refresh_token}), timeout=15, verify=self._ssl_validate)
if res.ok:
logger.debug("JWT token successfully refreshed")
self._jwt_token = res.json()["result"]["token"]
self._jwt_token = orjson.loads(res.text)["result"]["token"]
else:
logger.error("Failed to refresh token: %s", res.reason)

def _make_request(self, method, url_path, json=None, headers=None, files=None, timeout=30, stream=None) -> requests.Response:
_headers = headers if headers else self._headers
res = requests.request(method, f"{self._host}{url_path}", json=json, headers=_headers, files=files, timeout=timeout, stream=stream)
res = requests.request(method, f"{self._host}{url_path}", data=orjson.dumps(json), headers=_headers, files=files, timeout=timeout, stream=stream, verify=self._ssl_validate)
if res.status_code == 401: # Unauthorized
logger.debug("JWT token expired, refreshing...")
self._refresh_moonraker_token()
res = requests.request(method, f"{self._host}{url_path}", json=json, headers=_headers, files=files, timeout=timeout, stream=stream)
res = requests.request(method, f"{self._host}{url_path}", data=orjson.dumps(json), headers=_headers, files=files, timeout=timeout, stream=stream, verify=self._ssl_validate)
if not res.ok:
logger.error(res.reason)
return res
Expand Down Expand Up @@ -404,7 +405,7 @@ def get_print_stats(self, message_pre: str = "") -> str:
return self._get_printing_file_info(message_pre) + self._get_sensors_message() + self._get_power_devices_mess()

def get_status(self) -> str:
resp = self._make_request("GET", "/printer/objects/query?webhooks&print_stats&display_status").json()["result"]["status"]
resp = orjson.loads(self._make_request("GET", "/printer/objects/query?webhooks&print_stats&display_status").text)["result"]["status"]
print_stats = resp["print_stats"]
message = ""

Expand Down Expand Up @@ -432,7 +433,7 @@ def get_status(self) -> str:
return message

def get_file_info_by_name(self, filename: str, message: str) -> Tuple[str, BytesIO]:
resp = self._make_request("GET", f"/server/files/metadata?filename={urllib.parse.quote(filename)}").json()["result"]
resp = orjson.loads(self._make_request("GET", f"/server/files/metadata?filename={urllib.parse.quote(filename)}").text)["result"]
message += "\n"
if "filament_total" in resp and resp["filament_total"] > 0.0:
message += f"Filament: {round(resp['filament_total'] / 1000, 2)}m"
Expand All @@ -456,7 +457,7 @@ def get_file_info_by_name(self, filename: str, message: str) -> Tuple[str, Bytes

def get_gcode_files(self):
response = self._make_request("GET", "/server/files/list?root=gcodes")
files = sorted(response.json()["result"], key=lambda item: item["modified"], reverse=True)
files = sorted(orjson.loads(response.text)["result"], key=lambda item: item["modified"], reverse=True)
return files

def upload_gcode_file(self, file: BytesIO, upload_path: str) -> bool:
Expand All @@ -473,7 +474,7 @@ def get_versions_info(self, bot_only: bool = False) -> str:
if not response.ok:
logger.warning(response.reason)
return ""
version_info = response.json()["result"]["version_info"]
version_info = orjson.loads(response.text)["result"]["version_info"]
version_message = ""
for comp, inf in version_info.items():
if comp == "system":
Expand All @@ -497,7 +498,7 @@ def add_bot_announcements_feed(self):
def get_param_from_db(self, param_name: str):
res = self._make_request("GET", f"/server/database/item?namespace={self._dbname}&key={param_name}")
if res.ok:
return res.json()["result"]["value"]
return orjson.loads(res.text)["result"]["value"]
else:
logger.error("Failed getting %s from %s \n\n%s", param_name, self._dbname, res.reason)
# Fixme: return default value? check for 404!
Expand Down
10 changes: 8 additions & 2 deletions bot/websocket_helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from functools import wraps
import logging
import random
import ssl
import time
from typing import Dict

from apscheduler.schedulers.background import BackgroundScheduler # type: ignore
import orjson
Expand Down Expand Up @@ -41,6 +43,9 @@ def __init__(
logging_handler: logging.Handler,
):
self._host: str = config.bot_config.host
self._port = config.bot_config.port
self._protocol: str = "wss" if config.bot_config.ssl else "ws"
self._ssl_opt: Dict = {} if config.bot_config.ssl_validate else {"cert_reqs": ssl.CERT_NONE, "check_hostname": False}
self._klippy: Klippy = klippy
self._notifier: Notifier = notifier
self._timelapse: Timelapse = timelapse
Expand All @@ -54,8 +59,9 @@ def __init__(
if logging_handler:
logger.addHandler(logging_handler)

# Todo: add port + protocol + ssl_validate
self.websocket = websocket.WebSocketApp(
f"ws://{self._host}/websocket{self._klippy.one_shot_token}",
f"{self._protocol}://{self._host}:{self._port}/websocket{self._klippy.one_shot_token}",
on_message=self.websocket_to_message,
on_open=self.on_open,
on_error=self.on_error,
Expand Down Expand Up @@ -450,4 +456,4 @@ def run_forever(self):

self._scheduler.add_job(self.reshedule, "interval", seconds=2, id="ws_reschedule", replace_existing=True)

self.websocket.run_forever(skip_utf8_validation=True)
self.websocket.run_forever(skip_utf8_validation=True, sslopt=self._ssl_opt)
3 changes: 2 additions & 1 deletion tests/resources/telegram.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[bot]
server: 192.168.1.56:7125
server: 192.168.1.56
port: 7125
chat_id: 16612341234
bot_token: 23423423334:sdfgsdfg-dfgdfgsdfg
light_device: leds
Expand Down
3 changes: 2 additions & 1 deletion tests/resources/telegram_secrets.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
secrets_path: tests/resources/secrets.conf

[bot]
server: 192.168.1.56:7125
server: 192.168.1.56
port: 7125
chat_id: 16612341234
bot_token: 23423423334:sdfgsdfg-dfgdfgsdfg
light_device: leds
Expand Down

0 comments on commit 50c02a5

Please sign in to comment.