add new endpoints for app v2 (#116)
* add some new endpoints for app v2 * add oauth * update EcoVacsHomeProducts - new attribute model * add some missing endpoints for app v2 * - use only one level subdomain urls on service list - add status * add new used subdomains (for europe) * improve docker images creation as requirements are change less often * reformat area list * add revoke expired oauths * add docker-compose example * wait for mqtt start fixes #107 * make images even smaller by copying only required folders instead of all (.git,...) * fix exception during logging * return fail if we can't get clean logs * improve docker docs. Thanks for the hit @Bustel * fix body not json serializable * add docker-compose example in separate folder
This commit is contained in:
parent
9b1ae262a8
commit
df36bdd29d
21 changed files with 2778 additions and 904 deletions
|
|
@ -1,20 +1,3 @@
|
|||
# Logs
|
||||
logs
|
||||
|
||||
# Tests
|
||||
tests
|
||||
|
||||
# Examples
|
||||
examples
|
||||
|
||||
# Docs
|
||||
docs
|
||||
|
||||
# Certs
|
||||
certs
|
||||
|
||||
.coverage
|
||||
|
||||
# Markdown
|
||||
*.md
|
||||
|
||||
|
|
|
|||
10
Dockerfile
10
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"]
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ bumper_announce_ip = os.environ.get("BUMPER_ANNOUNCE_IP") or bumper_listen
|
|||
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():
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
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 logging
|
||||
import os
|
||||
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 @@ class ConfServer:
|
|||
|
||||
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 @@ class ConfServer:
|
|||
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 @@ class ConfServer:
|
|||
|
||||
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}",
|
||||
},
|
||||
to_log["response"]["body"] = f"{json.loads(response.body)}"
|
||||
|
||||
"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}",
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ class ConfServer:
|
|||
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:
|
||||
|
|
|
|||
89
bumper/db.py
89
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()
|
||||
|
|
|
|||
2353
bumper/models.py
2353
bumper/models.py
File diff suppressed because it is too large
Load diff
|
|
@ -1,30 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
from aiohttp import web
|
||||
from bumper import plugins
|
||||
import logging
|
||||
import bumper
|
||||
from bumper.models import *
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from bumper import plugins
|
||||
from datetime import datetime, timedelta
|
||||
from bumper.models import *
|
||||
|
||||
|
||||
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 @@ class portal_api_appsvr(plugins.ConfServerApp):
|
|||
# "did": "did",
|
||||
# "lang": "EN",
|
||||
# "mid": "ls1ok3"
|
||||
# }
|
||||
# }
|
||||
# example response
|
||||
# {
|
||||
# "todo": "result",
|
||||
|
|
@ -147,7 +145,7 @@ class portal_api_appsvr(plugins.ConfServerApp):
|
|||
# "mailTitle": "I'm sharing my DEEBOT and you're invited!"
|
||||
# },
|
||||
# "ret": "ok"
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -155,7 +153,59 @@ class portal_api_appsvr(plugins.ConfServerApp):
|
|||
|
||||
# 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()
|
||||
|
|
|
|||
38
bumper/plugins/bumper_confserver_portal_ecms.py
Normal file
38
bumper/plugins/bumper_confserver_portal_ecms.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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 @@ class portal_api_lg(plugins.ConfServerApp):
|
|||
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 @@ class portal_api_lg(plugins.ConfServerApp):
|
|||
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"] = '<ctl count="30"/>'
|
||||
|
||||
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 @@ class portal_api_lg(plugins.ConfServerApp):
|
|||
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 @@ class portal_api_lg(plugins.ConfServerApp):
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -1,29 +1,27 @@
|
|||
#!/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
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from bumper import plugins
|
||||
from bumper.models import *
|
||||
|
||||
|
||||
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 @@ class portal_api_pim(plugins.ConfServerApp):
|
|||
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 @@ class portal_api_pim(plugins.ConfServerApp):
|
|||
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 @@ class portal_api_pim(plugins.ConfServerApp):
|
|||
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 @@ configGroupsResponse = {
|
|||
"configFailedUrl": "https://portal-ww.ecouser.net/api/pim/configfail.html?lang=en&defaultLang=en",
|
||||
"contactUS": "helper"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
39
bumper/plugins/bumper_confserver_portal_rapp.py
Normal file
39
bumper/plugins/bumper_confserver_portal_rapp.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -1,26 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
from aiohttp import web
|
||||
from bumper import plugins
|
||||
import logging
|
||||
import bumper
|
||||
from bumper.models import *
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from bumper import plugins
|
||||
from datetime import datetime, timedelta
|
||||
from bumper.models import *
|
||||
|
||||
|
||||
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 @@ class v1_private_common(plugins.ConfServerApp):
|
|||
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 @@ class v1_private_common(plugins.ConfServerApp):
|
|||
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 @@ class v1_private_common(plugins.ConfServerApp):
|
|||
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"}]}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ class v1_private_user(plugins.ConfServerApp):
|
|||
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 @@ class v1_private_user(plugins.ConfServerApp):
|
|||
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()
|
||||
|
||||
|
|
|
|||
58
bumper/plugins/bumper_confserver_v1_private_userSetting.py
Normal file
58
bumper/plugins/bumper_confserver_v1_private_userSetting.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -3,4 +3,8 @@ ecovacs.com
|
|||
ecouser.net
|
||||
*.ecouser.net
|
||||
ecovacs.net
|
||||
*.ecovacs.net
|
||||
*.ecovacs.net
|
||||
*.ww.ecouser.net
|
||||
*.dc-eu.ww.ecouser.net
|
||||
*.dc.ww.ecouser.net
|
||||
*.area.ww.ecouser.net
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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+) |
|
||||
|
|
@ -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
|
||||
````
|
||||
````
|
||||
|
||||
# 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.
|
||||
|
|
|
|||
45
example/docker-compose/docker-compose.yaml
Normal file
45
example/docker-compose/docker-compose.yaml
Normal file
|
|
@ -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
|
||||
41
example/docker-compose/nginx/nginx.conf
Normal file
41
example/docker-compose/nginx/nginx.conf
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
14
tests/test_models.py
Normal file
14
tests/test_models.py
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue