diff --git a/.dockerignore b/.dockerignore index f4a3930..a6766a5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,20 +1,3 @@ -# Logs -logs - -# Tests -tests - -# Examples -examples - -# Docs -docs - -# Certs -certs - -.coverage - # Markdown *.md diff --git a/Dockerfile b/Dockerfile index dde7ee9..912db45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,11 +22,15 @@ RUN apk add build-base FROM base -COPY . /bumper - -WORKDIR /bumper +COPY requirements.txt /requirements.txt # install required python packages RUN pip3 install -r requirements.txt +WORKDIR /bumper + +# Copy only required folders instead of all +COPY create_certs/ create_certs/ +COPY bumper/ bumper/ + ENTRYPOINT ["python3", "-m", "bumper"] diff --git a/bumper/__init__.py b/bumper/__init__.py index ccce858..dc90528 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -57,6 +57,7 @@ def strtobool(strbool): bumper_debug = strtobool(os.environ.get("BUMPER_DEBUG")) or False use_auth = False token_validity_seconds = 3600 # 1 hour +oauth_validity_days = 15 db = None mqtt_server = None @@ -236,7 +237,8 @@ async def start(): xmpp_server = XMPPServer((bumper_listen, xmpp_listen_port)) # Start MQTT Server - asyncio.create_task(mqtt_server.broker_coro()) + # await start otherwise we get an error connecting the helper bot + await asyncio.create_task(mqtt_server.broker_coro()) # Start MQTT Helperbot asyncio.create_task(mqtt_helperbot.start_helper_bot()) @@ -264,6 +266,7 @@ async def start(): async def maintenance(): revoke_expired_tokens() + revoke_expired_oauths() async def shutdown(): diff --git a/bumper/confserver.py b/bumper/confserver.py index 8b9cec2..65e0143 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -1,21 +1,16 @@ #!/usr/bin/env python3 -import json +import asyncio import logging -import ssl -import string -import random -import bumper import os -from bumper.models import * -from bumper import plugins -from datetime import datetime, timedelta -import asyncio -from aiohttp import web +import ssl + import aiohttp_jinja2 import jinja2 -import uuid -import xml.etree.ElementTree as ET +from aiohttp import web + +from bumper import plugins +from bumper.models import * class aiohttp_filter(logging.Filter): @@ -62,13 +57,12 @@ def confserver_app(self): self.app.add_routes( [ - - web.get("", self.handle_base, name="base"), + web.get("", self.handle_base, name="base"), web.get("/bot/remove/{did}", self.handle_RemoveBot, name='remove-bot'), web.get("/client/remove/{resource}", self.handle_RemoveClient, name='remove-client'), web.get("/restart_{service}", self.handle_RestartService, name='restart-service'), web.post("/lookup.do", self.handle_lookup), - + web.post("/newauth.do", self.handle_newauth), ] ) @@ -217,8 +211,18 @@ async def handle_base(self, request): async def log_all_requests(self, request, handler): if request._match_info.route.name not in self.excludelogging: - + to_log = { + "request": { + "route_name": f"{request.match_info.route.name}", + "method": f"{request.method}", + "path": f"{request.path}", + "query_string": f"{request.query_string}", + "raw_path": f"{request.raw_path}", + "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', + } + } try: + postbody = None if request.content_length: if request.content_type == "application/x-www-form-urlencoded": postbody = await request.post() @@ -232,78 +236,33 @@ async def log_all_requests(self, request, handler): else: postbody = await request.post() - else: - postbody = None + + to_log["request"]["body"] = f"{postbody}" response = await handler(request) + if response is None: + confserverlog.warning("Response was null!") + confserverlog.warning(json.dumps(to_log)) + return response + + to_log["response"] = { + "status": f"{response.status}", + } if not "application/octet-stream" in response.content_type: - logall = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - }, - - "response": { - "response_body": f"{json.loads(response.body)}", - "status": f"{response.status}", - } - } - else: - logall = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - }, - - "response": { - "status": f"{response.status}", - } - } + to_log["response"]["body"] = f"{json.loads(response.body)}" - confserverlog.debug(json.dumps(logall)) + confserverlog.debug(json.dumps(to_log)) return response except web.HTTPNotFound as notfound: confserverlog.debug("Request path {} not found".format(request.raw_path)) - requestlog = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - } - } - confserverlog.debug(json.dumps(requestlog)) + confserverlog.debug(json.dumps(to_log)) return notfound except Exception as e: - confserverlog.exception("{}".format(e)) - requestlog = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - } - } - confserverlog.debug(json.dumps(requestlog)) + confserverlog.exception("{}".format(e)) + confserverlog.error(json.dumps(to_log)) return e else: @@ -507,6 +466,26 @@ async def handle_lookup(self, request): except Exception as e: confserverlog.exception("{}".format(e)) + async def handle_newauth(self, request): + # Bumper is only returning the submitted token. No reason yet to create another new token + try: + if request.content_type == "application/x-www-form-urlencoded": + postbody = await request.post() + else: + postbody = json.loads(await request.text()) + + confserverlog.debug(postbody) + + body = { + "authCode": postbody["itToken"], + "result": "ok", + "todo": "result" + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) async def disconnect(self): try: diff --git a/bumper/db.py b/bumper/db.py index 71c158a..304add3 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 -import bumper -from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts -from tinydb import TinyDB, Query -from tinydb.storages import MemoryStorage -from datetime import datetime, timedelta -import os import json import logging +import os +from datetime import datetime, timedelta +from tinydb import TinyDB, Query + +import bumper +from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts, OAuth bumperlog = logging.getLogger("bumper") @@ -32,6 +32,7 @@ def db_get(): db.table("clients", cache_size=0) db.table("bots", cache_size=0) db.table("tokens", cache_size=0) + db.table("oauth", cache_size=0) return db @@ -202,6 +203,55 @@ def user_revoke_authcode(userid, token, authcode): ) +def revoke_expired_oauths(): + opendb = db_get() + with opendb: + table = opendb.table("oauth") + entries = table.all() + + for i in entries: + oauth = OAuth(**i) + if datetime.now() >= datetime.fromisoformat(oauth.expire_at): + bumperlog.debug( + "Removing oauth {} due to expiration".format(oauth.access_token) + ) + table.remove(doc_ids=[i.doc_id]) + + +def user_revoke_expired_oauths(userid): + opendb = db_get() + with opendb: + table = opendb.table("oauth") + search = table.search(Query().userid == userid) + for i in search: + oauth = OAuth(**i) + if datetime.now() >= datetime.fromisoformat(oauth.expire_at): + bumperlog.debug( + "Removing oauth {} due to expiration".format(oauth.access_token) + ) + table.remove(doc_ids=[i.doc_id]) + + +def user_add_oauth(userid) -> OAuth: + user_revoke_expired_oauths(userid) + opendb = db_get() + with opendb: + table = opendb.table("oauth") + entry = table.get(Query().userid == userid) + if entry: + return OAuth(**entry) + else: + oauth = OAuth.create_new(userid) + bumperlog.debug("Adding oauth {} for userid {}".format(oauth.access_token, userid)) + table.insert(oauth.toDB()) + return oauth + + +def token_by_authcode(authcode): + tokens = db_get().table("tokens") + return tokens.get(Query().authcode == authcode) + + def get_disconnected_xmpp_clients(): clients = db_get().table("clients") Client = Query() @@ -214,8 +264,8 @@ def check_authcode(uid, authcode): tmpauth = tokens.get( (Query().authcode == authcode) & ( # Match authcode - (Query().userid == uid.replace("fuid_", "")) - | (Query().userid == "fuid_{}".format(uid)) + (Query().userid == uid.replace("fuid_", "")) + | (Query().userid == "fuid_{}".format(uid)) ) # Userid with or without fuid_ ) if tmpauth: @@ -246,8 +296,8 @@ def check_token(uid, token): tmpauth = tokens.get( (Query().token == token) & ( # Match token - (Query().userid == uid.replace("fuid_", "")) - | (Query().userid == "fuid_{}".format(uid)) + (Query().userid == uid.replace("fuid_", "")) + | (Query().userid == "fuid_{}".format(uid)) ) # Userid with or without fuid_ ) if tmpauth: @@ -275,7 +325,7 @@ def bot_add(sn, did, devclass, resource, 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 devclass == "" or "@" not in sn or "tmp" not in sn ): # try to prevent bad additions to the bot list bumperlog.info( "Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did) @@ -302,6 +352,21 @@ def bot_toEcoVacsHome_JSON(bot): # EcoVacs Home bot["UILogicId"] = botprod["product"]["UILogicId"] bot["ota"] = botprod["product"]["ota"] bot["icon"] = botprod["product"]["iconUrl"] + bot["model"] = botprod["product"]["model"] + bot["pip"] = botprod["product"]["_id"] + bot["deviceName"] = botprod["product"]["name"] + bot["materialNo"] = botprod["product"]["materialNo"] + bot["product_category"] = "DEEBOT" if botprod["product"]["name"].startswith("DEEBOT") else "UNKNOWN" + # bot["updateInfo"] = { + # "changeLog": "", + # "needUpdate": False + # } + # bot["service"] = { + # "jmq": "jmq-ngiot-eu.dc.ww.ecouser.net", + # "mqs": "api-ngiot.dc-as.ww.ecouser.net" + # } + bot["status"] = 1 if bot["mqtt_connection"] or bot["xmpp_connection"] else 0 + return json.dumps( bot, default=lambda o: o.__dict__, sort_keys=False ) # , indent=4) @@ -345,12 +410,14 @@ def client_add(userid, realm, resource): bumperlog.info("Adding new client with resource {}".format(newclient.resource)) client_full_upsert(newclient.asdict()) + def client_remove(resource): clients = db_get().table("clients") client = client_get(resource) if client: clients.remove(doc_ids=[client.doc_id]) + def client_get(resource): clients = db_get().table("clients") Client = Query() diff --git a/bumper/models.py b/bumper/models.py index 7177596..e547926 100644 --- a/bumper/models.py +++ b/bumper/models.py @@ -1,10 +1,14 @@ #!/usr/bin/env python3 import json +import uuid +from datetime import datetime, timedelta + +import bumper class VacBotDevice(object): def __init__( - self, did="", vac_bot_device_class="", resource="", name="", nick="", company="" + self, did="", vac_bot_device_class="", resource="", name="", nick="", company="" ): self.vac_bot_device_class = vac_bot_device_class self.company = company @@ -83,714 +87,1654 @@ class EcoVacsHome_Login(EcoVacs_Login): ucUid = "" -# EcoVacs Home Product IOT Map - 2020-01-05 +class OAuth: + access_token = "" + expire_at = "" + refresh_token = "" + userId = "" + + def __init__(self, **entries): + self.__dict__.update(entries) + + @classmethod + def create_new(cls, userId: str): + oauth = OAuth() + oauth.userId = userId + oauth.access_token = uuid.uuid4().hex + oauth.expire_at = "{}".format(datetime.utcnow() + timedelta(days=bumper.oauth_validity_days)) + oauth.refresh_token = uuid.uuid4().hex + return oauth + + def toDB(self): + return self.__dict__ + + def toResponse(self): + data = self.__dict__ + data["expire_at"] = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time( + datetime.fromisoformat(self.expire_at).timestamp()) + return data + + +# EcoVacs Home Product IOT Map - 2021-04-15 # https://portal-ww.ecouser.net/api/pim/product/getProductIotMap EcoVacsHomeProducts = [ - { - "classid": "dl8fht", - "product": { - "_id": "5acb0fa87c295c0001876ecf", - "materialNo": "702-0000-0170", - "name": "DEEBOT 600 Series", - "icon": "5acc32067c295c0001876eea", - "UILogicId": "D_600", - "ota": False, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea" - } - }, - { - "classid": "02uwxm", - "product": { - "_id": "5ae1481e7ccd1a0001e1f69e", - "materialNo": "110-1715-0201", - "name": "DEEBOT OZMO Slim10 Series", - "icon": "5b1dddc48bc45700014035a1", - "UILogicId": "D_OZMO_SLIM10", - "ota": False, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1" - } - }, - { - "classid": "y79a7u", - "product": { - "_id": "5b04c0227ccd1a0001e1f6a8", - "materialNo": "110-1810-0101", - "name": "DEEBOT OZMO 900 Series", - "icon": "5b04c0217ccd1a0001e1f6a7", - "UILogicId": "D_OZMO_900", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7" - } - }, - { - "classid": "jr3pqa", - "product": { - "_id": "5b43077b8bc457000140363e", - "materialNo": "702-0000-0202", - "name": "DEEBOT 711", - "icon": "5b5ac4cc8d5a56000111e769", - "UILogicId": "D_700", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769" - } - }, - { - "classid": "uv242z", - "product": { - "_id": "5b5149b4ac0b87000148c128", - "materialNo": "702-0000-0205", - "name": "DEEBOT 710", - "icon": "5b5ac4e45f21100001882bb9", - "UILogicId": "D_700", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9" - } - }, - { - "classid": "ls1ok3", - "product": { - "_id": "5b6561060506b100015c8868", - "materialNo": "110-1711-0201", - "name": "DEEBOT 900 Series", - "icon": "5ba4a2cb6c2f120001c32839", - "UILogicId": "D_900", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839" - } - }, - { - "classid": "eyi9jv", - "product": { - "_id": "5b7b65f364e1680001a08b54", - "materialNo": "702-0000-0202", - "name": "DEEBOT 715", - "icon": "5b7b65f176f7f10001e9a0c2", - "UILogicId": "D_700", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7b65f176f7f10001e9a0c2" - } - }, - { - "classid": "4zfacv", - "product": { - "_id": "5bf2596f23244a00013f2f13", - "materialNo": "910", - "name": "DEEBOT 910", - "icon": "5c778731280fda0001770ba0", - "UILogicId": "DN_2G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c778731280fda0001770ba0" - } - }, - { - "classid": "115", - "product": { - "_id": "5bbedd2822d57f00018c13b7", - "materialNo": "110-1602-0101", - "name": "DEEBOT OZMO/PRO 930 Series", - "icon": "5cf711aeb0acfc000179ff8a", - "UILogicId": "DR_930G", - "ota": True, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cf711aeb0acfc000179ff8a" - } - }, - { - "classid": "vi829v", - "product": { - "_id": "5c19a8f3a1e6ee0001782247", - "materialNo": "110-1819-0101", - "name": "DEEBOT OZMO 920 Series", - "icon": "5c9c7995e9e9270001354ab4", - "UILogicId": "DX_5G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c9c7995e9e9270001354ab4" - } - }, - { - "classid": "yna5xi", - "product": { - "_id": "5c19a91ca1e6ee000178224a", - "materialNo": "110-1820-0101", - "name": "DEEBOT OZMO 950 Series", - "icon": "5caafd7e1285190001685965", - "UILogicId": "DX_9G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5caafd7e1285190001685965" - } - }, - { - "classid": "gd4uut", - "product": { - "_id": "5bc8189d68142800016a6937", - "materialNo": "110-1803-0101", - "name": "DEEBOT OZMO 960", - "icon": "5c7384767b93c700013f12e7", - "UILogicId": "DR_935G", - "ota": True, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c7384767b93c700013f12e7" - } - }, - { - "classid": "m7lqzi", - "product": { - "_id": "5c653edf7b93c700013f12cc", - "materialNo": "113-1708-0001", - "name": "ATMOBOT Pro", - "icon": "5d2c63a5ba13eb00013feab7", - "UILogicId": "AA_30G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": False, - "alexa": False - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c63a5ba13eb00013feab7" - } - }, - { - "classid": "9akc61", - "product": { - "_id": "5c763f8263023c0001e7f855", - "materialNo": "702-0000-0163", - "name": "DEEBOT 505", - "icon": "5c932067280fda0001770d7f", - "UILogicId": "D_500", - "ota": False, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c932067280fda0001770d7f" - } - }, - { - "classid": "r8ead0", - "product": { - "_id": "5c763f63280fda0001770b88", - "materialNo": "702-0000-0163", - "name": "DEEBOT 502", - "icon": "5c93204b63023c0001e7faa7", - "UILogicId": "D_500", - "ota": False, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c93204b63023c0001e7faa7" - } - }, - { - "classid": "emzppx", - "product": { - "_id": "5c763f35280fda0001770b84", - "materialNo": "702-0000-0163", - "name": "DEEBOT 501", - "icon": "5c931fef280fda0001770d7e", - "UILogicId": "D_500", - "ota": False, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c931fef280fda0001770d7e" - } - }, - { - "classid": "vsc5ia", - "product": { - "_id": "5c763eba280fda0001770b81", - "materialNo": "702-0000-0163", - "name": "DEEBOT 500", - "icon": "5c874326280fda0001770d2a", - "UILogicId": "D_500", - "ota": False, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c874326280fda0001770d2a" - } - }, - { - "classid": "142", - "product": { - "_id": "5ca1ca7a12851900016858bd", - "materialNo": "110-1640-0101", - "name": "DEEBOT Mini2", - "icon": "5ca1ca79e9e9270001354b2d", - "UILogicId": "ECO_INTL_142", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca1ca79e9e9270001354b2d" - } - }, - { - "classid": "129", - "product": { - "_id": "5ca31df1e9e9270001354b35", - "materialNo": "110-1628-0101", - "name": "DEEBOT M86", - "icon": "5ca31df112851900016858c0", - "UILogicId": "ECO_INTL_129", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca31df112851900016858c0" - } - }, - { - "classid": "165", - "product": { - "_id": "5ca32a11e9e9270001354b39", - "materialNo": "702-0000-0189", - "name": "DEEBOT N79T/W", - "icon": "5ca32a1012851900016858c6", - "UILogicId": "ECO_INTL_165", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32a1012851900016858c6" - } - }, - { - "classid": "126", - "product": { - "_id": "5ca32ab212851900016858c7", - "materialNo": "702-0000-0136", - "name": "DEEBOT N79", - "icon": "5ca32ab2e9e9270001354b3d", - "UILogicId": "ECO_INTL_126", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32ab2e9e9270001354b3d" - } - }, - { - "classid": "159", - "product": { - "_id": "5ca32bc2e9e9270001354b41", - "materialNo": "110-1629-0203", - "name": "DEEBOT OZMO 601", - "icon": "5d4b7606de51dd0001fee12d", - "UILogicId": "ECO_INTL_159", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7606de51dd0001fee12d" - } - }, - { - "classid": "0xyhhr", - "product": { - "_id": "5ca4716312851900016858cd", - "materialNo": "110-1825-0201", - "name": "DEEBOT OZMO 700", - "icon": "5d117d4f0ac6ad00012b792d", - "UILogicId": "DV_5G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d117d4f0ac6ad00012b792d" - } - }, - { - "classid": "125", - "product": { - "_id": "5cae9703128519000168596a", - "materialNo": "110-1638-0102", - "name": "DEEBOT M80 Pro", - "icon": "5d2c14414d60de0001eaf1f2", - "UILogicId": "ECO_INTL_125", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c14414d60de0001eaf1f2" - } - }, - { - "classid": "141", - "product": { - "_id": "5cae97c9128519000168596f", - "materialNo": "110-1638-0101", - "name": "DEEBOT M81 Pro", - "icon": "5d2c2aa64d60de0001eaf1f6", - "UILogicId": "ECO_INTL_141", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c2aa64d60de0001eaf1f6" - } - }, - { - "classid": "130", - "product": { - "_id": "5cae98d01285190001685974", - "materialNo": "110-1629-0201", - "name": "DEEBOT OZMO 610 Series", - "icon": "5d4b7640de51dd0001fee131", - "UILogicId": "ECO_INTL_130", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7640de51dd0001fee131" - } - }, - { - "classid": "123", - "product": { - "_id": "5cae9b201285190001685977", - "materialNo": "110-1639-0102", - "name": "DEEBOT Slim2 Series", - "icon": "5d2c150dba13eb00013feaae", - "UILogicId": "ECO_INTL_123", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c150dba13eb00013feaae" - } - }, - { - "classid": "aqdd5p", - "product": { - "_id": "5cb7cfba179839000114d762", - "materialNo": "110-1711-0001", - "name": "DEEBOT DE55", - "icon": "5cb7cfbab72c4d00010e5fc7", - "UILogicId": "D_900", - "ota": True, - "supportType": { - "share": False, - "tmjl": False, - "assistant": False, - "alexa": False - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cb7cfbab72c4d00010e5fc7" - } - }, - { - "classid": "152", - "product": { - "_id": "5cbd97b961526a00019799bd", - "materialNo": "110-1628-0302", - "name": "DEEBOT", - "icon": "5d4b7628de51dd0001fee12f", - "UILogicId": "ECO_INTL_152", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7628de51dd0001fee12f" - } - }, - { - "classid": "155", - "product": { - "_id": "5cce893813afb7000195d6af", - "materialNo": "702-0000-0163", - "name": "DEEBOT N79S/SE", - "icon": "5cd4ca505b032200015a455d", - "UILogicId": "ECO_INTL_155", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cd4ca505b032200015a455d" - } - }, - { - "classid": "jjccwk", - "product": { - "_id": "5ce7870cd85b4d0001775db9", - "materialNo": "110-1825-0201", - "name": "DEEBOT OZMO 750", - "icon": "5d3aa309ba13eb00013feb69", - "UILogicId": "DV_6G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d3aa309ba13eb00013feb69" - } - }, - { - "classid": "d0cnel", - "product": { - "_id": "5ceba1c6d85b4d0001776986", - "materialNo": "702-0000-0202", - "name": "DEEBOT 711s", - "icon": "5d157f9f77a3a60001051f69", - "UILogicId": "D_700", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d157f9f77a3a60001051f69" - } - }, - { - "classid": "140", - "product": { - "_id": "5cd43b4cf542e00001dc2dec", - "materialNo": "110-1639-0011", - "name": "DEEBOT Slim Neo", - "icon": "5d2c152f4d60de0001eaf1f4", - "UILogicId": "ECO_INTL_140", - "ota": False, - "supportType": { - "share": False, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c152f4d60de0001eaf1f4" - } - }, - { - "classid": "2pv572", - "product": { - "_id": "5d1474630ac6ad00012b7940", - "materialNo": "110-1810-0107", - "name": "DEEBOT OZMO 905", - "icon": "5d1474632a6bd50001b5b6f3", - "UILogicId": "D_OZMO_900", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d1474632a6bd50001b5b6f3" - } - }, - { - "classid": "xb83mv", - "product": { - "_id": "5d246180350e7a0001e84bea", - "materialNo": "88393939393", - "name": "DEEBOT U3", - "icon": "5d3fe649de51dd0001fee0de", - "UILogicId": "DK_4G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d3fe649de51dd0001fee0de" - } - }, - { - "classid": "4f0c4e", - "product": { - "_id": "5d2c5fcd4d60de0001eaf2a5", - "materialNo": "70200000227", - "name": "AT01", - "icon": "5d2d996c4d60de0001eaf2b5", - "UILogicId": "AT_01G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": False, - "alexa": False - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2d996c4d60de0001eaf2b5" - } - }, - { - "classid": "q1v5dn", - "product": { - "_id": "5d312ae18d8d430001817002", - "materialNo": "70200000228", - "name": "AT01", - "icon": "5d83375f6b6a570001569e26", - "UILogicId": "AT_01G", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": False, - "alexa": False - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d83375f6b6a570001569e26" - } - }, - { - "classid": "16wdph", - "product": { - "_id": "5d280ce344af3600013839ab", - "materialNo": "702-0000-0170", - "name": "DEEBOT 661", - "icon": "5d280ce3350e7a0001e84c95", - "UILogicId": "D_661", - "ota": False, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d280ce3350e7a0001e84c95" - } - }, - { - "classid": "zi1uwd", - "product": { - "_id": "5d78f4e878d8b60001e23edc", - "materialNo": "3", - "name": "DEEBOT U3 LINE FRIENDS", - "icon": "5da834a8d66cd10001f58265", - "UILogicId": "DK_4GLINE", - "ota": True, - "supportType": { - "share": True, - "tmjl": False, - "assistant": True, - "alexa": True - }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5da834a8d66cd10001f58265" + { + "classid": "4f0c4e", + "product": { + "_id": "5d2c5fcd4d60de0001eaf2a5", + "materialNo": "70200000227", + "name": "AT01", + "icon": "5d2d996c4d60de0001eaf2b5", + "model": "AT01", + "UILogicId": "AT_01G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2d996c4d60de0001eaf2b5" + } + }, + { + "classid": "vsc5ia", + "product": { + "_id": "5c763eba280fda0001770b81", + "materialNo": "702-0000-0163", + "name": "DEEBOT 500", + "icon": "5c874326280fda0001770d2a", + "model": "D500", + "UILogicId": "D_500", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c874326280fda0001770d2a" + } + }, + { + "classid": "zi1uwd", + "product": { + "_id": "5d78f4e878d8b60001e23edc", + "materialNo": "3", + "name": "DEEBOT U3 LINE FRIENDS", + "icon": "5da834a8d66cd10001f58265", + "model": "DK4G.11", + "UILogicId": "DK_4GLINE", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5da834a8d66cd10001f58265" + } + }, + { + "classid": "12baap", + "product": { + "_id": "5ea8d1fe73193e3bef67c551", + "materialNo": "110-1919-1801", + "name": "DEEBOT U2 PRO", + "icon": "606425981269020008a9627b", + "model": "U2_APAC2", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425981269020008a9627b" + } + }, + { + "classid": "02uwxm", + "product": { + "_id": "5ae1481e7ccd1a0001e1f69e", + "materialNo": "110-1715-0201", + "name": "DEEBOT OZMO Slim10 Series", + "icon": "5b1dddc48bc45700014035a1", + "model": "SLIM10", + "UILogicId": "D_OZMO_SLIM10", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1" + } + }, + { + "classid": "eyi9jv", + "product": { + "_id": "5b7b65f364e1680001a08b54", + "materialNo": "702-0000-0203", + "name": "DEEBOT 715", + "icon": "5b7b65f176f7f10001e9a0c2", + "model": "DV3G", + "UILogicId": "D_700", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7b65f176f7f10001e9a0c2" + } + }, + { + "classid": "9rft3c", + "product": { + "_id": "5e14196a6e71b80001b60fda", + "materialNo": "191165", + "name": "DEEBOT OZMO T5", + "icon": "6062795ad18cbd0008e2fce8", + "model": "DX9G_T5", + "UILogicId": "DX_9G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6062795ad18cbd0008e2fce8" + } + }, + { + "classid": "141", + "product": { + "_id": "5cae97c9128519000168596f", + "materialNo": "110-1638-0101", + "name": "DEEBOT M81 Pro", + "icon": "5d2c2aa64d60de0001eaf1f6", + "model": "M81", + "UILogicId": "ECO_INTL_141", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c2aa64d60de0001eaf1f6" + } + }, + { + "classid": "fqxoiu", + "product": { + "_id": "5e8e8d8a032edd8457c66bfb", + "materialNo": "110-1921-1100", + "name": "DEEBOT OZMO T8+", + "icon": "605b059217c95b0008ff20d4", + "model": "OT8+", + "UILogicId": "DT_8G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605b059217c95b0008ff20d4" + } + }, + { + "classid": "u6eqoa", + "product": { + "_id": "5e993f2f6a299d0bd506d665", + "materialNo": "110-1919-1401", + "name": "DEEBOT U2 PRO", + "icon": "606425a11269020008a9627d", + "model": "U2_APAC", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425a11269020008a9627d" + } + }, + { + "classid": "dl8fht", + "product": { + "_id": "5acb0fa87c295c0001876ecf", + "materialNo": "702-0000-0170", + "name": "DEEBOT 600 Series", + "icon": "5acc32067c295c0001876eea", + "model": "D600", + "UILogicId": "D_600", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea" + } + }, + { + "classid": "yna5xi", + "product": { + "_id": "5c19a91ca1e6ee000178224a", + "materialNo": "110-1820-0101", + "name": "DEEBOT OZMO 950 Series", + "icon": "606278df4a84d700082b39f1", + "model": "DX9G", + "UILogicId": "DX_9G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606278df4a84d700082b39f1" + } + }, + { + "classid": "123", + "product": { + "_id": "5cae9b201285190001685977", + "materialNo": "110-1639-0102", + "name": "DEEBOT Slim2 Series", + "icon": "5d2c150dba13eb00013feaae", + "model": "Slim2", + "UILogicId": "ECO_INTL_123", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c150dba13eb00013feaae" + } + }, + { + "classid": "140", + "product": { + "_id": "5cd43b4cf542e00001dc2dec", + "materialNo": "110-1639-0011", + "name": "DEEBOT Slim Neo", + "icon": "5d2c152f4d60de0001eaf1f4", + "model": "SlimNeo", + "UILogicId": "ECO_INTL_140", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c152f4d60de0001eaf1f4" + } + }, + { + "classid": "q1v5dn", + "product": { + "_id": "5d312ae18d8d430001817002", + "materialNo": "70200000228", + "name": "AT01", + "icon": "5d83375f6b6a570001569e26", + "model": "AT01", + "UILogicId": "AT_01G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d83375f6b6a570001569e26" + } + }, + { + "classid": "16wdph", + "product": { + "_id": "5d280ce344af3600013839ab", + "materialNo": "702-0000-0171", + "name": "DEEBOT 661", + "icon": "5d280ce3350e7a0001e84c95", + "model": "D661", + "UILogicId": "D_661", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d280ce3350e7a0001e84c95" + } + }, + { + "classid": "rvo6ev", + "product": { + "_id": "5e859780648255c8bf530e14", + "materialNo": "110-1919-1001", + "name": "DEEBOT U2", + "icon": "606426feb0a931000860fad5", + "model": "U2_AL", + "UILogicId": "U2_HIGH_MODE_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426feb0a931000860fad5" + } + }, + { + "classid": "09m4bu", + "product": { + "_id": "5e9d340b8c92c777ab83557f", + "materialNo": "130-6225-0605", + "name": "K650", + "icon": "5ef31b8cee3c1200075b6f67", + "model": "K651G", + "UILogicId": "K650_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ef31b8cee3c1200075b6f67" + } + }, + { + "classid": "y2qy3m", + "product": { + "_id": "5ea8d28922838d15795ed88d", + "materialNo": "110-1919-1701", + "name": "DEEBOT U2 PRO", + "icon": "606425784a84d700082b39f6", + "model": "U2_APAC3", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425784a84d700082b39f6" + } + }, + { + "classid": "0xyhhr", + "product": { + "_id": "5ca4716312851900016858cd", + "materialNo": "110-1825-0201", + "name": "DEEBOT OZMO 700", + "icon": "5d117d4f0ac6ad00012b792d", + "model": "DV5G", + "UILogicId": "DV_5G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d117d4f0ac6ad00012b792d" + } + }, + { + "classid": "ipzjy0", + "product": { + "_id": "5e9923878c92c7676b835555", + "materialNo": "110-1919-0702", + "name": "DEEBOT U2", + "icon": "606426c64a84d700082b39fa", + "model": "U2_EL1", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426c64a84d700082b39fa" + } + }, + { + "classid": "hsgwhi", + "product": { + "_id": "5de9b6fb787cdf0001ef98ac", + "materialNo": "113-1931-0001", + "name": "ANDY", + "icon": "5e731a4a06f6de700464c69d", + "model": "ANDY", + "UILogicId": "ATMOBOT_ANDY", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e731a4a06f6de700464c69d" + } + }, + { + "classid": "h18jkh", + "product": { + "_id": "5e8e8d2a032edd3c03c66bf7", + "materialNo": "110-1921-0400", + "name": "DEEBOT OZMO T8", + "icon": "5e8e8d146482551d72530e47", + "model": "OT8G", + "UILogicId": "DT_8G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8e8d146482551d72530e47" + } + }, + { + "classid": "126", + "product": { + "_id": "5ca32ab212851900016858c7", + "materialNo": "702-0000-0136", + "name": "DEEBOT N79", + "icon": "5ca32ab2e9e9270001354b3d", + "model": "N79", + "UILogicId": "ECO_INTL_126", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32ab2e9e9270001354b3d" + } + }, + { + "classid": "x5d34r", + "product": { + "_id": "5de0d86ed88546000195239a", + "materialNo": "110-1913-0101", + "name": "DEEBOT OZMO T8 AIVI", + "icon": "605053e7fc527c00087fda1e", + "model": "DXAI_INTL", + "UILogicId": "DX_AIG", + "ota": True, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605053e7fc527c00087fda1e" + } + }, + { + "classid": "ls1ok3", + "product": { + "_id": "5b6561060506b100015c8868", + "materialNo": "110-1711-0201", + "name": "DEEBOT 900 Series", + "icon": "5ba4a2cb6c2f120001c32839", + "model": "DN5G", + "UILogicId": "D_900", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839" + } + }, + { + "classid": "y79a7u", + "product": { + "_id": "5b04c0227ccd1a0001e1f6a8", + "materialNo": "110-1810-0101", + "name": "DEEBOT OZMO 900 Series", + "icon": "5b04c0217ccd1a0001e1f6a7", + "model": "DN5G", + "UILogicId": "D_OZMO_900", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7" + } + }, + { + "classid": "vi829v", + "product": { + "_id": "5c19a8f3a1e6ee0001782247", + "materialNo": "110-1819-0101", + "name": "DEEBOT OZMO 920 Series", + "icon": "606278d3fc527c00087fdb08", + "model": "DX5G", + "UILogicId": "DX_5G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606278d3fc527c00087fdb08" + } + }, + { + "classid": "55aiho", + "product": { + "_id": "5e698a6306f6de52c264c61b", + "materialNo": "110-1921-1101", + "name": "DEEBOT OZMO T8+", + "icon": "605be27250928b0007c13264", + "model": "T8+", + "UILogicId": "DT_8G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605be27250928b0007c13264" + } + }, + { + "classid": "gd4uut", + "product": { + "_id": "5bc8189d68142800016a6937", + "materialNo": "110-1803-0101", + "name": "DEEBOT OZMO 960", + "icon": "5e8da019032edd9008c66bf0", + "model": "DG7G", + "UILogicId": "DR_935G", + "ota": True, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8da019032edd9008c66bf0" + } + }, + { + "classid": "130", + "product": { + "_id": "5cae98d01285190001685974", + "materialNo": "110-1629-0201", + "name": "DEEBOT OZMO 610 Series", + "icon": "5d4b7640de51dd0001fee131", + "model": "OZMO600", + "UILogicId": "ECO_INTL_130", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7640de51dd0001fee131" + } + }, + { + "classid": "2pv572", + "product": { + "_id": "5d1474630ac6ad00012b7940", + "materialNo": "110-1810-0107", + "name": "DEEBOT OZMO 905", + "icon": "5d1474632a6bd50001b5b6f3", + "model": "OZMO905", + "UILogicId": "D_OZMO_900", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d1474632a6bd50001b5b6f3" + } + }, + { + "classid": "xb83mv", + "product": { + "_id": "5d246180350e7a0001e84bea", + "materialNo": "88393939393", + "name": "DEEBOT U3", + "icon": "5d3fe649de51dd0001fee0de", + "model": "SLIM4_INTL", + "UILogicId": "DK_4G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d3fe649de51dd0001fee0de" + } + }, + { + "classid": "ar5bjb", + "product": { + "_id": "5e58a73d36e8f3cab08f031f", + "materialNo": "130-6211-0610", + "name": "DEEBOT 665", + "icon": "5e58a2df36e8f39e318f031d", + "model": "D665", + "UILogicId": "D_661", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e58a2df36e8f39e318f031d" + } + }, + { + "classid": "7j1tu6", + "product": { + "_id": "5e993e566a299d449a06d65a", + "materialNo": "110-1919-0801", + "name": "DEEBOT U2 PRO", + "icon": "6064260545505e0008e5cb49", + "model": "U2_EH2", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064260545505e0008e5cb49" + } + }, + { + "classid": "ts2ofl", + "product": { + "_id": "5e993e8b8c92c753dd83555f", + "materialNo": "110-1919-0901", + "name": "DEEBOT U2", + "icon": "606425dfd18cbd0008e2fcf3", + "model": "U2_AUL", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425dfd18cbd0008e2fcf3" + } + }, + { + "classid": "125", + "product": { + "_id": "5cae9703128519000168596a", + "materialNo": "110-1638-0102", + "name": "DEEBOT M80 Pro", + "icon": "5d2c14414d60de0001eaf1f2", + "model": "M80", + "UILogicId": "ECO_INTL_125", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c14414d60de0001eaf1f2" + } + }, + { + "classid": "jh3ry2", + "product": { + "_id": "5de9b77f0136c00001cb1f8e", + "materialNo": "113-1931-0003", + "name": "AVA", + "icon": "6049b34d1269020008a95aef", + "model": "AVA", + "UILogicId": "ATMOBOT_AVA", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6049b34d1269020008a95aef" + } + }, + { + "classid": "wlqdkp", + "product": { + "_id": "5e9924018c92c7c480835559", + "materialNo": "110-1919-0701", + "name": "DEEBOT U2", + "icon": "6064263ad18cbd0008e2fcf4", + "model": "U2_EL2", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064263ad18cbd0008e2fcf4" + } + }, + { + "classid": "c0lwyn", + "product": { + "_id": "5e993eef6a299d7e4a06d660", + "materialNo": "110-1919-1301", + "name": "DEEBOT U2 PRO", + "icon": "606425ab4a84d700082b39f7", + "model": "U2_AUH", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425ab4a84d700082b39f7" + } + }, + { + "classid": "emzppx", + "product": { + "_id": "5c763f35280fda0001770b84", + "materialNo": "702-0000-0169", + "name": "DEEBOT 501", + "icon": "5c931fef280fda0001770d7e", + "model": "D501", + "UILogicId": "D_500", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c931fef280fda0001770d7e" + } + }, + { + "classid": "115", + "product": { + "_id": "5bbedd2822d57f00018c13b7", + "materialNo": "110-1602-0101", + "name": "DEEBOT OZMO/PRO 930 Series", + "icon": "5cf711aeb0acfc000179ff8a", + "model": "DR930", + "UILogicId": "DR_930G", + "ota": True, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cf711aeb0acfc000179ff8a" + } + }, + { + "classid": "jr3pqa", + "product": { + "_id": "5b43077b8bc457000140363e", + "materialNo": "702-0000-0202", + "name": "DEEBOT 711", + "icon": "5b5ac4cc8d5a56000111e769", + "model": "D711", + "UILogicId": "D_700", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769" + } + }, + { + "classid": "142", + "product": { + "_id": "5ca1ca7a12851900016858bd", + "materialNo": "110-1640-0101", + "name": "DEEBOT Mini2", + "icon": "5ca1ca79e9e9270001354b2d", + "model": "Mini2", + "UILogicId": "ECO_INTL_142", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca1ca79e9e9270001354b2d" + } + }, + { + "classid": "aqdd5p", + "product": { + "_id": "5cb7cfba179839000114d762", + "materialNo": "110-1711-0001", + "name": "DEEBOT DE55", + "icon": "5cb7cfbab72c4d00010e5fc7", + "model": "DE5G", + "UILogicId": "D_900", + "ota": True, + "supportType": { + "share": False, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cb7cfbab72c4d00010e5fc7" + } + }, + { + "classid": "152", + "product": { + "_id": "5cbd97b961526a00019799bd", + "materialNo": "110-1628-0302", + "name": "DEEBOT", + "icon": "5d4b7628de51dd0001fee12f", + "model": "D600", + "UILogicId": "ECO_INTL_152", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7628de51dd0001fee12f" + } + }, + { + "classid": "d0cnel", + "product": { + "_id": "5ceba1c6d85b4d0001776986", + "materialNo": "702-0000-0204", + "name": "DEEBOT 711s", + "icon": "5d157f9f77a3a60001051f69", + "model": "D711S", + "UILogicId": "D_700", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d157f9f77a3a60001051f69" + } + }, + { + "classid": "u4h1uk", + "product": { + "_id": "5e993eba8c92c71489835564", + "materialNo": "110-1919-1101", + "name": "DEEBOT U2 PRO", + "icon": "606425bed18cbd0008e2fcf2", + "model": "U2_JP", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425bed18cbd0008e2fcf2" + } + }, + { + "classid": "3ab24g", + "product": { + "_id": "5e9d34418c92c717e9835583", + "materialNo": "130-6225-0302", + "name": "K650", + "icon": "5ef31b80f5dcdf000767cf4d", + "model": "K652G", + "UILogicId": "K650_RANDOM_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ef31b80f5dcdf000767cf4d" + } + }, + { + "classid": "9akc61", + "product": { + "_id": "5c763f8263023c0001e7f855", + "materialNo": "20200115005", + "name": "DEEBOT 505", + "icon": "5c932067280fda0001770d7f", + "model": "D505", + "UILogicId": "D_500", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c932067280fda0001770d7f" + } + }, + { + "classid": "159", + "product": { + "_id": "5ca32bc2e9e9270001354b41", + "materialNo": "110-1629-0203", + "name": "DEEBOT OZMO 601", + "icon": "5d4b7606de51dd0001fee12d", + "model": "159", + "UILogicId": "ECO_INTL_159", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7606de51dd0001fee12d" + } + }, + { + "classid": "b742vd", + "product": { + "_id": "5e699a4106f6de83ea64c620", + "materialNo": "110-1921-0301", + "name": "DEEBOT OZMO T8", + "icon": "5e8e93a7032edd3f5ec66d4a", + "model": "T8G", + "UILogicId": "DT_8G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8e93a7032edd3f5ec66d4a" + } + }, + { + "classid": "uv242z", + "product": { + "_id": "5b5149b4ac0b87000148c128", + "materialNo": "702-0000-0205", + "name": "DEEBOT 710", + "icon": "5b5ac4e45f21100001882bb9", + "model": "D700", + "UILogicId": "D_700", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9" + } + }, + { + "classid": "155", + "product": { + "_id": "5cce893813afb7000195d6af", + "materialNo": "702-0000-0164", + "name": "DEEBOT N79S/SE", + "icon": "5cd4ca505b032200015a455d", + "model": "DN622", + "UILogicId": "ECO_INTL_155", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cd4ca505b032200015a455d" + } + }, + { + "classid": "1qdu4z", + "product": { + "_id": "5de9b7f50136c00001cb1f96", + "materialNo": "117-1923-0101", + "name": "Aaron", + "icon": "5e71c7df298f0d9cabfef86f", + "model": "AT80K", + "UILogicId": "AT80", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e71c7df298f0d9cabfef86f" + } + }, + { + "classid": "4zfacv", + "product": { + "_id": "5bf2596f23244a00013f2f13", + "materialNo": "910", + "name": "DEEBOT 910", + "icon": "5c778731280fda0001770ba0", + "model": "DN2_INTL", + "UILogicId": "DN_2G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c778731280fda0001770ba0" + } + }, + { + "classid": "nq9yhl", + "product": { + "_id": "5e8597b0032edd333ac66bbf", + "materialNo": "110-1919-0601", + "name": "DEEBOT U2 PRO", + "icon": "606426ebb0a931000860fad4", + "model": "U2_AH", + "UILogicId": "U2_HIGH_MODE_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426ebb0a931000860fad4" + } + }, + { + "classid": "m7lqzi", + "product": { + "_id": "5c653edf7b93c700013f12cc", + "materialNo": "113-1708-0001", + "name": "ATMOBOT Pro", + "icon": "5d2c63a5ba13eb00013feab7", + "model": "AA30G", + "UILogicId": "AA_30G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c63a5ba13eb00013feab7" + } + }, + { + "classid": "r8ead0", + "product": { + "_id": "5c763f63280fda0001770b88", + "materialNo": "702-0000-0161", + "name": "DEEBOT 502", + "icon": "5c93204b63023c0001e7faa7", + "model": "D502", + "UILogicId": "D_500", + "ota": False, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c93204b63023c0001e7faa7" + } + }, + { + "classid": "jjccwk", + "product": { + "_id": "5ce7870cd85b4d0001775db9", + "materialNo": "20200115004", + "name": "DEEBOT OZMO 750", + "icon": "5d3aa309ba13eb00013feb69", + "model": "DV6G", + "UILogicId": "DV_6G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d3aa309ba13eb00013feb69" + } + }, + { + "classid": "129", + "product": { + "_id": "5ca31df1e9e9270001354b35", + "materialNo": "110-1628-0101", + "name": "DEEBOT M86", + "icon": "5ca31df112851900016858c0", + "model": "M86", + "UILogicId": "ECO_INTL_129", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca31df112851900016858c0" + } + }, + { + "classid": "165", + "product": { + "_id": "5ca32a11e9e9270001354b39", + "materialNo": "702-0000-0189", + "name": "DEEBOT N79T/W", + "icon": "5ca32a1012851900016858c6", + "model": "N79T", + "UILogicId": "ECO_INTL_165", + "ota": False, + "supportType": { + "share": False, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32a1012851900016858c6" + } + }, + { + "classid": "jffnlf", + "product": { + "_id": "5e53208426be716edf4b55cf", + "materialNo": "130-6311-1702", + "name": "DEEBOT N3 MAX", + "icon": "5e53207a26be71596c4b55cd", + "model": "DU3G", + "UILogicId": "DU_6G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e53207a26be71596c4b55cd" + } + }, + { + "classid": "d4v1pm", + "product": { + "_id": "5e9924416a299dddac06d656", + "materialNo": "110-1919-0802", + "name": "DEEBOT U2 PRO", + "icon": "606426254a84d700082b39f9", + "model": "U2_EH1", + "UILogicId": "U2_HIGH_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426254a84d700082b39f9" + } + }, + { + "classid": "34vhpm", + "product": { + "_id": "5edd998afdd6a30008da039b", + "materialNo": "110-1913-0501", + "name": "DEEBOT T8 AIVI +", + "icon": "605050031269020008a95af9", + "model": "DXAIS_TW", + "UILogicId": "DX_AIG", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605050031269020008a95af9" + } + }, + { + "classid": "tpnwyu", + "product": { + "_id": "5edd9a4075f2fc000636086c", + "materialNo": "110-1913-0401", + "name": "DEEBOT T8 AIVI +", + "icon": "6050503e1269020008a95afa", + "model": "DXAIS_HK", + "UILogicId": "DX_AIG", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6050503e1269020008a95afa" + } + }, + { + "classid": "wgxm70", + "product": { + "_id": "5ed5e4d3a719ea460ec3216c", + "materialNo": "110-1921-0012", + "name": "DEEBOT T8", + "icon": "5edf2bbedb28cc00062f8bd7", + "model": "T8GC", + "UILogicId": "DT_8G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5edf2bbedb28cc00062f8bd7" + } + }, + { + "classid": "1zqysa", + "product": { + "_id": "5ee85c64fdd6a30008da0af4", + "materialNo": "110-1919-2201", + "name": "DEEBOT U2 POWER", + "icon": "6064258d1269020008a9627a", + "model": "U2_HK", + "UILogicId": "U2_HIGH_NOMAG_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064258d1269020008a9627a" + } + }, + { + "classid": "chmi0g", + "product": { + "_id": "5ee85cabfdd6a30008da0af8", + "materialNo": "110-1919-2301", + "name": "DEEBOT U2 POWER", + "icon": "606425821269020008a96279", + "model": "U2_TW", + "UILogicId": "U2_HIGH_NOMAG_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425821269020008a96279" + } + }, + { + "classid": "p5nx9u", + "product": { + "_id": "5f0d45404a3cbe00073d17db", + "materialNo": "110-2022-0001", + "name": "yeedi 2 hybrid", + "icon": "5f59e774c0f03a0008ee72e0", + "model": "K750", + "UILogicId": "DK_750", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5f59e774c0f03a0008ee72e0" + } + }, + { + "classid": "n6cwdb", + "product": { + "_id": "5f729454a541bd000881cc7b", + "materialNo": "110-2009-0900", + "name": "DEEBOT N8", + "icon": "60627a92fc527c00087fdb0a", + "model": "N8_LDS_WHITE_N", + "UILogicId": "T5_SE_G_DTOF", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627a92fc527c00087fdb0a" + } + }, + { + "classid": "lhbd50", + "product": { + "_id": "5f88195e6cf8de0008ed7c11", + "materialNo": "110-2010-1001", + "name": "DEEBOT T9+", + "icon": "603f51243b03f50007b6c2ca", + "model": "DEEBOT_OZMO_T9PLUS", + "UILogicId": "T9_PRO_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/603f51243b03f50007b6c2ca" + } + }, + { + "classid": "ucn2xe", + "product": { + "_id": "5f8819156cf8de0008ed7c0d", + "materialNo": "110-2010-0301", + "name": "DEEBOT T9", + "icon": "603f510e3b03f50007b6c2c9", + "model": "DEEBOT_OZMO_T9", + "UILogicId": "T9_PRO_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/603f510e3b03f50007b6c2c9" + } + }, + { + "classid": "0bdtzz", + "product": { + "_id": "5fa105c6d16a99000667eb54", + "materialNo": "110-1921-0404", + "name": "DEEBOT OZMO T8 PURE", + "icon": "5fa105bbd16a99000667eb52", + "model": "OT8_PURE", + "UILogicId": "DT_8G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5fa105bbd16a99000667eb52" + } + }, + { + "classid": "r5zxjr", + "product": { + "_id": "5fa2441169320300086ff812", + "materialNo": "110-2009-0101", + "name": "DEEBOT N7", + "icon": "60627c09b0a931000860facd", + "model": "N7_LDS", + "UILogicId": "T5_SE_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627c09b0a931000860facd" + } + }, + { + "classid": "r5y7re", + "product": { + "_id": "5fa4b1f8c7260e000858584b", + "materialNo": "110-2009-0902", + "name": "DEEBOT N8", + "icon": "60627a9cb0a931000860fac7", + "model": "N8_DTOF_TW", + "UILogicId": "T5_SE_G_DTOF", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627a9cb0a931000860fac7" + } + }, + { + "classid": "snxbvc", + "product": { + "_id": "5fa4e31dd16a99000667eb94", + "materialNo": "110-2008-0102", + "name": "DEEBOT N8 PRO", + "icon": "60627bbfb0a931000860fac9", + "model": "N8_PRO_WHITE", + "UILogicId": "DT_8SE_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bbfb0a931000860fac9" + } + }, + { + "classid": "7bryc5", + "product": { + "_id": "5fa4e248c7260e0008585850", + "materialNo": "110-2029-0001", + "name": "DEEBOT N8+", + "icon": "5fb474d4d16a99000667edd9", + "model": "N8_PLUS_WHITE", + "UILogicId": "N8_PLUS", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5fb474d4d16a99000667edd9" + } + }, + { + "classid": "b2jqs4", + "product": { + "_id": "5fbc7cc69601440008b24469", + "materialNo": "110-2029-0002", + "name": "DEEBOT N8+", + "icon": "5feaeb27d4cb3a0006679047", + "model": "N8_PLUS_BLACK", + "UILogicId": "N8_PLUS", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb27d4cb3a0006679047" + } + }, + { + "classid": "yu362x", + "product": { + "_id": "5fbc7da39601440008b2446d", + "materialNo": "110-2008-0103", + "name": "DEEBOT N8 PRO", + "icon": "60627bde50928b0007c13273", + "model": "N8_PRO_BLACK", + "UILogicId": "DT_8SE_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bde50928b0007c13273" + } + }, + { + "classid": "ifbw08", + "product": { + "_id": "5fbc7e18c7260e0008585c8f", + "materialNo": "110-2008-0901", + "name": "DEEBOT N8 PRO+", + "icon": "5feaeb47d4cb3a0006679048", + "model": "N8_PRO_PLUS_WHITE", + "UILogicId": "DT_8SE_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb47d4cb3a0006679048" + } + }, + { + "classid": "85as7h", + "product": { + "_id": "5fbc7f5069320300086ffa5e", + "materialNo": "110-2008-0902", + "name": "DEEBOT N8 PRO+", + "icon": "5feaeb585f437d0008e0e00c", + "model": "N8_PRO_PLUS_BLACK", + "UILogicId": "DT_8SE_G", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb585f437d0008e0e00c" + } + }, + { + "classid": "ty84oi", + "product": { + "_id": "5fbcac79c7260e0008585c94", + "materialNo": "110-2029-1201", + "name": "DEEBOT N8", + "icon": "60627bcafc527c00087fdb0c", + "model": "N8_WHITE", + "UILogicId": "N8_PLUS", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bcafc527c00087fdb0c" + } + }, + { + "classid": "36xnxf", + "product": { + "_id": "5fbcacf369320300086ffa63", + "materialNo": "110-2029-0701", + "name": "DEEBOT N8", + "icon": "60627bd517c95b0008ff20ec", + "model": "N8_BLACK\t", + "UILogicId": "N8_PLUS", + "ota": True, + "supportType": { + "share": True, + "tmjl": False, + "assistant": True, + "alexa": True + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bd517c95b0008ff20ec" + } + }, + { + "classid": "2pj946", + "product": { + "_id": "5fd1e362bbf4b30006cd9255", + "materialNo": "111-2005-0301", + "name": "WINBOT 920", + "icon": "6049b3631269020008a95af0", + "model": "W920_INT", + "UILogicId": "winbot_g", + "ota": True, + "supportType": { + "share": False, + "tmjl": False, + "assistant": False, + "alexa": False + }, + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6049b3631269020008a95af0" + } } - } ] - RETURN_API_SUCCESS = "0000" ERR_ACTIVATE_TOKEN_TIMEOUT = "1006" ERR_COMMON = "0001" @@ -830,4 +1774,3 @@ class EcoVacsHome_Login(EcoVacs_Login): ERR_WRONG_EMAIL_ADDRESS: "1008", ERR_WRONG_PWD_FROMATE: "1009", } - diff --git a/bumper/plugins/bumper_confserver_portal_appsvr.py b/bumper/plugins/bumper_confserver_portal_appsvr.py index 2083135..a92ca66 100644 --- a/bumper/plugins/bumper_confserver_portal_appsvr.py +++ b/bumper/plugins/bumper_confserver_portal_appsvr.py @@ -1,30 +1,28 @@ #!/usr/bin/env python3 -import asyncio +import logging + from aiohttp import web + from bumper import plugins -import logging -import bumper from bumper.models import * -from bumper import plugins -from datetime import datetime, timedelta class portal_api_appsvr(plugins.ConfServerApp): def __init__(self): self.name = "portal_api_appsvr" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "portal_api" - - self.routes = [ - - web.route("*", "/appsvr/app.do", self.handle_appsvr_api, name="portal_api_appsvr_app"), + self.routes = [ + web.route("*", "/appsvr/app.do", self.handle_appsvr_app, name="portal_api_appsvr_app"), + web.route("*", "/appsvr/service/list", self.handle_appsvr_service_list, name="portal_api_appsvr_service_list"), + web.route("*", "/appsvr/oauth_callback", self.handle_appsvr_oauth_callback, name="portal_api_appsvr_oauth_callback"), ] self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - async def handle_appsvr_api(self, request): + async def handle_appsvr_app(self, request): if not request.method == "GET": # Skip GET for now try: @@ -137,7 +135,7 @@ async def handle_appsvr_api(self, request): # "did": "did", # "lang": "EN", # "mid": "ls1ok3" - # } + # } # example response # { # "todo": "result", @@ -147,7 +145,7 @@ async def handle_appsvr_api(self, request): # "mailTitle": "I'm sharing my DEEBOT and you're invited!" # }, # "ret": "ok" - # } + # } except Exception as e: @@ -155,7 +153,59 @@ async def handle_appsvr_api(self, request): # Return fail for GET body = {"result": "fail", "todo": "result"} - return web.json_response(body) - -plugin = portal_api_appsvr() + return web.json_response(body) + + async def handle_appsvr_service_list(self, request): + try: + # original urls comment out as they are sub sub domain, which the current certificate is not valid + # using url, where the certs is valid + # data = { + # "account": "users-base.dc-eu.ww.ecouser.net", + # "jmq": "jmq-ngiot-eu.dc.ww.ecouser.net", + # "lb": "lbo.ecouser.net", + # "magw": "api-app.dc-eu.ww.ecouser.net", + # "msgcloud": "msg-eu.ecouser.net:5223", + # "ngiotLb": "jmq-ngiot-eu.area.ww.ecouser.net", + # "rop": "api-rop.dc-eu.ww.ecouser.net" + # } + + data = { + "account": "users-base.ecouser.net", + "jmq": "jmq-ngiot-eu.ecouser.net", + "lb": "lbo.ecouser.net", + "magw": "api-app.ecouser.net", + "msgcloud": "msg-eu.ecouser.net:5223", + "ngiotLb": "jmq-ngiot-eu.ecouser.net", + "rop": "api-rop.ecouser.net" + } + + body = { + "code": 0, + "data": data, + "ret": "ok", + "todo": "result" + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_appsvr_oauth_callback(self, request): + try: + token = bumper.token_by_authcode(request.query["code"]) + oauth = bumper.user_add_oauth(token["userid"]) + body = { + "code": 0, + "data": oauth.toResponse(), + "ret": "ok", + "todo": "result" + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = portal_api_appsvr() diff --git a/bumper/plugins/bumper_confserver_portal_ecms.py b/bumper/plugins/bumper_confserver_portal_ecms.py new file mode 100644 index 0000000..e408d18 --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_ecms.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import logging + +from aiohttp import web + +from bumper import plugins +from bumper.models import * + + +class portal_api_ecms(plugins.ConfServerApp): + + def __init__(self): + self.name = "portal_api_ecms" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + web.route("*", "/ecms/app/ad/res", self.handle_ad_res, name="portal_api_ecms_ad_res"), + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_ad_res(self, request): + try: + body = { + "code": 0, + "data": [], + "message": "success", + "success": True + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + +plugin = portal_api_ecms() diff --git a/bumper/plugins/bumper_confserver_portal_lg.py b/bumper/plugins/bumper_confserver_portal_lg.py index c43d59b..77b9709 100644 --- a/bumper/plugins/bumper_confserver_portal_lg.py +++ b/bumper/plugins/bumper_confserver_portal_lg.py @@ -1,24 +1,22 @@ #!/usr/bin/env python3 -import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins -from datetime import datetime, timedelta -import os -import string import random +import string import xml.etree.ElementTree as ET +from aiohttp import web + +from bumper import plugins +from bumper.models import * + + class portal_api_lg(plugins.ConfServerApp): def __init__(self): self.name = "portal_api_lg" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "portal_api" - + self.routes = [ web.route("*", "/lg/log.do", self.handle_lg_log, name="portal_api_lg_log"), @@ -28,10 +26,11 @@ def __init__(self): self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time async def handle_lg_log(self, request): # EcoVacs Home + randomid = "".join(random.sample(string.ascii_letters, 6)) + try: json_body = json.loads(await request.text()) - randomid = "".join(random.sample(string.ascii_letters, 6)) did = json_body["did"] botdetails = bumper.bot_get(did) @@ -53,14 +52,14 @@ async def handle_lg_log(self, request): # EcoVacs Home json_body["payloadType"] = "x" if not "payload" in json_body: - #json_body["payload"] = "" + # json_body["payload"] = "" if json_body["td"] == "GetCleanLogs": json_body["td"] = "q" json_body["payload"] = '' if did != "": bot = bumper.bot_get(did) - if bot["company"] == "eco-ng": + if bot["company"] == "eco-ng": retcmd = await bumper.mqtt_helperbot.send_command( json_body, randomid ) @@ -74,11 +73,11 @@ async def handle_lg_log(self, request): # EcoVacs Home for l in cleanlogs: cleanlog = { "ts": l.attrib['s'], - "area": l.attrib['a'], + "area": l.attrib['a'], "last": l.attrib['l'], "cleanType": l.attrib['t'], - #imageUrl allows for providing images of cleanings, something to look into later - #"imageUrl": "https://localhost:8007", + # imageUrl allows for providing images of cleanings, something to look into later + # "imageUrl": "https://localhost:8007", } logs.append(cleanlog) body = { @@ -98,11 +97,12 @@ async def handle_lg_log(self, request): # EcoVacs Home json_body["toId"] ) ) - body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} - return web.json_response(body) except Exception as e: logging.exception("{}".format(e)) -plugin = portal_api_lg() + body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} + return web.json_response(body) + +plugin = portal_api_lg() diff --git a/bumper/plugins/bumper_confserver_portal_pim.py b/bumper/plugins/bumper_confserver_portal_pim.py index 286165f..2c7bee1 100644 --- a/bumper/plugins/bumper_confserver_portal_pim.py +++ b/bumper/plugins/bumper_confserver_portal_pim.py @@ -1,29 +1,27 @@ #!/usr/bin/env python3 -import asyncio +import logging +import os + from aiohttp import web + from bumper import plugins -import logging -import bumper from bumper.models import * -from bumper import plugins -from datetime import datetime, timedelta -import os + class portal_api_pim(plugins.ConfServerApp): def __init__(self): self.name = "portal_api_pim" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "portal_api" - + self.routes = [ - web.route("*", "/pim/product/getProductIotMap", self.handle_getProductIotMap, name="portal_api_pim_getProductIotMap"), web.route("*", "/pim/file/get/{id}", self.handle_pimFile, name="portal_api_pim_file"), web.route("*", "/pim/product/getConfignetAll", self.handle_getConfignetAll, name="portal_api_pim_getConfignetAll"), web.route("*", "/pim/product/getConfigGroups", self.handle_getConfigGroups, name="portal_api_pim_getConfigGroups"), web.route("*", "/pim/dictionary/getErrDetail", self.handle_getErrDetail, name="portal_api_pim_getErrDetail"), - + web.route("*", "/pim/product/software/config/batch", self.handle_product_config_batch, name="portal_api_pim_product_config_batch"), ] self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time @@ -43,10 +41,10 @@ async def handle_pimFile(self, request): try: fileID = request.match_info.get("id", "") - return web.FileResponse(os.path.join(bumper.bumper_dir,"bumper","web","images","robotvac_image.jpg")) - + return web.FileResponse(os.path.join(bumper.bumper_dir, "bumper", "web", "images", "robotvac_image.jpg")) + except Exception as e: - logging.exception("{}".format(e)) + logging.exception("{}".format(e)) async def handle_getConfignetAll(self, request): try: @@ -54,7 +52,7 @@ async def handle_getConfignetAll(self, request): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception("{}".format(e)) async def handle_getConfigGroups(self, request): try: @@ -62,20 +60,48 @@ async def handle_getConfigGroups(self, request): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception("{}".format(e)) async def handle_getErrDetail(self, request): try: body = { - "code": -1, - "data": [], - "msg": "This errcode's detail is not exists" - } + "code": -1, + "data": [], + "msg": "This errcode's detail is not exists" + } return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) - + logging.exception("{}".format(e)) + + async def handle_product_config_batch(self, request): + try: + json_body = json.loads(await request.text()) + data = [] + for pid in json_body["pids"]: + for productConfig in productConfigBatch: + if pid == productConfig["pid"]: + data.append(productConfig) + continue + + # not found in productConfigBatch + # some devices don't have any product configuration + data.append({ + "cfg": {}, + "pid": pid + }) + + body = { + "code": 200, + "data": data, + "message": "success" + } + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + plugin = portal_api_pim() confignetAllResponse = { @@ -2680,4 +2706,175 @@ async def handle_getErrDetail(self, request): "configFailedUrl": "https://portal-ww.ecouser.net/api/pim/configfail.html?lang=en&defaultLang=en", "contactUS": "helper" } -} \ No newline at end of file +} + +productConfigBatch = [ + { + "pid": "5e14196a6e71b80001b60fda", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5e8e8d8a032edd8457c66bfb", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5c19a91ca1e6ee000178224a", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5e8e8d2a032edd3c03c66bf7", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5de0d86ed88546000195239a", + "cfg": { + "supported": { + "tmallstand": False, + "video": True, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5c19a8f3a1e6ee0001782247", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5e698a6306f6de52c264c61b", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5e699a4106f6de83ea64c620", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5edd998afdd6a30008da039b", + "cfg": { + "supported": { + "tmallstand": False, + "video": True, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5edd9a4075f2fc000636086c", + "cfg": { + "supported": { + "tmallstand": False, + "video": True, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5ed5e4d3a719ea460ec3216c", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5f88195e6cf8de0008ed7c11", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5f8819156cf8de0008ed7c0d", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + }, + { + "pid": "5fa105c6d16a99000667eb54", + "cfg": { + "supported": { + "tmallstand": False, + "video": False, + "battery": True, + "clean": True, + "charge": True + } + } + } +] diff --git a/bumper/plugins/bumper_confserver_portal_rapp.py b/bumper/plugins/bumper_confserver_portal_rapp.py new file mode 100644 index 0000000..ca5ef99 --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_rapp.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +from aiohttp import web +import logging +from bumper.models import * +from bumper import plugins + + +class api_rapp(plugins.ConfServerApp): + + def __init__(self): + self.name = "api_rapp" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + web.route("*", "/rapp/sds/user/data/map/get", self.handle_map_get, name="api_rapp"), + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_map_get(self, request): + try: + body = { + "code": 0, + "data": { + "data": { + "name": "My Home" + }, + "tag": None + }, + "message": "success" + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = api_rapp() diff --git a/bumper/plugins/bumper_confserver_v1_private_common.py b/bumper/plugins/bumper_confserver_v1_private_common.py index 71a0034..f84d944 100644 --- a/bumper/plugins/bumper_confserver_v1_private_common.py +++ b/bumper/plugins/bumper_confserver_v1_private_common.py @@ -1,26 +1,28 @@ #!/usr/bin/env python3 -import asyncio +import logging + from aiohttp import web + from bumper import plugins -import logging -import bumper from bumper.models import * -from bumper import plugins -from datetime import datetime, timedelta class v1_private_common(plugins.ConfServerApp): def __init__(self): self.name = "v1_private_common" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "api_v1" - + self.routes = [ web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkAPPVersion", self.handle_checkAPPVersion, name="v1_common_checkAppVersion"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkVersion", self.handle_checkVersion, name="v1_common_checkVersion"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/uploadDeviceInfo", self.handle_uploadDeviceInfo, name="v1_common_uploadDeviceInfo"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getSystemReminder", self.handle_getSystemReminder, name="v1_common_getSystemReminder"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkVersion", self.handle_checkVersion, name="v1_common_checkVersion"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/uploadDeviceInfo", self.handle_uploadDeviceInfo, name="v1_common_uploadDeviceInfo"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getSystemReminder", self.handle_getSystemReminder, name="v1_common_getSystemReminder"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getConfig",self.handle_getConfig, name="v1_common_getConfig"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getAreas",self.handle_getAreas, name="v1_common_getAreas"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getAgreementURLBatch", self.handle_getAgreementURLBatch, name="v1_common_getAgreementURLBatch"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getTimestamp", self.handle_getTimestamp, name="v1_common_getTimestamp"), ] @@ -71,7 +73,7 @@ async def handle_checkAPPVersion(self, request): # EcoVacs Home return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception("{}".format(e)) async def handle_uploadDeviceInfo(self, request): # EcoVacs Home try: @@ -86,7 +88,7 @@ async def handle_uploadDeviceInfo(self, request): # EcoVacs Home return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception("{}".format(e)) async def handle_getSystemReminder(self, request): # EcoVacs Home try: @@ -108,8 +110,353 @@ async def handle_getSystemReminder(self, request): # EcoVacs Home return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception("{}".format(e)) + + async def handle_getConfig(self, request): + try: + data = [] + for key in request.query["keys"].split(','): + data.append({ + "key": key, + "value": "Y" + }) + + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": data, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_getAreas(self, request): + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": AREA_LIST, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_getAgreementURLBatch(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": [ + { + "acceptTime": None, + "force": None, + "id": "20180804040641_7d746faf18b8cb22a50d145598fe4c90", + "type": "USER", + "url": "https://gl-eu-wap.ecovacs.com/content/agreement?id=20180804040641_7d746faf18b8cb22a50d145598fe4c90&language=EN", + "version": "1.03" + }, + { + "acceptTime": None, + "force": None, + "id": "20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac", + "type": "PRIVACY", + "url": "https://gl-eu-wap.ecovacs.com/content/agreement?id=20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac&language=EN", + "version": "1.03" + } + ], + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_getTimestamp(self, request): # EcoVacs Home + try: + time = self.get_milli_time(datetime.utcnow().timestamp()) + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "timestamp": time + }, + "msg": "操作成功", + "success": True, + "time": time, + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) plugin = v1_private_common() +AREA_LIST = {"currentVersion": 231, + "areaList": [{"areaKey": "JP", "chsName": "日本", "enName": "Japan", "pyFirst": "R"}, + {"areaKey": "MY", "chsName": "马来西亚", "enName": "Malaysia", "pyFirst": "M"}, + {"areaKey": "DE", "chsName": "德国", "enName": "Germany", "pyFirst": "D"}, + {"areaKey": "LI", "chsName": "列支敦斯登", "enName": "Liechtenstein", "pyFirst": "L"}, + {"areaKey": "AT", "chsName": "奥地利", "enName": "Austria", "pyFirst": "A"}, + {"areaKey": "TW", "chsName": "台湾", "enName": "Taiwan", "pyFirst": "T"}, + {"areaKey": "FR", "chsName": "法国", "enName": "France", "pyFirst": "F"}, + {"areaKey": "CN", "chsName": "中国大陆", "enName": "China Mainland", "pyFirst": "Z"}, + {"areaKey": "SG", "chsName": "新加坡", "enName": "Singapore", "pyFirst": "X"}, + {"areaKey": "RE", "chsName": "留尼汪岛", "enName": "Reunion Island", "pyFirst": "L"}, + {"areaKey": "EH", "chsName": "西撒哈拉", "enName": "Western Sahara", "pyFirst": "X"}, + {"areaKey": "WF", "chsName": "瓦利斯群岛和富图纳群岛", "enName": "Wallis and Futuna Islands", + "pyFirst": "W"}, + {"areaKey": "KP", "chsName": "朝鲜", "enName": "North Korea", "pyFirst": "C"}, + {"areaKey": "ZW", "chsName": "津巴布韦", "enName": "Zimbabwe", "pyFirst": "J"}, + {"areaKey": "VI", "chsName": "美属维尔京群岛", "enName": "United States Virgin Islands", + "pyFirst": "M"}, + {"areaKey": "PF", "chsName": "法属玻里尼西亚", "enName": "French Polynesia", + "pyFirst": "F"}, + {"areaKey": "DJ", "chsName": "吉布提", "enName": "Djibouti", "pyFirst": "J"}, + {"areaKey": "KZ", "chsName": "哈萨克斯坦", "enName": "Kazakhstan", "pyFirst": "H"}, + {"areaKey": "TV", "chsName": "图瓦卢", "enName": "Tuvalu", "pyFirst": "T"}, + {"areaKey": "VU", "chsName": "瓦努阿图", "enName": "Vanuatu", "pyFirst": "W"}, + {"areaKey": "IN", "chsName": "印度", "enName": "India", "pyFirst": "Y"}, + {"areaKey": "CM", "chsName": "喀麦隆", "enName": "Cameroon", "pyFirst": "K"}, + {"areaKey": "LK", "chsName": "斯里兰卡", "enName": "Sri Lanka", "pyFirst": "S"}, + {"areaKey": "CC", "chsName": "科科斯群岛", "enName": "Cocos Islands", "pyFirst": "K"}, + {"areaKey": "KY", "chsName": "开曼群岛", "enName": "Cayman Islands", "pyFirst": "K"}, + {"areaKey": "QA", "chsName": "卡塔尔", "enName": "Qatar", "pyFirst": "K"}, + {"areaKey": "AZ", "chsName": "阿塞拜疆", "enName": "Azerbaijan", "pyFirst": "A"}, + {"areaKey": "HN", "chsName": "洪都拉斯", "enName": "Honduras", "pyFirst": "H"}, + {"areaKey": "AW", "chsName": "阿鲁巴岛", "enName": "Aruba", "pyFirst": "A"}, + {"areaKey": "KH", "chsName": "柬埔寨", "enName": "Cambodia", "pyFirst": "J"}, + {"areaKey": "CO", "chsName": "哥伦比亚", "enName": "Colombia", "pyFirst": "G"}, + {"areaKey": "IR", "chsName": "伊朗", "enName": "Iran", "pyFirst": "Y"}, + {"areaKey": "ZA", "chsName": "南非", "enName": "South Africa", "pyFirst": "N"}, + {"areaKey": "UY", "chsName": "乌拉圭", "enName": "Uruguay", "pyFirst": "W"}, + {"areaKey": "GU", "chsName": "关岛", "enName": "Guam", "pyFirst": "G"}, + {"areaKey": "GH", "chsName": "加纳", "enName": "Ghana", "pyFirst": "J"}, + {"areaKey": "GN", "chsName": "几内亚", "enName": "Guynea", "pyFirst": "J"}, + {"areaKey": "MH", "chsName": "马绍尔群岛", "enName": "Marshall Islands", + "pyFirst": "M"}, + {"areaKey": "SE", "chsName": "瑞典", "enName": "Sweden", "pyFirst": "R"}, + {"areaKey": "SB", "chsName": "所罗门群岛", "enName": "Solomon Islands", + "pyFirst": "S"}, + {"areaKey": "NE", "chsName": "尼日尔", "enName": "Niger", "pyFirst": "N"}, + {"areaKey": "HT", "chsName": "海地", "enName": "Haiti", "pyFirst": "H"}, + {"areaKey": "PL", "chsName": "波兰", "enName": "Poland", "pyFirst": "B"}, + {"areaKey": "DO", "chsName": "多米尼加共和国", "enName": "Dominican Republic", + "pyFirst": "D"}, + {"areaKey": "PS", "chsName": "巴勒斯坦", "enName": "Palestine", "pyFirst": "B"}, + {"areaKey": "KW", "chsName": "科威特", "enName": "Kuwait", "pyFirst": "K"}, + {"areaKey": "UZ", "chsName": "乌兹别克斯坦", "enName": "Republic of Uzbekistan", + "pyFirst": "W"}, + {"areaKey": "GD", "chsName": "格林纳达", "enName": "Grenada", "pyFirst": "G"}, + {"areaKey": "KG", "chsName": "吉尔吉斯斯坦", "enName": "Kyrgyzstan", "pyFirst": "J"}, + {"areaKey": "JO", "chsName": "约旦", "enName": "Jordan", "pyFirst": "Y"}, + {"areaKey": "IL", "chsName": "以色列", "enName": "Israel", "pyFirst": "Y"}, + {"areaKey": "UK", "chsName": "英国", "enName": "United Kingdom", "pyFirst": "Y"}, + {"areaKey": "MW", "chsName": "马拉维", "enName": "Malawi", "pyFirst": "M"}, + {"areaKey": "MC", "chsName": "摩纳哥", "enName": "Monaco", "pyFirst": "M"}, + {"areaKey": "IC", "chsName": "加那利群岛", "enName": "Canary Islands", "pyFirst": "J"}, + {"areaKey": "JM", "chsName": "牙买加", "enName": "Jamaica", "pyFirst": "Y"}, + {"areaKey": "MP", "chsName": "北马里亚纳群岛", "enName": "The Northern Mariana Islands", + "pyFirst": "B"}, + {"areaKey": "BH", "chsName": "巴林岛", "enName": "Bahrain", "pyFirst": "B"}, + {"areaKey": "MK", "chsName": "马其顿", "enName": "Macedonia", "pyFirst": "M"}, + {"areaKey": "ET", "chsName": "埃塞俄比亚", "enName": "Ethiopia", "pyFirst": "A"}, + {"areaKey": "CL", "chsName": "智利", "enName": "Chile", "pyFirst": "Z"}, + {"areaKey": "GP", "chsName": "瓜德罗普岛", "enName": "Guadeloupe", "pyFirst": "G"}, + {"areaKey": "FK", "chsName": "福克兰群岛", "enName": "Falkland Islands", + "pyFirst": "F"}, + {"areaKey": "GL", "chsName": "格陵兰", "enName": "Greenland", "pyFirst": "G"}, + {"areaKey": "BF", "chsName": "布基纳法索", "enName": "Burkina Faso", "pyFirst": "B"}, + {"areaKey": "GI", "chsName": "直布罗陀", "enName": "Gibraltar", "pyFirst": "Z"}, + {"areaKey": "MV", "chsName": "马尔代夫", "enName": "Maldives", "pyFirst": "M"}, + {"areaKey": "CU", "chsName": "古巴", "enName": "Cuba", "pyFirst": "G"}, + {"areaKey": "LS", "chsName": "莱索托", "enName": "Lesotho", "pyFirst": "L"}, + {"areaKey": "MA", "chsName": "摩洛哥", "enName": "Morocco", "pyFirst": "M"}, + {"areaKey": "AL", "chsName": "阿尔巴尼亚", "enName": "Albania", "pyFirst": "A"}, + {"areaKey": "AF", "chsName": "阿富汗", "enName": "Afghanistan", "pyFirst": "A"}, + {"areaKey": "CA", "chsName": "加拿大", "enName": "Canada", "pyFirst": "J"}, + {"areaKey": "BB", "chsName": "巴巴多斯", "enName": "Barbados", "pyFirst": "B"}, + {"areaKey": "LC", "chsName": "圣卢西亚岛", "enName": "Saint Lucia", "pyFirst": "S"}, + {"areaKey": "PN", "chsName": "皮特克恩岛", "enName": "Pitcairn Island", + "pyFirst": "P"}, + {"areaKey": "LV", "chsName": "拉脱维亚", "enName": "Latvia", "pyFirst": "L"}, + {"areaKey": "NO", "chsName": "挪威", "enName": "Norway", "pyFirst": "N"}, + {"areaKey": "BE", "chsName": "比利时", "enName": "Belgium", "pyFirst": "B"}, + {"areaKey": "VE", "chsName": "委内瑞拉", "enName": "Venezuela", "pyFirst": "W"}, + {"areaKey": "MQ", "chsName": "马提尼克", "enName": "Martinique", "pyFirst": "M"}, + {"areaKey": "GY", "chsName": "圭亚那", "enName": "Guyana", "pyFirst": "G"}, + {"areaKey": "AM", "chsName": "亚美尼亚", "enName": "Armenia", "pyFirst": "Y"}, + {"areaKey": "EC", "chsName": "厄瓜多尔", "enName": "Ecuador", "pyFirst": "E"}, + {"areaKey": "CV", "chsName": "佛得角", "enName": "Cape Verde", "pyFirst": "F"}, + {"areaKey": "NZ", "chsName": "新西兰", "enName": "New Zealand", "pyFirst": "X"}, + {"areaKey": "RO", "chsName": "罗马尼亚", "enName": "Romania", "pyFirst": "L"}, + {"areaKey": "DM", "chsName": "多米尼加", "enName": "Dominica", "pyFirst": "D"}, + {"areaKey": "TZ", "chsName": "坦桑尼亚", "enName": "Tanzania", "pyFirst": "T"}, + {"areaKey": "BD", "chsName": "孟加拉国", "enName": "Bangladesh", "pyFirst": "M"}, + {"areaKey": "TD", "chsName": "乍得", "enName": "Chad", "pyFirst": "Z"}, + {"areaKey": "LT", "chsName": "立陶宛", "enName": "Lithuania", "pyFirst": "L"}, + {"areaKey": "TJ", "chsName": "塔吉克斯坦", "enName": "Tajikistan", "pyFirst": "T"}, + {"areaKey": "TK", "chsName": "托克劳", "enName": "Tokelau", "pyFirst": "T"}, + {"areaKey": "BS", "chsName": "巴哈马群岛", "enName": "Bahamas", "pyFirst": "B"}, + {"areaKey": "MM", "chsName": "缅甸", "enName": "Myanmar", "pyFirst": "M"}, + {"areaKey": "BI", "chsName": "布隆迪", "enName": "Burundi", "pyFirst": "B"}, + {"areaKey": "PY", "chsName": "巴拉圭", "enName": "Paraguay", "pyFirst": "B"}, + {"areaKey": "SK", "chsName": "斯洛伐克", "enName": "Slovakia", "pyFirst": "S"}, + {"areaKey": "FI", "chsName": "芬兰", "enName": "Finland", "pyFirst": "F"}, + {"areaKey": "GA", "chsName": "加蓬", "enName": "Gabon", "pyFirst": "J"}, + {"areaKey": "DZ", "chsName": "阿尔及利亚", "enName": "Algeria", "pyFirst": "A"}, + {"areaKey": "FO", "chsName": "法罗群岛", "enName": "Faroe Islands", "pyFirst": "F"}, + {"areaKey": "ZM", "chsName": "赞比亚", "enName": "Zambia", "pyFirst": "Z"}, + {"areaKey": "NU", "chsName": "纽埃", "enName": "Niue", "pyFirst": "N"}, + {"areaKey": "ER", "chsName": "厄立特里亚国", "enName": "Eritrea", "pyFirst": "E"}, + {"areaKey": "HK", "chsName": "香港", "enName": "Hong Kong", "pyFirst": "X"}, + {"areaKey": "IT", "chsName": "意大利", "enName": "Italy", "pyFirst": "Y"}, + {"areaKey": "MS", "chsName": "蒙特色拉特岛", "enName": "Montserrat", "pyFirst": "M"}, + {"areaKey": "EE", "chsName": "爱沙尼亚", "enName": "Estonia", "pyFirst": "A"}, + {"areaKey": "WS", "chsName": "萨摩亚", "enName": "Samoa", "pyFirst": "S"}, + {"areaKey": "TG", "chsName": "多哥", "enName": "Togo", "pyFirst": "D"}, + {"areaKey": "ML", "chsName": "马里", "enName": "Mali", "pyFirst": "M"}, + {"areaKey": "GF", "chsName": "法属圭亚那", "enName": "French Guyana", "pyFirst": "F"}, + {"areaKey": "KM", "chsName": "科摩罗", "enName": "Comoros", "pyFirst": "K"}, + {"areaKey": "ID", "chsName": "印度尼西亚", "enName": "Indonesia", "pyFirst": "Y"}, + {"areaKey": "KE", "chsName": "肯尼亚", "enName": "Kenya", "pyFirst": "K"}, + {"areaKey": "EG", "chsName": "埃及", "enName": "Egypt", "pyFirst": "A"}, + {"areaKey": "NF", "chsName": "诺福克岛", "enName": "Norfolk Island", "pyFirst": "N"}, + {"areaKey": "RS", "chsName": "塞尔维亚", "enName": "Serbia", "pyFirst": "S"}, + {"areaKey": "TR", "chsName": "土耳其", "enName": "Turkey", "pyFirst": "T"}, + {"areaKey": "DK", "chsName": "丹麦", "enName": "Denmark", "pyFirst": "D"}, + {"areaKey": "AD", "chsName": "安道尔", "enName": "Andorra", "pyFirst": "A"}, + {"areaKey": "LR", "chsName": "利比里亚", "enName": "Liberia", "pyFirst": "L"}, + {"areaKey": "AE", "chsName": "阿拉伯联合酋长国", "enName": "United Arab Emirates", + "pyFirst": "A"}, + {"areaKey": "CH", "chsName": "瑞士", "enName": "Switzerland", "pyFirst": "R"}, + {"areaKey": "AU", "chsName": "澳大利亚", "enName": "Australia", "pyFirst": "A"}, + {"areaKey": "TP", "chsName": "东帝汶", "enName": "East Timor", "pyFirst": "D"}, + {"areaKey": "LY", "chsName": "利比亚", "enName": "Libya", "pyFirst": "L"}, + {"areaKey": "RW", "chsName": "卢旺达", "enName": "Rwanda", "pyFirst": "L"}, + {"areaKey": "SA", "chsName": "沙特阿拉伯", "enName": "Saudi Arabia", "pyFirst": "S"}, + {"areaKey": "AR", "chsName": "阿根廷", "enName": "Argentina", "pyFirst": "A"}, + {"areaKey": "GM", "chsName": "冈比亚", "enName": "Gambia", "pyFirst": "G"}, + {"areaKey": "BY", "chsName": "白俄罗斯", "enName": "Belarus", "pyFirst": "B"}, + {"areaKey": "SL", "chsName": "塞拉利昂", "enName": "Sierra Leone", "pyFirst": "S"}, + {"areaKey": "TM", "chsName": "土库曼斯坦", "enName": "Turkmenistan", "pyFirst": "T"}, + {"areaKey": "AG", "chsName": "安提瓜和巴布达", "enName": "Antigua and Barbuda", + "pyFirst": "A"}, + {"areaKey": "MR", "chsName": "毛里塔尼亚", "enName": "Mauritania", "pyFirst": "M"}, + {"areaKey": "PT", "chsName": "葡萄牙", "enName": "Portugal", "pyFirst": "P"}, + {"areaKey": "BW", "chsName": "博茨瓦纳", "enName": "Botswana", "pyFirst": "B"}, + {"areaKey": "GT", "chsName": "危地马拉", "enName": "Guatemala", "pyFirst": "W"}, + {"areaKey": "BT", "chsName": "不丹", "enName": "Bhutan", "pyFirst": "B"}, + {"areaKey": "AI", "chsName": "安圭拉岛", "enName": "Anguilla", "pyFirst": "A"}, + {"areaKey": "OM", "chsName": "阿曼", "enName": "Oman", "pyFirst": "A"}, + {"areaKey": "KI", "chsName": "基里巴斯", "enName": "Kiribati", "pyFirst": "J"}, + {"areaKey": "UA", "chsName": "乌克兰", "enName": "Ukraine", "pyFirst": "W"}, + {"areaKey": "YE", "chsName": "也门", "enName": "Yemen", "pyFirst": "Y"}, + {"areaKey": "DR", "chsName": "刚果民主共和国", + "enName": "Democratic Republic of the Congo", "pyFirst": "G"}, + {"areaKey": "MD", "chsName": "摩尔多瓦", "enName": "Moldova", "pyFirst": "M"}, + {"areaKey": "GW", "chsName": "几内亚比绍", "enName": "Guinea-Bissau", "pyFirst": "J"}, + {"areaKey": "CG", "chsName": "刚果布共和国", "enName": "Congo Brazzaville", + "pyFirst": "G"}, + {"areaKey": "SN", "chsName": "塞内加尔", "enName": "Senegal", "pyFirst": "S"}, + {"areaKey": "BA", "chsName": "波黑", "enName": "Bosnia Hercegovina", + "pyFirst": "B"}, + {"areaKey": "MO", "chsName": "澳门", "enName": "Macao", "pyFirst": "A"}, + {"areaKey": "KN", "chsName": "圣基茨和尼维斯", "enName": "Saint Kitts and Nevis", + "pyFirst": "S"}, + {"areaKey": "TO", "chsName": "汤加", "enName": "Tonga", "pyFirst": "T"}, + {"areaKey": "NG", "chsName": "尼日利亚", "enName": "Nigeria", "pyFirst": "N"}, + {"areaKey": "TT", "chsName": "特立尼达和多巴哥", "enName": "Trinidad and Tobago", + "pyFirst": "T"}, + {"areaKey": "CF", "chsName": "中非共和国", "enName": "Central African Republic", + "pyFirst": "Z"}, + {"areaKey": "PE", "chsName": "秘鲁", "enName": "Peru", "pyFirst": "M"}, + {"areaKey": "PG", "chsName": "巴布亚新几内亚", "enName": "Papua New Guinea", + "pyFirst": "B"}, + {"areaKey": "CX", "chsName": "圣延岛", "enName": "Christmas Island", "pyFirst": "S"}, + {"areaKey": "AN", "chsName": "安的列斯", "enName": "Netherlands Antilles", + "pyFirst": "A"}, + {"areaKey": "BO", "chsName": "玻利维亚", "enName": "Bolivia", "pyFirst": "B"}, + {"areaKey": "IQ", "chsName": "伊拉克", "enName": "Iraq", "pyFirst": "Y"}, + {"areaKey": "NP", "chsName": "尼泊尔", "enName": "Nepal", "pyFirst": "N"}, + {"areaKey": "BJ", "chsName": "贝宁", "enName": "Benin", "pyFirst": "B"}, + {"areaKey": "VN", "chsName": "越南", "enName": "Vietnam", "pyFirst": "Y"}, + {"areaKey": "NI", "chsName": "尼加拉瓜", "enName": "Nicaragua", "pyFirst": "N"}, + {"areaKey": "PW", "chsName": "帕劳群岛", "enName": "Palau", "pyFirst": "P"}, + {"areaKey": "SO", "chsName": "索马里", "enName": "Somalia", "pyFirst": "S"}, + {"areaKey": "SM", "chsName": "圣马力诺", "enName": "San Marino", "pyFirst": "S"}, + {"areaKey": "NR", "chsName": "瑙鲁", "enName": "Nauru", "pyFirst": "N"}, + {"areaKey": "BN", "chsName": "文莱", "enName": "Brunei Darussalam", "pyFirst": "W"}, + {"areaKey": "MZ", "chsName": "莫桑比克", "enName": "Mozambique", "pyFirst": "M"}, + {"areaKey": "GR", "chsName": "希腊", "enName": "Greece", "pyFirst": "X"}, + {"areaKey": "TN", "chsName": "突尼斯", "enName": "Tunisia", "pyFirst": "T"}, + {"areaKey": "RU", "chsName": "俄罗斯", "enName": "Russian Federation", + "pyFirst": "E"}, + {"areaKey": "MG", "chsName": "马达加斯加岛", "enName": "Madagascar", "pyFirst": "M"}, + {"areaKey": "NA", "chsName": "纳米比亚", "enName": "Namibia", "pyFirst": "N"}, + {"areaKey": "CQ", "chsName": "赤道几内亚", "enName": "Equatorial Guinea", + "pyFirst": "C"}, + {"areaKey": "SR", "chsName": "苏里南", "enName": "Suriname", "pyFirst": "S"}, + {"areaKey": "MU", "chsName": "毛里求斯", "enName": "Mauritius", "pyFirst": "M"}, + {"areaKey": "LA", "chsName": "老挝", "enName": "Laos", "pyFirst": "L"}, + {"areaKey": "US", "chsName": "美国", "enName": "United States", "pyFirst": "M"}, + {"areaKey": "ST", "chsName": "圣多美与普林希比共和国", "enName": "Sao Tome and Principe", + "pyFirst": "S"}, + {"areaKey": "BM", "chsName": "百慕大群岛", "enName": "Bermuda", "pyFirst": "B"}, + {"areaKey": "LU", "chsName": "卢森堡", "enName": "Luxembourg", "pyFirst": "L"}, + {"areaKey": "CR", "chsName": "哥斯达黎加", "enName": "Costa Rica", "pyFirst": "G"}, + {"areaKey": "KR", "chsName": "韩国", "enName": "South Korea", "pyFirst": "H"}, + {"areaKey": "CZ", "chsName": "捷克", "enName": "Czech Republic", "pyFirst": "J"}, + {"areaKey": "MX", "chsName": "墨西哥", "enName": "Mexico", "pyFirst": "M"}, + {"areaKey": "SH", "chsName": "圣赫勒拿岛", "enName": "St Helena", "pyFirst": "S"}, + {"areaKey": "AO", "chsName": "安哥拉", "enName": "Angola", "pyFirst": "A"}, + {"areaKey": "MN", "chsName": "蒙古", "enName": "Mongolia", "pyFirst": "M"}, + {"areaKey": "VC", "chsName": "圣文森特和格林纳丁斯", + "enName": "Saint Vincent and the Grenadines", "pyFirst": "S"}, + {"areaKey": "PH", "chsName": "菲律宾", "enName": "Philippines", "pyFirst": "F"}, + {"areaKey": "SC", "chsName": "塞舌尔", "enName": "Seychelles", "pyFirst": "S"}, + {"areaKey": "CK", "chsName": "库克群岛", "enName": "Cook Islands", "pyFirst": "K"}, + {"areaKey": "PK", "chsName": "巴基斯坦", "enName": "Pakistan", "pyFirst": "B"}, + {"areaKey": "HR", "chsName": "克罗地亚", "enName": "Croatia", "pyFirst": "K"}, + {"areaKey": "TH", "chsName": "泰国", "enName": "Thailand", "pyFirst": "T"}, + {"areaKey": "SI", "chsName": "斯洛文尼亚", "enName": "Slovenia", "pyFirst": "S"}, + {"areaKey": "VG", "chsName": "英属维尔京群岛", "enName": "British Virgin Islands", + "pyFirst": "Y"}, + {"areaKey": "SY", "chsName": "阿拉伯叙利亚共和国", "enName": "Syrian Arab Republic", + "pyFirst": "A"}, + {"areaKey": "CY", "chsName": "塞浦路斯", "enName": "Cyprus", "pyFirst": "S"}, + {"areaKey": "BR", "chsName": "巴西", "enName": "Brazil", "pyFirst": "B"}, + {"areaKey": "LB", "chsName": "黎巴嫩", "enName": "Lebanon", "pyFirst": "L"}, + {"areaKey": "IS", "chsName": "冰岛", "enName": "Iceland", "pyFirst": "B"}, + {"areaKey": "PA", "chsName": "巴拿马", "enName": "Panama", "pyFirst": "B"}, + {"areaKey": "FM", "chsName": "密克罗尼西亚", "enName": "Micronesia", "pyFirst": "M"}, + {"areaKey": "VA", "chsName": "梵蒂冈", "enName": "Vatican City State", + "pyFirst": "F"}, + {"areaKey": "NC", "chsName": "新喀里多尼亚", "enName": "New Caledonia", "pyFirst": "X"}, + {"areaKey": "MT", "chsName": "马尔他", "enName": "Malta", "pyFirst": "M"}, + {"areaKey": "BG", "chsName": "保加利亚", "enName": "Bulgaria", "pyFirst": "B"}, + {"areaKey": "ES", "chsName": "西班牙", "enName": "Spain", "pyFirst": "X"}, + {"areaKey": "CI", "chsName": "象牙海岸", "enName": "Ivory Coast", "pyFirst": "X"}, + {"areaKey": "IE", "chsName": "爱尔兰", "enName": "Ireland", "pyFirst": "A"}, + {"areaKey": "BZ", "chsName": "伯利兹城", "enName": "Belize", "pyFirst": "B"}, + {"areaKey": "SZ", "chsName": "斯威士兰", "enName": "Swaziland", "pyFirst": "S"}, + {"areaKey": "SV", "chsName": "萨尔瓦多", "enName": "EI Salvador", "pyFirst": "S"}, + {"areaKey": "GE", "chsName": "格鲁吉亚", "enName": "Georgia", "pyFirst": "G"}, + {"areaKey": "SD", "chsName": "苏丹", "enName": "Sudan", "pyFirst": "S"}, + {"areaKey": "PR", "chsName": "波多黎各", "enName": "Puerto Rico", "pyFirst": "B"}, + {"areaKey": "FJ", "chsName": "斐济", "enName": "Fiji", "pyFirst": "F"}, + {"areaKey": "NL", "chsName": "荷兰", "enName": "Netherlands", "pyFirst": "H"}, + {"areaKey": "UG", "chsName": "乌干达", "enName": "Uganda", "pyFirst": "W"}, + {"areaKey": "HU", "chsName": "匈牙利", "enName": "Hungary", "pyFirst": "X"}, + {"areaKey": "TC", "chsName": "特克斯和凯科斯群岛", "enName": "Turks and Caicos Islands", + "pyFirst": "T"}]} diff --git a/bumper/plugins/bumper_confserver_v1_private_user.py b/bumper/plugins/bumper_confserver_v1_private_user.py index 68beb33..ab04b54 100644 --- a/bumper/plugins/bumper_confserver_v1_private_user.py +++ b/bumper/plugins/bumper_confserver_v1_private_user.py @@ -27,6 +27,9 @@ def __init__(self): web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreementBatch", self.handle_checkAgreement,name="v1_user_checkAgreementBatch"), web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserAccountInfo", authhandler.getUserAccountInfo,name="v1_user_getUserAccountInfo"), web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserMenuInfo", self.handle_getUserMenuInfo,name="v1_user_getUserMenuInfo"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/changeArea", self.handle_changeArea, name="v1_user_changeArea"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/queryChangeArea", self.handle_changeArea, name="v1_user_queryChangeArea"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/acceptAgreementBatch", self.handle_acceptAgreementBatch, name="v1_user_acceptAgreementBatch"), # Direct register from app: # /{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister #Register by email @@ -144,5 +147,37 @@ async def handle_getUserMenuInfo(self, request): except Exception as e: logging.exception("{}".format(e)) + async def handle_changeArea(self, request): + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "isNeedReLogin": "N" + }, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_acceptAgreementBatch(self, request): + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": None, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + plugin = v1_private_user() diff --git a/bumper/plugins/bumper_confserver_v1_private_userSetting.py b/bumper/plugins/bumper_confserver_v1_private_userSetting.py new file mode 100644 index 0000000..3a7720f --- /dev/null +++ b/bumper/plugins/bumper_confserver_v1_private_userSetting.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +from aiohttp import web +import logging +import bumper +from bumper import plugins +from datetime import datetime + + +class v1_private_userSetting(plugins.ConfServerApp): + + def __init__(self): + + self.name = "v1_private_userSetting" + self.plugin_type = "sub_api" + self.sub_api = "api_v1" + + self.routes = [ + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/userSetting/getSuggestionSetting", self.handle_getSuggestionSetting,name="v1_userSetting_getSuggestionSetting"), + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_getSuggestionSetting(self, request): + try: + + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "acceptSuggestion": "Y", + "itemList": [ + { + "name": "Aktionen/Angebote/Ereignisse", + "settingKey": "MARKETING", + "val": "Y" + }, + { + "name": "Benutzerbefragung", + "settingKey": "QUESTIONNAIRE", + "val": "Y" + }, + { + "name": "Produkt-Upgrade/Hilfe für Benutzer", + "settingKey": "INTRODUCTION", + "val": "Y" + } + ] + }, + "msg": "操作成功", + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + +plugin = v1_private_userSetting() diff --git a/create_certs/Bumper_SAN.txt b/create_certs/Bumper_SAN.txt index 9994fc3..2ffe727 100644 --- a/create_certs/Bumper_SAN.txt +++ b/create_certs/Bumper_SAN.txt @@ -3,4 +3,8 @@ ecovacs.com ecouser.net *.ecouser.net ecovacs.net -*.ecovacs.net \ No newline at end of file +*.ecovacs.net +*.ww.ecouser.net +*.dc-eu.ww.ecouser.net +*.dc.ww.ecouser.net +*.area.ww.ecouser.net \ No newline at end of file diff --git a/docs/Create_Certs.md b/docs/Create_Certs.md index daef11c..626c994 100644 --- a/docs/Create_Certs.md +++ b/docs/Create_Certs.md @@ -53,9 +53,9 @@ I get it, you don't trust create_certs and want to do it manually. The easiest ### Create a Root CA -1. Create csrconfig.txt for use in later commands +1. Create csrconfig_ca.txt for use in later commands -***csrconfig.txt*** +***csrconfig_ca.txt*** ```` [ req ] default_md = sha256 @@ -70,9 +70,9 @@ keyUsage=critical,keyCertSign,cRLSign basicConstraints=critical,CA:true,pathlen:1 ```` -1. Create certconfig.txt for use in later commands +1. Create certconfig_ca.txt for use in later commands -***certconfig.txt*** +***certconfig_ca.txt*** ```` [ req ] default_md = sha256 @@ -91,21 +91,21 @@ basicConstraints=critical,CA:true,pathlen:1 1. Generate the RSA private key - `openssl genpkey -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out priv.key` + `openssl genrsa -out ca.key 4096` 1. Create the CSR - `openssl req -new -nodes -key priv.key -config csrconfig.txt -out cert.csr` + `openssl req -new -nodes -key ca.key -config csrconfig_ca.txt -out ca.csr` 1. Self-sign your CSR - `openssl req -x509 -nodes -in cert.csr -days 3650 -key priv.key -config certconfig.txt -extensions req_ext -out cert.crt` + `openssl req -x509 -nodes -in ca.csr -days 1095 -key ca.key -config certconfig_ca.txt -extensions req_ext -out ca.crt` ### Create the Server Certificate -1. Create csrconfig.txt for use in later commands +1. Create csrconfig_bumper.txt for use in later commands -***csrconfig.txt*** +***csrconfig_bumper.txt*** ```` [ req ] default_md = sha256 @@ -127,11 +127,15 @@ DNS.2 = ecouser.net DNS.3 = *.ecouser.net DNS.4 = ecovacs.net DNS.5 = *.ecovacs.net +DNS.6 = *.ww.ecouser.net +DNS.7 = *.dc-eu.ww.ecouser.net +DNS.8 = *.dc.ww.ecouser.net +DNS.9 = *.area.ww.ecouser.net ```` -1. Create certconfig.txt for use in later commands +1. Create certconfig_bumper.txt for use in later commands -***certconfig.txt*** +***certconfig_bumper.txt*** ```` [ req ] default_md = sha256 @@ -155,19 +159,23 @@ DNS.2 = ecouser.net DNS.3 = *.ecouser.net DNS.4 = ecovacs.net DNS.5 = *.ecovacs.net +DNS.6 = *.ww.ecouser.net +DNS.7 = *.dc-eu.ww.ecouser.net +DNS.8 = *.dc.ww.ecouser.net +DNS.9 = *.area.ww.ecouser.net ```` 1. Generate the RSA private key - `openssl genpkey -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out priv.key` + `openssl genrsa -out bumper.key 4096` 1. Create the CSR - `openssl req -new -nodes -key priv.key -config csrconfig.txt -out cert.csr` + `openssl req -new -nodes -key bumper.key -config csrconfig_bumper.txt -out bumper.csr` 1. Sign your CSR with a root CA cert - `openssl x509 -req -in cert.csr -days 3650 -CA ca.crt -CAkey priv.key -extfile certconfig.txt -extensions req_ext -CAserial /tmp/tmp-10593TSH1OlVSxC7C -CAcreateserial -out cert.crt` + `openssl x509 -req -in bumper.csr -days 365 -CA ca.crt -CAkey ca.key -extfile certconfig_bumper.txt -extensions req_ext -CAcreateserial -out bumper.crt` ## Using a Custom CA/Self diff --git a/docs/DNS_Setup.md b/docs/DNS_Setup.md index a519be8..e433b8d 100644 --- a/docs/DNS_Setup.md +++ b/docs/DNS_Setup.md @@ -26,6 +26,7 @@ If overriding DNS for the top-level domains isn't an option, you'll need to conf **Note:** Depending on country, your phone/robot may be using a different domain. Most of these domains contain country-specific placeholders. Not all domains have been documented at this point, and this list will be updated as more are identified/seen. The preferred way to ensure Bumper works is to override the full domains as above. +**Note:** The app dynamically gets the required domains from the endpoint `api/appsvr/service/list` and therefore ecovacs can use different domains for different models. Replacement Examples: @@ -46,6 +47,7 @@ Replacement Examples: | `eco-{countrycode}-api.ecovacs.com` | Used for Login | | `gl-{countrycode}-api.ecovacs.com` | Used by EcoVacs Home app | | `gl-{countrycode}-openapi.ecovacs.com` | Used by EcoVacs Home app | +| `portal.ecouser.net` | Used for Login and Rest API | | `portal-{countrycode}.ecouser.net` | Used for Login and Rest API | | `portal-{region}.ecouser.net` | Used for Login and Rest API | | `portal-ww.ecouser.net` | Used for various Rest APIs | @@ -59,4 +61,11 @@ Replacement Examples: | `recommender.ecovacs.com` | Used by Ecovacs Home app | | `bigdata-international.ecovacs.com` | Telemetry/tracking | | `bigdata-northamerica.ecovacs.com` | Telemetry/tracking | +| `bigdata-europe.ecovacs.com` | Telemetry/tracking | | `bigdata-{unknown regions}.ecovacs.com` | Telemetry/tracking | +| `api-app.ww.ecouser.net` | Api for App (v2+) | +| `api-app.dc-{region}.ww.ecouser.net` | Api for App (v2+) | +| `users-base.dc-{region}.ww.ecouser.net` | Accounts for App (v2+) | +| `jmq-ngiot-{region}.dc.ww.ecouser.net` | MQTT for App (v2+) | +| `api-rop.dc-{region}.ww.ecouser.net` | App (v2+) | +| `jmq-ngiot-{region}.area.ww.ecouser.net`| App (v2+) | \ No newline at end of file diff --git a/docs/Docker.md b/docs/Docker.md index d6cc839..95d8f49 100644 --- a/docs/Docker.md +++ b/docs/Docker.md @@ -41,4 +41,14 @@ Optionally you can map existing directories for logs, data, and certs. ```` docker run -it -e "BUMPER_ANNOUNCE_IP=X.X.X.X" -p 443:443 -p 8007:8007 -p 8883:8883 -p 5223:5223 -v /home/user/bumper/data:/bumper/data --name bumper bmartin5692/bumper -```` \ No newline at end of file +```` + +# Docker-compose + +A docker-compose example can be found in the ["example" folder](https://github.com/bmartin5692/bumper/tree/master/example/docker-compose). + +The docker-compose starts two services: +- bumper itself +- nginx proxy, which redirects MQTT traffic on port `443` to port `8883` + +The redirection is required as the app v2+ and robots with a newer firmware are connecting to the mqtt server on port 433. diff --git a/example/docker-compose/docker-compose.yaml b/example/docker-compose/docker-compose.yaml new file mode 100644 index 0000000..70c47d5 --- /dev/null +++ b/example/docker-compose/docker-compose.yaml @@ -0,0 +1,45 @@ +--- +version: "3.6" + +networks: + bumper: + internal: true + +services: + nginx: + depends_on: + - bumper + image: nginx:alpine + networks: + default: + bumper: + ports: + - 443:443 + - 5223:5223 + - 8007:8007 + - 8883:8883 + restart: unless-stopped + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + - ./nginx/:/etc/nginx:ro # See config file below + + bumper: + image: bmartin5692/bumper + restart: unless-stopped + networks: + bumper: + + environment: + PUID: 1000 + PGID: 1000 + TZ: Europe/Rome + BUMPER_ANNOUNCE_IP: XXX # Insert your IP + BUMPER_LISTEN: 0.0.0.0 + # BUMPER_DEBUG: "true" + LOG_TO_STDOUT: "true" + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + - ./config:/bumper/data + - ./certs:/bumper/certs \ No newline at end of file diff --git a/example/docker-compose/nginx/nginx.conf b/example/docker-compose/nginx/nginx.conf new file mode 100644 index 0000000..8bd0da8 --- /dev/null +++ b/example/docker-compose/nginx/nginx.conf @@ -0,0 +1,41 @@ +error_log stderr; +pid /var/run/nginx.pid; + +events { } + +stream { + resolver 127.0.0.11 ipv6=off; #docker dns server + map_hash_bucket_size 64; + + map $ssl_preread_server_name $internalport { + # redirect all requests, which contain "mq" in the SNI -> MQTT + ~^.*(mq).*\.eco(vacs|user)\.(net|com)$ 8883; + + # the rest of eco(user|vacs) requests + ~^.*eco(vacs|user)\.(net|com)$ 443; + + # mapping default to MQTT as the bots are connecting directly to the ip without SNI + default 8883; + } + + server { + listen 443; + ssl_preread on; + proxy_pass bumper:$internalport; + } + + server { + listen 5223; + proxy_pass bumper:5223; + } + + server { + listen 8007; + proxy_pass bumper:8007; + } + + server { + listen 8883; + proxy_pass bumper:8883; + } +} \ No newline at end of file diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..cd84a47 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,14 @@ +from bumper import OAuth + + +def test_oauth(): + userId = "test" + o_auth = OAuth.create_new(userId) + assert o_auth is not None + assert o_auth.userId == userId + assert o_auth.access_token is not None + assert o_auth.expire_at is not None + assert o_auth.refresh_token is not None + + data = o_auth.toResponse() + assert data is not None