fix flake8 findings
This commit is contained in:
parent
23a84dda04
commit
a0eb86a76f
16 changed files with 230 additions and 121 deletions
|
|
@ -1,3 +1,4 @@
|
|||
"""Init module."""
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -5,8 +6,8 @@ import socket
|
|||
import sys
|
||||
|
||||
from bumper.db import (
|
||||
bot_reset_connectionStatus,
|
||||
client_reset_connectionStatus,
|
||||
bot_reset_connection_status,
|
||||
client_reset_connection_status,
|
||||
revoke_expired_oauths,
|
||||
revoke_expired_tokens,
|
||||
)
|
||||
|
|
@ -18,6 +19,7 @@ from bumper.xmppserver import XMPPServer
|
|||
|
||||
|
||||
def strtobool(strbool: str | bool | None) -> bool:
|
||||
"""Convert str to bool."""
|
||||
if str(strbool).lower() in ["true", "1", "t", "y", "on", "yes"]:
|
||||
return True
|
||||
else:
|
||||
|
|
@ -77,13 +79,14 @@ web_server_bindings = [
|
|||
|
||||
|
||||
async def start() -> None:
|
||||
"""Start bumper."""
|
||||
# Reset xmpp/mqtt to false in database for bots and clients
|
||||
bot_reset_connectionStatus()
|
||||
client_reset_connectionStatus()
|
||||
bot_reset_connection_status()
|
||||
client_reset_connection_status()
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
except:
|
||||
except: # noqa: E722
|
||||
loop = asyncio.new_event_loop()
|
||||
|
||||
if bumper_debug:
|
||||
|
|
@ -150,22 +153,28 @@ async def start() -> None:
|
|||
|
||||
|
||||
async def maintenance() -> None:
|
||||
"""Run maintenance."""
|
||||
revoke_expired_tokens()
|
||||
revoke_expired_oauths()
|
||||
|
||||
|
||||
async def shutdown() -> None:
|
||||
"""Shutdown bumper."""
|
||||
try:
|
||||
bumperlog.info("Shutting down")
|
||||
global shutting_down
|
||||
shutting_down = True
|
||||
|
||||
global mqtt_helperbot
|
||||
await mqtt_helperbot.disconnect()
|
||||
global web_server
|
||||
await web_server.shutdown()
|
||||
global mqtt_server
|
||||
while mqtt_server.state == "starting":
|
||||
await asyncio.sleep(0.1)
|
||||
if mqtt_server.state == "started":
|
||||
await mqtt_server.shutdown()
|
||||
global xmpp_server
|
||||
if xmpp_server.server:
|
||||
if xmpp_server.server.is_serving:
|
||||
xmpp_server.server.close()
|
||||
|
|
@ -177,6 +186,7 @@ async def shutdown() -> None:
|
|||
|
||||
|
||||
def main(argv: None | list[str] = None) -> None:
|
||||
"""Start everything."""
|
||||
import argparse
|
||||
|
||||
global bumper_debug
|
||||
|
|
|
|||
215
bumper/db.py
215
bumper/db.py
|
|
@ -1,3 +1,4 @@
|
|||
"""Database module."""
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
|
@ -13,17 +14,17 @@ from .util import get_logger
|
|||
_LOGGER = get_logger("db")
|
||||
|
||||
|
||||
def db_file() -> str:
|
||||
return os.environ.get("DB_FILE") or os_db_path()
|
||||
def _db_file() -> str:
|
||||
return os.environ.get("DB_FILE") or _os_db_path()
|
||||
|
||||
|
||||
def os_db_path() -> str: # createdir=True):
|
||||
def _os_db_path() -> str: # createdir=True):
|
||||
return os.path.join(bumper.data_dir, "bumper.db")
|
||||
|
||||
|
||||
def db_get() -> TinyDB:
|
||||
def _db_get() -> TinyDB:
|
||||
# Will create the database if it doesn't exist
|
||||
db = TinyDB(db_file())
|
||||
db = TinyDB(_db_file())
|
||||
|
||||
# Will create the tables if they don't exist
|
||||
db.table("users", cache_size=0)
|
||||
|
|
@ -36,29 +37,32 @@ def db_get() -> TinyDB:
|
|||
|
||||
|
||||
def user_add(userid: str) -> None:
|
||||
"""Add user."""
|
||||
newuser = BumperUser()
|
||||
newuser.userid = userid
|
||||
|
||||
user = user_get(userid)
|
||||
if not user:
|
||||
_LOGGER.info(f"Adding new user with userid: {newuser.userid}")
|
||||
user_full_upsert(newuser.asdict())
|
||||
_user_full_upsert(newuser.asdict())
|
||||
|
||||
|
||||
def user_get(userid: str) -> None | Document:
|
||||
users = db_get().table("users")
|
||||
"""Get user."""
|
||||
users = _db_get().table("users")
|
||||
User = Query()
|
||||
return users.get(User.userid == userid)
|
||||
|
||||
|
||||
def user_by_deviceid(deviceid: str) -> None | Document:
|
||||
users = db_get().table("users")
|
||||
def user_by_device_id(deviceid: str) -> None | Document:
|
||||
"""Get user by device id."""
|
||||
users = _db_get().table("users")
|
||||
User = Query()
|
||||
return users.get(User.devices.any([deviceid]))
|
||||
|
||||
|
||||
def user_full_upsert(user: dict[str, Any]) -> None:
|
||||
opendb = db_get()
|
||||
def _user_full_upsert(user: dict[str, Any]) -> None:
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
users = opendb.table("users")
|
||||
User = Query()
|
||||
|
|
@ -66,21 +70,23 @@ def user_full_upsert(user: dict[str, Any]) -> None:
|
|||
|
||||
|
||||
def user_add_device(userid: str, devid: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Add device to user."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
users = opendb.table("users")
|
||||
User = Query()
|
||||
user = users.get(User.userid == userid)
|
||||
if user:
|
||||
userdevices = list(user["devices"])
|
||||
if not devid in userdevices:
|
||||
if devid not in userdevices:
|
||||
userdevices.append(devid)
|
||||
|
||||
users.upsert({"devices": userdevices}, User.userid == userid)
|
||||
|
||||
|
||||
def user_remove_device(userid: str, devid: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Remove device from user."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
users = opendb.table("users")
|
||||
User = Query()
|
||||
|
|
@ -94,21 +100,23 @@ def user_remove_device(userid: str, devid: str) -> None:
|
|||
|
||||
|
||||
def user_add_bot(userid: str, did: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Add bot to user."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
users = opendb.table("users")
|
||||
User = Query()
|
||||
user = users.get(User.userid == userid)
|
||||
if user:
|
||||
userbots = list(user["bots"])
|
||||
if not did in userbots:
|
||||
if did not in userbots:
|
||||
userbots.append(did)
|
||||
|
||||
users.upsert({"bots": userbots}, User.userid == userid)
|
||||
|
||||
|
||||
def user_remove_bot(userid: str, did: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Remove bot from user."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
users = opendb.table("users")
|
||||
User = Query()
|
||||
|
|
@ -122,17 +130,20 @@ def user_remove_bot(userid: str, did: str) -> None:
|
|||
|
||||
|
||||
def user_get_tokens(userid: str) -> list[Document]:
|
||||
tokens = db_get().table("tokens")
|
||||
"""Get all tokens by given user."""
|
||||
tokens = _db_get().table("tokens")
|
||||
return tokens.search(Query().userid == userid)
|
||||
|
||||
|
||||
def user_get_token(userid: str, token: str) -> Document | None:
|
||||
tokens = db_get().table("tokens")
|
||||
"""Get token by user."""
|
||||
tokens = _db_get().table("tokens")
|
||||
return tokens.get((Query().userid == userid) & (Query().token == token))
|
||||
|
||||
|
||||
def user_add_token(userid: str, token: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Ass token for given user."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
tokens = opendb.table("tokens")
|
||||
tmptoken = tokens.get((Query().userid == userid) & (Query().token == token))
|
||||
|
|
@ -151,7 +162,8 @@ def user_add_token(userid: str, token: str) -> None:
|
|||
|
||||
|
||||
def user_revoke_all_tokens(userid: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Revoke all tokens for given user."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
tokens = opendb.table("tokens")
|
||||
tsearch = tokens.search(Query().userid == userid)
|
||||
|
|
@ -160,7 +172,8 @@ def user_revoke_all_tokens(userid: str) -> None:
|
|||
|
||||
|
||||
def user_revoke_expired_tokens(userid: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Revoke expired user tokens."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
tokens = opendb.table("tokens")
|
||||
tsearch = tokens.search(Query().userid == userid)
|
||||
|
|
@ -171,7 +184,8 @@ def user_revoke_expired_tokens(userid: str) -> None:
|
|||
|
||||
|
||||
def user_revoke_token(userid: str, token: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Revoke user token."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
tokens = opendb.table("tokens")
|
||||
tmptoken = tokens.get((Query().userid == userid) & (Query().token == token))
|
||||
|
|
@ -180,7 +194,8 @@ def user_revoke_token(userid: str, token: str) -> None:
|
|||
|
||||
|
||||
def user_add_authcode(userid: str, token: str, authcode: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Add user authcode."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
tokens = opendb.table("tokens")
|
||||
tmptoken = tokens.get((Query().userid == userid) & (Query().token == token))
|
||||
|
|
@ -192,7 +207,8 @@ def user_add_authcode(userid: str, token: str, authcode: str) -> None:
|
|||
|
||||
|
||||
def user_revoke_authcode(userid: str, token: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Revoke user authcode."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
tokens = opendb.table("tokens")
|
||||
tmptoken = tokens.get((Query().userid == userid) & (Query().token == token))
|
||||
|
|
@ -204,7 +220,8 @@ def user_revoke_authcode(userid: str, token: str) -> None:
|
|||
|
||||
|
||||
def revoke_expired_oauths() -> None:
|
||||
opendb = db_get()
|
||||
"""Revoke expired oauths."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
table = opendb.table("oauth")
|
||||
entries = table.all()
|
||||
|
|
@ -217,7 +234,8 @@ def revoke_expired_oauths() -> None:
|
|||
|
||||
|
||||
def user_revoke_expired_oauths(userid: str) -> None:
|
||||
opendb = db_get()
|
||||
"""Revoke expired oauths by user."""
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
table = opendb.table("oauth")
|
||||
search = table.search(Query().userid == userid)
|
||||
|
|
@ -229,8 +247,9 @@ def user_revoke_expired_oauths(userid: str) -> None:
|
|||
|
||||
|
||||
def user_add_oauth(userid: str) -> OAuth:
|
||||
"""Add oauth for user."""
|
||||
user_revoke_expired_oauths(userid)
|
||||
opendb = db_get()
|
||||
opendb = _db_get()
|
||||
with opendb:
|
||||
table = opendb.table("oauth")
|
||||
entry = table.get(Query().userid == userid)
|
||||
|
|
@ -244,19 +263,22 @@ def user_add_oauth(userid: str) -> OAuth:
|
|||
|
||||
|
||||
def token_by_authcode(authcode: str) -> Document | None:
|
||||
tokens = db_get().table("tokens")
|
||||
"""Get token by authcode."""
|
||||
tokens = _db_get().table("tokens")
|
||||
return tokens.get(Query().authcode == authcode)
|
||||
|
||||
|
||||
def get_disconnected_xmpp_clients() -> list[Document]:
|
||||
clients = db_get().table("clients")
|
||||
Client = Query()
|
||||
return clients.search(Client.xmpp_connection == False)
|
||||
"""Get disconnected XMPP clients."""
|
||||
clients = _db_get().table("clients")
|
||||
client = Query()
|
||||
return clients.search(client.xmpp_connection == False) # noqa: E712
|
||||
|
||||
|
||||
def check_authcode(uid: str, authcode: str) -> bool:
|
||||
"""Check authcode."""
|
||||
_LOGGER.debug(f"Checking for authcode: {authcode}")
|
||||
tokens = db_get().table("tokens")
|
||||
tokens = _db_get().table("tokens")
|
||||
tmpauth = tokens.get(
|
||||
(Query().authcode == authcode)
|
||||
& ( # Match authcode
|
||||
|
|
@ -270,9 +292,10 @@ def check_authcode(uid: str, authcode: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def loginByItToken(authcode: str) -> dict[str, str]:
|
||||
def login_by_it_token(authcode: str) -> dict[str, str]:
|
||||
"""Login by token."""
|
||||
_LOGGER.debug(f"Checking for authcode: {authcode}")
|
||||
tokens = db_get().table("tokens")
|
||||
tokens = _db_get().table("tokens")
|
||||
tmpauth = tokens.get(
|
||||
Query().authcode
|
||||
== authcode
|
||||
|
|
@ -288,8 +311,9 @@ def loginByItToken(authcode: str) -> dict[str, str]:
|
|||
|
||||
|
||||
def check_token(uid: str, token: str) -> bool:
|
||||
"""Check token."""
|
||||
_LOGGER.debug(f"Checking for token: {token}")
|
||||
tokens = db_get().table("tokens")
|
||||
tokens = _db_get().table("tokens")
|
||||
tmpauth = tokens.get(
|
||||
(Query().token == token)
|
||||
& ( # Match token
|
||||
|
|
@ -304,122 +328,137 @@ def check_token(uid: str, token: str) -> bool:
|
|||
|
||||
|
||||
def revoke_expired_tokens() -> None:
|
||||
tokens = db_get().table("tokens").all()
|
||||
"""Revoke expired tokens."""
|
||||
tokens = _db_get().table("tokens").all()
|
||||
for i in tokens:
|
||||
if datetime.now() >= datetime.fromisoformat(i["expiration"]):
|
||||
_LOGGER.debug("Removing token {} due to expiration".format(i["token"]))
|
||||
db_get().table("tokens").remove(doc_ids=[i.doc_id])
|
||||
_db_get().table("tokens").remove(doc_ids=[i.doc_id])
|
||||
|
||||
|
||||
def bot_add(sn: str, did: str, devclass: str, resource: str, company: str) -> None:
|
||||
newbot = VacBotDevice()
|
||||
newbot.did = did
|
||||
newbot.name = sn
|
||||
newbot.vac_bot_device_class = devclass
|
||||
newbot.resource = resource
|
||||
newbot.company = company
|
||||
def bot_add(sn: str, did: str, dev_class: str, resource: str, company: str) -> None:
|
||||
"""Add bot."""
|
||||
new_bot = VacBotDevice()
|
||||
new_bot.did = did
|
||||
new_bot.name = sn
|
||||
new_bot.vac_bot_device_class = dev_class
|
||||
new_bot.resource = resource
|
||||
new_bot.company = company
|
||||
|
||||
bot = bot_get(did)
|
||||
if not bot: # Not existing bot in database
|
||||
if (
|
||||
not devclass == "" or "@" not in sn or "tmp" not in sn
|
||||
not dev_class == "" or "@" not in sn or "tmp" not in sn
|
||||
): # try to prevent bad additions to the bot list
|
||||
_LOGGER.info(f"Adding new bot with SN: {newbot.name} DID: {newbot.did}")
|
||||
bot_full_upsert(newbot.asdict())
|
||||
_LOGGER.info(f"Adding new bot with SN: {new_bot.name} DID: {new_bot.did}")
|
||||
bot_full_upsert(new_bot.asdict())
|
||||
|
||||
|
||||
def bot_remove(did: str) -> None:
|
||||
bots = db_get().table("bots")
|
||||
"""Remove bot."""
|
||||
bots = _db_get().table("bots")
|
||||
bot = bot_get(did)
|
||||
if bot:
|
||||
bots.remove(doc_ids=[bot.doc_id])
|
||||
|
||||
|
||||
def bot_get(did: str) -> Document | None:
|
||||
bots = db_get().table("bots")
|
||||
Bot = Query()
|
||||
return bots.get(Bot.did == did)
|
||||
"""Get bot."""
|
||||
bots = _db_get().table("bots")
|
||||
bot = Query()
|
||||
return bots.get(bot.did == did)
|
||||
|
||||
|
||||
def bot_full_upsert(vacbot: dict[str, Any]) -> None:
|
||||
bots = db_get().table("bots")
|
||||
Bot = Query()
|
||||
"""Upsert bot."""
|
||||
bots = _db_get().table("bots")
|
||||
bot = Query()
|
||||
if "did" in vacbot:
|
||||
bots.upsert(vacbot, Bot.did == vacbot["did"])
|
||||
bots.upsert(vacbot, bot.did == vacbot["did"])
|
||||
else:
|
||||
_LOGGER.error(f"No DID in vacbot: {vacbot}")
|
||||
|
||||
|
||||
def bot_set_nick(did: str, nick: str) -> None:
|
||||
bots = db_get().table("bots")
|
||||
Bot = Query()
|
||||
bots.upsert({"nick": nick}, Bot.did == did)
|
||||
"""Bot set nickname."""
|
||||
bots = _db_get().table("bots")
|
||||
bot = Query()
|
||||
bots.upsert({"nick": nick}, bot.did == did)
|
||||
|
||||
|
||||
def bot_set_mqtt(did: str, mqtt: bool) -> None:
|
||||
bots = db_get().table("bots")
|
||||
Bot = Query()
|
||||
bots.upsert({"mqtt_connection": mqtt}, Bot.did == did)
|
||||
"""Bot ste MQTT status."""
|
||||
bots = _db_get().table("bots")
|
||||
bot = Query()
|
||||
bots.upsert({"mqtt_connection": mqtt}, bot.did == did)
|
||||
|
||||
|
||||
def bot_set_xmpp(did: str, xmpp: bool) -> None:
|
||||
bots = db_get().table("bots")
|
||||
Bot = Query()
|
||||
bots.upsert({"xmpp_connection": xmpp}, Bot.did == did)
|
||||
"""Bot set XMPP status."""
|
||||
bots = _db_get().table("bots")
|
||||
bot = Query()
|
||||
bots.upsert({"xmpp_connection": xmpp}, bot.did == did)
|
||||
|
||||
|
||||
def client_add(userid: str, realm: str, resource: str) -> None:
|
||||
newclient = VacBotClient()
|
||||
newclient.userid = userid
|
||||
newclient.realm = realm
|
||||
newclient.resource = resource
|
||||
"""Add client."""
|
||||
new_client = VacBotClient()
|
||||
new_client.userid = userid
|
||||
new_client.realm = realm
|
||||
new_client.resource = resource
|
||||
|
||||
client = client_get(resource)
|
||||
if not client:
|
||||
_LOGGER.info(f"Adding new client with resource {newclient.resource}")
|
||||
client_full_upsert(newclient.asdict())
|
||||
_LOGGER.info(f"Adding new client with resource {new_client.resource}")
|
||||
_client_full_upsert(new_client.asdict())
|
||||
|
||||
|
||||
def client_remove(resource: str) -> None:
|
||||
clients = db_get().table("clients")
|
||||
"""Remove client."""
|
||||
clients = _db_get().table("clients")
|
||||
client = client_get(resource)
|
||||
if client:
|
||||
clients.remove(doc_ids=[client.doc_id])
|
||||
|
||||
|
||||
def client_get(resource: str) -> Document | None:
|
||||
clients = db_get().table("clients")
|
||||
Client = Query()
|
||||
return clients.get(Client.resource == resource)
|
||||
"""Get client by resource."""
|
||||
clients = _db_get().table("clients")
|
||||
client = Query()
|
||||
return clients.get(client.resource == resource)
|
||||
|
||||
|
||||
def client_full_upsert(client: dict[str, Any]) -> None:
|
||||
clients = db_get().table("clients")
|
||||
Client = Query()
|
||||
clients.upsert(client, Client.resource == client["resource"])
|
||||
def _client_full_upsert(client: dict[str, Any]) -> None:
|
||||
clients = _db_get().table("clients")
|
||||
client_query = Query()
|
||||
clients.upsert(client, client_query.resource == client["resource"])
|
||||
|
||||
|
||||
def client_set_mqtt(resource: str, mqtt: bool) -> None:
|
||||
clients = db_get().table("clients")
|
||||
Client = Query()
|
||||
clients.upsert({"mqtt_connection": mqtt}, Client.resource == resource)
|
||||
"""Client set MQTT status."""
|
||||
clients = _db_get().table("clients")
|
||||
client = Query()
|
||||
clients.upsert({"mqtt_connection": mqtt}, client.resource == resource)
|
||||
|
||||
|
||||
def client_set_xmpp(resource: str, xmpp: bool) -> None:
|
||||
clients = db_get().table("clients")
|
||||
Client = Query()
|
||||
clients.upsert({"xmpp_connection": xmpp}, Client.resource == resource)
|
||||
"""Client set XMPP status."""
|
||||
clients = _db_get().table("clients")
|
||||
client = Query()
|
||||
clients.upsert({"xmpp_connection": xmpp}, client.resource == resource)
|
||||
|
||||
|
||||
def bot_reset_connectionStatus() -> None:
|
||||
bots = db_get().table("bots")
|
||||
def bot_reset_connection_status() -> None:
|
||||
"""Reset all bot connection status."""
|
||||
bots = _db_get().table("bots")
|
||||
for bot in bots:
|
||||
bot_set_mqtt(bot["did"], False)
|
||||
bot_set_xmpp(bot["did"], False)
|
||||
|
||||
|
||||
def client_reset_connectionStatus() -> None:
|
||||
clients = db_get().table("clients")
|
||||
def client_reset_connection_status() -> None:
|
||||
"""Reset all client connection status."""
|
||||
clients = _db_get().table("clients")
|
||||
for client in clients:
|
||||
client_set_mqtt(client["resource"], False)
|
||||
client_set_xmpp(client["resource"], False)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
"""Dns module."""
|
||||
from aiohttp import AsyncResolver
|
||||
|
||||
|
||||
def get_resolver_with_public_nameserver() -> AsyncResolver:
|
||||
"""Get resolver."""
|
||||
# requires aiodns
|
||||
return AsyncResolver(nameservers=["1.1.1.1", "8.8.8.8"])
|
||||
|
||||
|
||||
async def resolve(host: str) -> str:
|
||||
"""Resolve host."""
|
||||
hosts = await get_resolver_with_public_nameserver().resolve(host)
|
||||
return hosts[0]["host"] # type:ignore[no-any-return]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
"""Models module."""
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
|
@ -8,6 +9,8 @@ from bumper.util import convert_to_millis
|
|||
|
||||
|
||||
class VacBotDevice:
|
||||
"""Vacuum device."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
did: str = "",
|
||||
|
|
@ -27,6 +30,7 @@ class VacBotDevice:
|
|||
self.xmpp_connection = False
|
||||
|
||||
def asdict(self) -> dict[str, str | bool]:
|
||||
"""Convert to dict."""
|
||||
return {
|
||||
"class": self.vac_bot_device_class,
|
||||
"company": self.company,
|
||||
|
|
@ -40,16 +44,21 @@ class VacBotDevice:
|
|||
|
||||
|
||||
class BumperUser:
|
||||
"""Bumper user."""
|
||||
|
||||
def __init__(self, userid: str = ""):
|
||||
self.userid = userid
|
||||
self.devices: list[str] = []
|
||||
self.bots: list[str] = []
|
||||
|
||||
def asdict(self) -> dict[str, Any]:
|
||||
"""Convert to dict."""
|
||||
return {"userid": self.userid, "devices": self.devices, "bots": self.bots}
|
||||
|
||||
|
||||
class GlobalVacBotDevice(VacBotDevice): # EcoVacs Home
|
||||
class GlobalVacBotDevice(VacBotDevice):
|
||||
"""Global vacuum device."""
|
||||
|
||||
UILogicId = ""
|
||||
ota = True
|
||||
updateInfo = {"changeLog": "", "needUpdate": False}
|
||||
|
|
@ -58,6 +67,8 @@ class GlobalVacBotDevice(VacBotDevice): # EcoVacs Home
|
|||
|
||||
|
||||
class VacBotClient:
|
||||
"""Vacuum client."""
|
||||
|
||||
def __init__(self, userid: str = "", realm: str = "", token: str = ""):
|
||||
self.userid = userid
|
||||
self.realm = realm
|
||||
|
|
@ -66,6 +77,7 @@ class VacBotClient:
|
|||
self.xmpp_connection = False
|
||||
|
||||
def asdict(self) -> dict[str, Any]:
|
||||
"""Convert to dict."""
|
||||
return {
|
||||
"userid": self.userid,
|
||||
"realm": self.realm,
|
||||
|
|
@ -76,6 +88,8 @@ class VacBotClient:
|
|||
|
||||
|
||||
class EcoVacs_Login:
|
||||
"""Ecovacs login."""
|
||||
|
||||
accessToken = ""
|
||||
country = ""
|
||||
email = ""
|
||||
|
|
@ -83,16 +97,21 @@ class EcoVacs_Login:
|
|||
username = ""
|
||||
|
||||
def toJSON(self) -> str:
|
||||
"""Convert to json."""
|
||||
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=False)
|
||||
|
||||
|
||||
class EcoVacsHome_Login(EcoVacs_Login):
|
||||
"""Ecovacs home login."""
|
||||
|
||||
loginName = ""
|
||||
mobile: str | None = ""
|
||||
ucUid = ""
|
||||
|
||||
|
||||
class OAuth:
|
||||
"""Oauth."""
|
||||
|
||||
access_token = ""
|
||||
expire_at = ""
|
||||
refresh_token = ""
|
||||
|
|
@ -103,6 +122,7 @@ class OAuth:
|
|||
|
||||
@classmethod
|
||||
def create_new(cls, userId: str) -> "OAuth":
|
||||
"""Create new."""
|
||||
oauth = OAuth()
|
||||
oauth.userId = userId
|
||||
oauth.access_token = uuid.uuid4().hex
|
||||
|
|
@ -113,9 +133,11 @@ class OAuth:
|
|||
return oauth
|
||||
|
||||
def toDB(self) -> dict:
|
||||
"""Convert for db."""
|
||||
return self.__dict__
|
||||
|
||||
def toResponse(self) -> dict:
|
||||
"""Convert to response."""
|
||||
data = self.__dict__
|
||||
data["expire_at"] = convert_to_millis(
|
||||
datetime.fromisoformat(self.expire_at).timestamp()
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ class HelperBot:
|
|||
self._commands.pop(request_id, None)
|
||||
|
||||
def publish(self, topic: str, data: bytes) -> None:
|
||||
"""Publish message."""
|
||||
self._client.publish(topic, data)
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"""Mqtt proxy module."""
|
||||
import asyncio
|
||||
import re
|
||||
import ssl
|
||||
import typing
|
||||
from collections.abc import MutableMapping
|
||||
|
|
@ -52,6 +51,7 @@ class ProxyClient:
|
|||
self._port = port
|
||||
|
||||
async def connect(self, username: str, password: str) -> None:
|
||||
"""Connect."""
|
||||
try:
|
||||
await self._client.connect(
|
||||
f"mqtts://{username}:{password}@{self._host}:{self._port}"
|
||||
|
|
@ -96,12 +96,15 @@ class ProxyClient:
|
|||
)
|
||||
|
||||
async def subscribe(self, topic: str, qos: QOS_0 | QOS_1 | QOS_2 = QOS_0) -> None:
|
||||
"""Subscribe to topic."""
|
||||
await self._client.subscribe([(topic, qos)])
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
"""Disconnect."""
|
||||
await self._client.disconnect()
|
||||
|
||||
async def publish(self, topic: str, message: bytes, qos: int | None = None) -> None:
|
||||
"""Publish message."""
|
||||
await self._client.publish(topic, message, qos)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -253,6 +253,8 @@ class BumperMQTTServerPlugin:
|
|||
async def on_broker_client_subscribed(
|
||||
self, client_id: str, topic: str, qos: QOS_0 | QOS_1 | QOS_2
|
||||
) -> None:
|
||||
"""Is called when a client subscribes on the broker."""
|
||||
|
||||
if bumper.bumper_proxy_mqtt:
|
||||
# if proxy mode, also subscribe on ecovacs server
|
||||
if client_id in self._proxy_clients:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"""Util module."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
|
@ -14,6 +16,7 @@ log_to_stdout = os.environ.get("LOG_TO_STDOUT")
|
|||
|
||||
|
||||
def get_logger(name: str, rotate: RotatingFileHandler | None = None) -> logging.Logger:
|
||||
"""Get logger."""
|
||||
found_logger = __loggers.get(name)
|
||||
if found_logger:
|
||||
return found_logger
|
||||
|
|
@ -53,4 +56,5 @@ def convert_to_millis(seconds: int | float) -> int:
|
|||
|
||||
|
||||
def get_current_time_as_millis() -> int:
|
||||
"""Get current time in millis."""
|
||||
return convert_to_millis(datetime.utcnow().timestamp())
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ from aiohttp.web_response import Response
|
|||
|
||||
from bumper import db, use_auth
|
||||
from bumper.db import (
|
||||
db_get,
|
||||
_db_get,
|
||||
user_add,
|
||||
user_add_authcode,
|
||||
user_add_bot,
|
||||
user_add_device,
|
||||
user_add_token,
|
||||
user_by_deviceid,
|
||||
user_by_device_id,
|
||||
user_get,
|
||||
user_get_token,
|
||||
user_revoke_expired_tokens,
|
||||
|
|
@ -60,7 +60,7 @@ async def login(request: Request) -> Response:
|
|||
if (
|
||||
not user_devid == ""
|
||||
): # Performing basic "auth" using devid, super insecure
|
||||
user = user_by_deviceid(user_devid)
|
||||
user = user_by_device_id(user_devid)
|
||||
if user:
|
||||
if "checkLogin" in request.path:
|
||||
_check_token(
|
||||
|
|
@ -132,7 +132,7 @@ async def get_authcode(request: Request) -> Response:
|
|||
user_devid = request.query["deviceId"] # Ecovacs Home
|
||||
|
||||
if user_devid:
|
||||
user = user_by_deviceid(user_devid)
|
||||
user = user_by_device_id(user_devid)
|
||||
if user:
|
||||
if "accessToken" in request.query:
|
||||
token = user_get_token(user["userid"], request.query["accessToken"])
|
||||
|
|
@ -224,8 +224,8 @@ def _auth_any(
|
|||
try:
|
||||
user_devid = devid
|
||||
countrycode = country
|
||||
user = user_by_deviceid(user_devid)
|
||||
bots = db_get().table("bots").all()
|
||||
user = user_by_device_id(user_devid)
|
||||
bots = _db_get().table("bots").all()
|
||||
login_details: EcoVacs_Login | EcoVacsHome_Login
|
||||
|
||||
if user: # Default to user 0
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ _LOGGER = get_logger("webserver_requests")
|
|||
|
||||
|
||||
class CustomEncoder(json.JSONEncoder):
|
||||
"""Custom json encoder, which supports set."""
|
||||
|
||||
def default(self, obj: Any) -> Any:
|
||||
"""Convert objects, which are not supported by the default JSONEncoder."""
|
||||
if isinstance(obj, set):
|
||||
return list(obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
|
@ -30,6 +33,7 @@ _EXCLUDE_FROM_LOGGING = [
|
|||
|
||||
@web.middleware
|
||||
async def log_all_requests(request: Request, handler: Handler) -> StreamResponse:
|
||||
"""Middleware to log all requests."""
|
||||
if (
|
||||
not request.match_info.route.resource
|
||||
) or request.match_info.route.resource.canonical in _EXCLUDE_FROM_LOGGING:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from aiohttp.web_routedef import AbstractRouteDef
|
|||
from amqtt.session import Session
|
||||
|
||||
import bumper
|
||||
from bumper.db import db_get, token_by_authcode, user_add_oauth
|
||||
from bumper.db import _db_get, token_by_authcode, user_add_oauth
|
||||
|
||||
from .. import WebserverPlugin
|
||||
from .pim import get_product_iot_map
|
||||
|
|
@ -73,7 +73,7 @@ async def _handle_appsvr_app(request: Request) -> Response:
|
|||
todo = postbody["todo"]
|
||||
|
||||
if todo == "GetGlobalDeviceList":
|
||||
bots = db_get().table("bots").all()
|
||||
bots = _db_get().table("bots").all()
|
||||
devices = []
|
||||
for bot in bots:
|
||||
if bot["class"] != "":
|
||||
|
|
|
|||
|
|
@ -10,7 +10,13 @@ from aiohttp.web_response import Response
|
|||
from aiohttp.web_routedef import AbstractRouteDef
|
||||
|
||||
from bumper import bumper_announce_ip
|
||||
from bumper.db import bot_remove, bot_set_nick, check_authcode, db_get, loginByItToken
|
||||
from bumper.db import (
|
||||
_db_get,
|
||||
bot_remove,
|
||||
bot_set_nick,
|
||||
check_authcode,
|
||||
login_by_it_token,
|
||||
)
|
||||
|
||||
from .. import WebserverPlugin
|
||||
|
||||
|
|
@ -81,7 +87,7 @@ async def _handle_usersapi(request: Request) -> Response:
|
|||
"userId": postbody["userId"],
|
||||
}
|
||||
else: # EcoVacs Home LoginByITToken
|
||||
login_token = loginByItToken(postbody["token"])
|
||||
login_token = login_by_it_token(postbody["token"])
|
||||
if login_token:
|
||||
body = {
|
||||
"resource": postbody["resource"],
|
||||
|
|
@ -95,7 +101,7 @@ async def _handle_usersapi(request: Request) -> Response:
|
|||
|
||||
elif todo == "GetDeviceList":
|
||||
body = {
|
||||
"devices": db_get().table("bots").all(),
|
||||
"devices": _db_get().table("bots").all(),
|
||||
"result": "ok",
|
||||
"todo": "result",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from aiohttp.web_request import Request
|
|||
from aiohttp.web_response import Response
|
||||
from aiohttp.web_routedef import AbstractRouteDef
|
||||
|
||||
from bumper.db import check_token, user_by_deviceid, user_revoke_token
|
||||
from bumper.db import check_token, user_by_device_id, user_revoke_token
|
||||
from bumper.web import auth_util
|
||||
|
||||
from ... import WebserverPlugin, get_success_response
|
||||
|
|
@ -84,7 +84,7 @@ async def _logout(request: Request) -> Response:
|
|||
try:
|
||||
user_device_id = request.match_info.get("devid", None)
|
||||
if user_device_id:
|
||||
user = user_by_deviceid(user_device_id)
|
||||
user = user_by_device_id(user_device_id)
|
||||
if user:
|
||||
if check_token(user["userid"], request.query["accessToken"]):
|
||||
# Deactivate old tokens and authcodes
|
||||
|
|
@ -101,7 +101,7 @@ async def _logout(request: Request) -> Response:
|
|||
async def _get_user_account_info(request: Request) -> Response:
|
||||
try:
|
||||
user_devid = request.match_info.get("devid", "")
|
||||
user = user_by_deviceid(user_devid)
|
||||
user = user_by_device_id(user_devid)
|
||||
if user:
|
||||
username = f"fusername_{user['userid']}"
|
||||
return get_success_response(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from aiohttp.web_request import Request
|
|||
from aiohttp.web_response import Response
|
||||
|
||||
import bumper
|
||||
from bumper.db import bot_get, bot_remove, client_get, client_remove, db_get
|
||||
from bumper.db import _db_get, bot_get, bot_remove, client_get, client_remove
|
||||
from bumper.dns import get_resolver_with_public_nameserver
|
||||
from bumper.util import get_logger
|
||||
from bumper.web.middlewares import log_all_requests
|
||||
|
|
@ -148,8 +148,8 @@ class WebServer:
|
|||
|
||||
async def _handle_base(self, request: Request) -> Response:
|
||||
try:
|
||||
bots = db_get().table("bots").all()
|
||||
clients = db_get().table("clients").all()
|
||||
bots = _db_get().table("bots").all()
|
||||
clients = _db_get().table("clients").all()
|
||||
mq_sessions = []
|
||||
for (session, _) in bumper.mqtt_server.broker._sessions.values():
|
||||
mq_sessions.append(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
"""XMPP module."""
|
||||
import asyncio
|
||||
import base64
|
||||
import re
|
||||
|
|
@ -23,6 +24,8 @@ boterrorlog = bumper.get_logger("boterror")
|
|||
|
||||
|
||||
class XMPPServer:
|
||||
"""XMPP server."""
|
||||
|
||||
server_id = "ecouser.net"
|
||||
clients: list["XMPPAsyncClient"] = []
|
||||
exit_flag = False
|
||||
|
|
@ -35,6 +38,7 @@ class XMPPServer:
|
|||
self.xmpp_protocol = lambda: XMPPServer_Protocol()
|
||||
|
||||
async def start_async_server(self) -> None:
|
||||
"""Start server."""
|
||||
try:
|
||||
xmppserverlog.info(f"Starting XMPP Server at {self._host}:{self._port}")
|
||||
|
||||
|
|
@ -51,7 +55,7 @@ class XMPPServer:
|
|||
raise e
|
||||
|
||||
def disconnect(self) -> None:
|
||||
|
||||
"""Disconnect."""
|
||||
xmppserverlog.debug("waiting for all clients to disconnect")
|
||||
for client in self.clients:
|
||||
client._disconnect()
|
||||
|
|
@ -62,11 +66,14 @@ class XMPPServer:
|
|||
|
||||
|
||||
class XMPPServer_Protocol(asyncio.Protocol):
|
||||
"""XMPP server protocol."""
|
||||
|
||||
client_id = None
|
||||
exit_flag = False
|
||||
_client: Optional["XMPPAsyncClient"] = None
|
||||
|
||||
def connection_made(self, transport: transports.BaseTransport) -> None:
|
||||
"""Establish connection."""
|
||||
if self._client: # Existing client... upgrading to TLS
|
||||
xmppserverlog.debug(f"Upgraded connection for {self._client.address}")
|
||||
self._client.transport = transport
|
||||
|
|
@ -78,6 +85,7 @@ class XMPPServer_Protocol(asyncio.Protocol):
|
|||
xmppserverlog.debug(f"New Connection from {client.address}")
|
||||
|
||||
def connection_lost(self, exc: Exception | None) -> None:
|
||||
"""Lost connection."""
|
||||
if self._client:
|
||||
XMPPServer.clients.remove(self._client)
|
||||
self._client.set_state("DISCONNECT")
|
||||
|
|
@ -90,11 +98,14 @@ class XMPPServer_Protocol(asyncio.Protocol):
|
|||
)
|
||||
|
||||
def data_received(self, data: bytes) -> None:
|
||||
"""Parse received data."""
|
||||
if self._client:
|
||||
self._client.parse_data(data)
|
||||
|
||||
|
||||
class XMPPAsyncClient:
|
||||
"""XMPP client."""
|
||||
|
||||
IDLE = 0
|
||||
CONNECT = 1
|
||||
INIT = 2
|
||||
|
|
@ -120,6 +131,7 @@ class XMPPAsyncClient:
|
|||
xmppserverlog.debug(f"new client with ip {self.address}")
|
||||
|
||||
def send(self, command: str) -> None:
|
||||
"""Send command."""
|
||||
try:
|
||||
if self.log_sent_message:
|
||||
xmppserverlog.debug(
|
||||
|
|
@ -158,6 +170,7 @@ class XMPPAsyncClient:
|
|||
return tag
|
||||
|
||||
def set_state(self, state: str) -> None:
|
||||
"""Set state."""
|
||||
try:
|
||||
new_state = getattr(XMPPAsyncClient, state)
|
||||
if self.state > new_state:
|
||||
|
|
@ -240,7 +253,7 @@ class XMPPAsyncClient:
|
|||
and client.state == client.READY
|
||||
):
|
||||
ctl_to = xml.get("to")
|
||||
if not "from" in xml.attrib:
|
||||
if "from" not in xml.attrib:
|
||||
xml.attrib["from"] = f"{self.bumper_jid}"
|
||||
rxmlstring = ET.tostring(xml).decode("utf-8")
|
||||
# clean up string to remove namespaces added by ET
|
||||
|
|
@ -270,7 +283,7 @@ class XMPPAsyncClient:
|
|||
|
||||
else:
|
||||
pingfrom = self.bumper_jid
|
||||
if not "from" in xml.attrib:
|
||||
if "from" not in xml.attrib:
|
||||
xml.attrib["from"] = f"{pingfrom}"
|
||||
pingstring = ET.tostring(xml).decode("utf-8")
|
||||
# clean up string to remove namespaces added by ET
|
||||
|
|
@ -292,6 +305,7 @@ class XMPPAsyncClient:
|
|||
xmppserverlog.exception(f"{e}")
|
||||
|
||||
async def schedule_ping(self, time: int) -> None:
|
||||
"""Schedule ping."""
|
||||
if not self.state == 5: # disconnected
|
||||
pingstring = "<iq from='{}' to='{}' id='s2c1' type='get'><ping xmlns='urn:xmpp:ping'/></iq>".format(
|
||||
XMPPServer.server_id, self.bumper_jid
|
||||
|
|
@ -303,7 +317,7 @@ class XMPPAsyncClient:
|
|||
def _handle_result(self, xml: ET.Element, data: str) -> None:
|
||||
try:
|
||||
ctl_to = xml.get("to")
|
||||
if not "from" in xml.attrib:
|
||||
if "from" not in xml.attrib:
|
||||
xml.attrib["from"] = f"{self.bumper_jid}"
|
||||
if "errno" in data:
|
||||
xmppserverlog.error(f"Error from bot - {data}")
|
||||
|
|
@ -383,7 +397,7 @@ class XMPPAsyncClient:
|
|||
client.bumper_jid != self.bumper_jid
|
||||
and client.state == client.READY
|
||||
):
|
||||
if not "@" in ctl_to: # No user@, send to all clients?
|
||||
if "@" not in ctl_to: # No user@, send to all clients?
|
||||
# TODO: Revisit later, this may be wrong
|
||||
client.send(rxmlstring)
|
||||
|
||||
|
|
@ -684,6 +698,7 @@ class XMPPAsyncClient:
|
|||
self.send(f'<presence to="{self.bumper_jid}"> dummy </presence>')
|
||||
|
||||
def parse_data(self, data: bytes) -> None:
|
||||
"""Parse data."""
|
||||
|
||||
if data.decode("utf-8").startswith(
|
||||
"<?xml"
|
||||
|
|
@ -770,7 +785,7 @@ class XMPPAsyncClient:
|
|||
|
||||
elif "not well-formed (invalid token)" in e.msg:
|
||||
# If a lone </stream:stream> - client is signalling end of session/disconnect
|
||||
if not "</stream:stream>" in newdata:
|
||||
if "</stream:stream>" not in newdata:
|
||||
xmppserverlog.error(f"xml parse error - {newdata} - {e}")
|
||||
else:
|
||||
self.send("</stream:stream>") # Close stream
|
||||
|
|
@ -781,7 +796,7 @@ class XMPPAsyncClient:
|
|||
xmppserverlog.debug(f"Handling connect data - {newdata}")
|
||||
self._handle_connect(newdata.encode("utf-8"))
|
||||
else:
|
||||
if not "</stream:stream>" in newdata:
|
||||
if "</stream:stream>" not in newdata:
|
||||
xmppserverlog.error(f"xml parse error - {newdata} - {e}")
|
||||
else:
|
||||
self.send("</stream:stream>") # Close stream
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ def test_db_path():
|
|||
env = os.environ.copy()
|
||||
env.pop("DB_FILE")
|
||||
with mock.patch.dict(os.environ, env, clear=True):
|
||||
assert db.db_file() == os.path.join(data_dir, "bumper.db")
|
||||
assert db._db_file() == os.path.join(data_dir, "bumper.db")
|
||||
|
||||
|
||||
def test_user_db():
|
||||
|
|
@ -24,7 +24,7 @@ def test_user_db():
|
|||
db.user_add_device("testuser", "dev_1234") # Add device to testuser
|
||||
|
||||
assert (
|
||||
db.user_by_deviceid("dev_1234")["userid"] == "testuser"
|
||||
db.user_by_device_id("dev_1234")["userid"] == "testuser"
|
||||
) # Test that testuser was found by deviceid
|
||||
|
||||
db.user_remove_device("testuser", "dev_1234") # Remove device from testuser
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue