Compare commits

..

11 commits

Author SHA1 Message Date
Brian Martin
fe8e4b0615 show <bytes content> for binary/images 2020-01-20 12:49:19 -05:00
Brian Martin
f97c07ac7b handle images and bytes
handle images and bytes
2020-01-20 12:36:36 -05:00
Brian Martin
346895eff3 fix tests 2020-01-20 12:07:05 -05:00
Brian Martin
c2bcb13769 support android app
handle form data sent by android apps
2020-01-20 12:01:01 -05:00
Brian Martin
a4a77708e5 add bumper api to proxymode
add bumper api to proxymode
2020-01-20 11:27:22 -05:00
Brian Martin
904167a8c5 proxymode db
move settings to db and rework code to support
2020-01-20 10:30:10 -05:00
Brian Martin
bdc452b99c reset connection status at startup
reset bot and client connections to false for xmpp/mqtt at startup
2020-01-20 08:34:08 -05:00
Brian Martin
3b7b31af47 log client topic subscription
log client topic subscription
2020-01-20 08:24:31 -05:00
Brian Martin
3e8ad68eb2 slim helperbot subscriptions
replace subscriptions with consolidated iot/#
2020-01-20 08:15:02 -05:00
Brian Martin
aca40e0e57 mqtt proxy
- mqtt and confserver proxy mode working
2020-01-20 00:51:22 -05:00
Brian Martin
d428c5019b confserver proxy
- app is proxying correctly
2020-01-18 13:43:25 -05:00
24 changed files with 1412 additions and 2859 deletions

View file

@ -1,3 +1,20 @@
# Logs
logs
# Tests
tests
# Examples
examples
# Docs
docs
# Certs
certs
.coverage
# Markdown # Markdown
*.md *.md

View file

@ -22,15 +22,11 @@ RUN apk add build-base
FROM base FROM base
COPY requirements.txt /requirements.txt COPY . /bumper
WORKDIR /bumper
# install required python packages # install required python packages
RUN pip3 install -r requirements.txt 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"] ENTRYPOINT ["python3", "-m", "bumper"]

View file

@ -26,13 +26,10 @@ def strtobool(strbool):
# os.environ['PYTHONASYNCIODEBUG'] = '1' # Uncomment to enable ASYNCIODEBUG # os.environ['PYTHONASYNCIODEBUG'] = '1' # Uncomment to enable ASYNCIODEBUG
bumper_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) bumper_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
log_to_stdout = os.environ.get("LOG_TO_STDOUT")
# Set defaults from environment variables first # Set defaults from environment variables first
# Folders # Folders
if not log_to_stdout: logs_dir = os.environ.get("BUMPER_LOGS") or os.path.join(bumper_dir, "logs")
logs_dir = os.environ.get("BUMPER_LOGS") or os.path.join(bumper_dir, "logs") os.makedirs(logs_dir, exist_ok=True) # Ensure logs directory exists or create
os.makedirs(logs_dir, exist_ok=True) # Ensure logs directory exists or create
data_dir = os.environ.get("BUMPER_DATA") or os.path.join(bumper_dir, "data") data_dir = os.environ.get("BUMPER_DATA") or os.path.join(bumper_dir, "data")
os.makedirs(data_dir, exist_ok=True) # Ensure data directory exists or create os.makedirs(data_dir, exist_ok=True) # Ensure data directory exists or create
certs_dir = os.environ.get("BUMPER_CERTS") or os.path.join(bumper_dir, "certs") certs_dir = os.environ.get("BUMPER_CERTS") or os.path.join(bumper_dir, "certs")
@ -57,8 +54,8 @@ bumper_announce_ip = os.environ.get("BUMPER_ANNOUNCE_IP") or bumper_listen
bumper_debug = strtobool(os.environ.get("BUMPER_DEBUG")) or False bumper_debug = strtobool(os.environ.get("BUMPER_DEBUG")) or False
use_auth = False use_auth = False
token_validity_seconds = 3600 # 1 hour token_validity_seconds = 3600 # 1 hour
oauth_validity_days = 15
db = None db = None
bumper_proxy_mode = strtobool(os.environ.get("BUMPER_PROXY_MODE")) or False
mqtt_server = None mqtt_server = None
mqtt_helperbot = None mqtt_helperbot = None
@ -84,102 +81,83 @@ logformat = logging.Formatter(
) )
bumperlog = logging.getLogger("bumper") bumperlog = logging.getLogger("bumper")
if not log_to_stdout: bumper_rotate = RotatingFileHandler("logs/bumper.log", maxBytes=5000000, backupCount=5)
bumper_rotate = RotatingFileHandler("logs/bumper.log", maxBytes=5000000, backupCount=5) bumper_rotate.setFormatter(logformat)
bumper_rotate.setFormatter(logformat) bumperlog.addHandler(bumper_rotate)
bumperlog.addHandler(bumper_rotate)
else:
bumperlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level # Override the logging level
# bumperlog.setLevel(logging.INFO) # bumperlog.setLevel(logging.INFO)
confserverlog = logging.getLogger("confserver") confserverlog = logging.getLogger("confserver")
if not log_to_stdout: conf_rotate = RotatingFileHandler(
conf_rotate = RotatingFileHandler( "logs/confserver.log", maxBytes=5000000, backupCount=5
"logs/confserver.log", maxBytes=5000000, backupCount=5 )
) conf_rotate.setFormatter(logformat)
conf_rotate.setFormatter(logformat) confserverlog.addHandler(conf_rotate)
confserverlog.addHandler(conf_rotate)
else:
confserverlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level # Override the logging level
# confserverlog.setLevel(logging.INFO) # confserverlog.setLevel(logging.INFO)
mqttserverlog = logging.getLogger("mqttserver") mqttserverlog = logging.getLogger("mqttserver")
if not log_to_stdout: mqtt_rotate = RotatingFileHandler(
mqtt_rotate = RotatingFileHandler( "logs/mqttserver.log", maxBytes=5000000, backupCount=5
"logs/mqttserver.log", maxBytes=5000000, backupCount=5 )
) mqtt_rotate.setFormatter(logformat)
mqtt_rotate.setFormatter(logformat) mqttserverlog.addHandler(mqtt_rotate)
mqttserverlog.addHandler(mqtt_rotate) # Override the logging level
else: # mqttserverlog.setLevel(logging.INFO)
mqttserverlog.addHandler(logging.StreamHandler(sys.stdout))
proxymodelog = logging.getLogger("proxymode")
proxymode_rotate = RotatingFileHandler(
"logs/proxymode.log", maxBytes=5000000, backupCount=5
)
proxymode_rotate.setFormatter(logformat)
proxymodelog.addHandler(proxymode_rotate)
# Override the logging level # Override the logging level
# mqttserverlog.setLevel(logging.INFO) # mqttserverlog.setLevel(logging.INFO)
### Additional MQTT Logs ### Additional MQTT Logs
translog = logging.getLogger("transitions") translog = logging.getLogger("transitions")
if not log_to_stdout: translog.addHandler(mqtt_rotate)
translog.addHandler(mqtt_rotate)
else:
translog.addHandler(logging.StreamHandler(sys.stdout))
translog.setLevel(logging.CRITICAL + 1) # Ignore this logger translog.setLevel(logging.CRITICAL + 1) # Ignore this logger
logging.getLogger("passlib").setLevel(logging.CRITICAL + 1) # Ignore this logger logging.getLogger("passlib").setLevel(logging.CRITICAL + 1) # Ignore this logger
brokerlog = logging.getLogger("hbmqtt.broker") brokerlog = logging.getLogger("hbmqtt.broker")
#brokerlog.setLevel( #brokerlog.setLevel(
# logging.CRITICAL + 1 # logging.CRITICAL + 1
#) # Ignore this logger #There are some sublogs that could be set if needed (.plugins) #) # Ignore this logger #There are some sublogs that could be set if needed (.plugins)
if not log_to_stdout: brokerlog.addHandler(mqtt_rotate)
brokerlog.addHandler(mqtt_rotate)
else:
brokerlog.addHandler(logging.StreamHandler(sys.stdout))
protolog = logging.getLogger("hbmqtt.mqtt.protocol") protolog = logging.getLogger("hbmqtt.mqtt.protocol")
#protolog.setLevel( #protolog.setLevel(
# logging.CRITICAL + 1 # logging.CRITICAL + 1
#) # Ignore this logger #) # Ignore this logger
if not log_to_stdout: protolog.addHandler(mqtt_rotate)
protolog.addHandler(mqtt_rotate)
else:
protolog.addHandler(logging.StreamHandler(sys.stdout))
clientlog = logging.getLogger("hbmqtt.client") clientlog = logging.getLogger("hbmqtt.client")
#clientlog.setLevel(logging.CRITICAL + 1) # Ignore this logger #clientlog.setLevel(logging.CRITICAL + 1) # Ignore this logger
if not log_to_stdout: clientlog.addHandler(mqtt_rotate)
clientlog.addHandler(mqtt_rotate)
else:
clientlog.addHandler(logging.StreamHandler(sys.stdout))
helperbotlog = logging.getLogger("helperbot") helperbotlog = logging.getLogger("helperbot")
if not log_to_stdout: helperbot_rotate = RotatingFileHandler(
helperbot_rotate = RotatingFileHandler( "logs/helperbot.log", maxBytes=5000000, backupCount=5
"logs/helperbot.log", maxBytes=5000000, backupCount=5 )
) helperbot_rotate.setFormatter(logformat)
helperbot_rotate.setFormatter(logformat) helperbotlog.addHandler(helperbot_rotate)
helperbotlog.addHandler(helperbot_rotate)
else:
helperbotlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level # Override the logging level
# helperbotlog.setLevel(logging.INFO) # helperbotlog.setLevel(logging.INFO)
boterrorlog = logging.getLogger("boterror") boterrorlog = logging.getLogger("boterror")
if not log_to_stdout: boterrorlog_rotate = RotatingFileHandler(
boterrorlog_rotate = RotatingFileHandler( "logs/boterror.log", maxBytes=5000000, backupCount=5
"logs/boterror.log", maxBytes=5000000, backupCount=5 )
) boterrorlog_rotate.setFormatter(logformat)
boterrorlog_rotate.setFormatter(logformat) boterrorlog.addHandler(boterrorlog_rotate)
boterrorlog.addHandler(boterrorlog_rotate)
else:
boterrorlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level # Override the logging level
# boterrorlog.setLevel(logging.INFO) # boterrorlog.setLevel(logging.INFO)
xmppserverlog = logging.getLogger("xmppserver") xmppserverlog = logging.getLogger("xmppserver")
if not log_to_stdout: xmpp_rotate = RotatingFileHandler(
xmpp_rotate = RotatingFileHandler( "logs/xmppserver.log", maxBytes=5000000, backupCount=5
"logs/xmppserver.log", maxBytes=5000000, backupCount=5 )
) xmpp_rotate.setFormatter(logformat)
xmpp_rotate.setFormatter(logformat) xmppserverlog.addHandler(xmpp_rotate)
xmppserverlog.addHandler(xmpp_rotate)
else:
xmppserverlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level # Override the logging level
# xmppserverlog.setLevel(logging.INFO) # xmppserverlog.setLevel(logging.INFO)
@ -193,6 +171,11 @@ xmpp_listen_port = 5223
async def start(): async def start():
#config_proxyMode_deleteTable() #delete existing proxymode table
#Reset xmpp/mqtt to false in database for bots and clients
bot_reset_connectionStatus()
client_reset_connectionStatus()
try: try:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -237,8 +220,9 @@ async def start():
xmpp_server = XMPPServer((bumper_listen, xmpp_listen_port)) xmpp_server = XMPPServer((bumper_listen, xmpp_listen_port))
# Start MQTT Server # Start MQTT Server
# await start otherwise we get an error connecting the helper bot asyncio.create_task(mqtt_server.broker_coro())
await asyncio.create_task(mqtt_server.broker_coro())
await asyncio.sleep(0.5) #Wait half a sec for broker to start
# Start MQTT Helperbot # Start MQTT Helperbot
asyncio.create_task(mqtt_helperbot.start_helper_bot()) asyncio.create_task(mqtt_helperbot.start_helper_bot())
@ -254,7 +238,22 @@ async def start():
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
# Start web servers # Start web servers
conf_server.confserver_app() if bumper_proxy_mode:
bumperlog.info("Proxy Mode Enabled")
if config_proxyMode_countEntries() == 0: # check if proxymode servers are entered
bumperlog.info("Proxy Mode - No Servers, Loading Defaults (US)")
config_proxyMode_defaults() # set defaults if 0
configproxy = config_proxyMode_getall()
cntentries = len(configproxy)
proxymodelog.info(f"Loaded {cntentries} entries from proxyconfig")
for entry in configproxy:
proxymodelog.info(f"Config Entry {entry}")
conf_server.confserver_proxy_app()
else:
conf_server.confserver_app()
asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf1_listen_port, usessl=True)) asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf1_listen_port, usessl=True))
asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf2_listen_port, usessl=False)) asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf2_listen_port, usessl=False))
@ -266,7 +265,6 @@ async def start():
async def maintenance(): async def maintenance():
revoke_expired_tokens() revoke_expired_tokens()
revoke_expired_oauths()
async def shutdown(): async def shutdown():
@ -385,12 +383,17 @@ def main(argv=None):
help="announce address to bots on checkin", help="announce address to bots on checkin",
) )
parser.add_argument("--debug", action="store_true", help="enable debug logs") parser.add_argument("--debug", action="store_true", help="enable debug logs")
parser.add_argument("--proxy-mode", action="store_true", help="enable proxy mode")
args = parser.parse_args(args=argv) args = parser.parse_args(args=argv)
if args.debug: if args.debug:
bumper_debug = True bumper_debug = True
if args.proxy_mode:
global bumper_proxy_mode
bumper_proxy_mode = True
if args.listen: if args.listen:
bumper_listen = args.listen bumper_listen = args.listen

View file

@ -1,16 +1,23 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import asyncio import json
import logging import logging
import os
import ssl 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 aiohttp
import aiohttp_jinja2 import aiohttp_jinja2
import jinja2 import jinja2
from aiohttp import web import uuid
import xml.etree.ElementTree as ET
from bumper import plugins
from bumper.models import *
class aiohttp_filter(logging.Filter): class aiohttp_filter(logging.Filter):
@ -35,6 +42,7 @@ logging.getLogger("aiohttp.access").addFilter(
aiohttp_filter() aiohttp_filter()
) # Add logging filter above to aiohttp.access ) # Add logging filter above to aiohttp.access
proxymodelog = logging.getLogger("proxymode")
class ConfServer: class ConfServer:
def __init__(self, address, usessl=False): def __init__(self, address, usessl=False):
@ -49,6 +57,115 @@ class ConfServer:
def get_milli_time(self, timetoconvert): def get_milli_time(self, timetoconvert):
return int(round(timetoconvert * 1000)) return int(round(timetoconvert * 1000))
def confserver_proxy_app(self):
self.app = web.Application(middlewares=[
self.log_all_requests,
])
aiohttp_jinja2.setup(self.app, loader=jinja2.FileSystemLoader(os.path.join(bumper.bumper_dir,"bumper","web","templates")))
self.app.add_routes(
[
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.route("*", "/{path:.*}", self.handle_proxy, name="confserver_proxy"),
]
)
return self.app
async def handle_proxy(self, request):
try:
ecoresp = ""
server_port = 443 #default to 443
if "_SSLProtocolTransport" != type(request.transport).__name__ and "_SelectorSocketTransport" != type(request.transport).__name__: #check not ssl transport class
if "_extra" in request.transport:
if "sockname" in request.transport._extra:
server_port = request.transport._extra["sockname"][1]
if request.raw_path == "/":
return await self.handle_base(request)
if request.raw_path == "/lookup.do":
return await self.handle_lookup(request) #use bumper to handle lookup so bot gets Bumper IP and not Ecovacs
matchproxy = bumper.config_proxyMode_getServerIP("app", request.host)
if matchproxy:
proxymodelog.info(f"Matched {request.host} to entry in proxyconfig!")
ecorequest = f"{request.scheme}://{matchproxy}"
else:
proxymodelog.info(f"No match for {request.host} in proxyconfig!")
if "ecovacs.com" in request.host:
proxymodelog.info(f"ecovacs.com in {request.host} defaulting to ecovacs.com IP!")
matchproxy = bumper.config_proxyMode_getServerIP("app", "ecovacs.com")
ecorequest = f"{request.scheme}://{matchproxy}"
elif "ecouser.net" in request.host:
proxymodelog.info(f"ecouser.net in {request.host} defaulting to ecouser.net IP!")
matchproxy = bumper.config_proxyMode_getServerIP("app", "ecouser.net")
ecorequest = f"{request.scheme}://{matchproxy}"
else:
proxymodelog.info(f"No matches for {request.host} defaulting to ecovacs.com IP!")
matchproxy = bumper.config_proxyMode_getServerIP("app", "ecovacs.com")
ecorequest = f"{request.scheme}://{matchproxy}"
if server_port != 443:
ecorequest = f"{ecorequest}:{server_port}"
proxymodelog.info(f"{request.host} - {ecorequest}")
ecorequest = f"{ecorequest}{request.path_qs}"
requestheaders = {'host': request.host}
async with aiohttp.ClientSession(headers=requestheaders, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
if request.content.total_bytes > 0:
proxymodelog.info(f"HTTP Proxy Request to EcoVacs (body=true) (host:{request.host}) - {ecorequest} - {request._read_bytes}")
if request.content_type == "application/x-www-form-urlencoded": # android apps use form
fdata = await request.post()
async with session.request(request.method, ecorequest, data=fdata) as resp:
ecoresp = await resp.text()
proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}")
else: # handle json
jdata = request._read_bytes.decode('utf8')
jdata = json.loads(jdata)
async with session.request(request.method, ecorequest, json=jdata) as resp:
ecoresp = await resp.text()
proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}")
else:
proxymodelog.info(f"HTTP Proxy Request to EcoVacs (body=false) (host:{request.host}) - {ecorequest}")
async with session.request(request.method, ecorequest) as resp:
if resp.content_type == "application/octet-stream":
ecoresp = await resp.read()
proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - <BYTES CONTENT>")
else:
ecoresp = await resp.text()
proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}")
if resp.status == 200:
if resp.content_type == "application/json":
ecoresp = json.loads(ecoresp)
return web.json_response(ecoresp)
elif resp.content_type == "application/octet-stream":
return web.Response(body=ecoresp)
else:
return web.Response(text=ecoresp)
else:
return web.Response(text=ecoresp)
except asyncio.CancelledError as e:
proxymodelog.error(f"Request cancelled or timeout - {ecorequest} - {jdata}")
return web.Response(text="")
pass
except Exception as e:
proxymodelog.exception("{}".format(e))
return web.Response(text="")
def confserver_app(self): def confserver_app(self):
self.app = web.Application(loop=asyncio.get_event_loop(), middlewares=[ self.app = web.Application(loop=asyncio.get_event_loop(), middlewares=[
self.log_all_requests, self.log_all_requests,
@ -57,12 +174,13 @@ class ConfServer:
self.app.add_routes( 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("/bot/remove/{did}", self.handle_RemoveBot, name='remove-bot'),
web.get("/client/remove/{resource}", self.handle_RemoveClient, name='remove-client'), web.get("/client/remove/{resource}", self.handle_RemoveClient, name='remove-client'),
web.get("/restart_{service}", self.handle_RestartService, name='restart-service'), web.get("/restart_{service}", self.handle_RestartService, name='restart-service'),
web.post("/lookup.do", self.handle_lookup), web.post("/lookup.do", self.handle_lookup),
web.post("/newauth.do", self.handle_newauth),
] ]
) )
@ -104,9 +222,11 @@ class ConfServer:
async def start_site(self, app, address='localhost', port=8080, usessl=False): async def start_site(self, app, address='localhost', port=8080, usessl=False):
runner = web.AppRunner(app) runner = web.AppRunner(app)
self.runners.append(runner) self.runners.append(runner)
await runner.setup() await runner.setup()
if usessl: if usessl:
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key) ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key)
@ -124,6 +244,36 @@ class ConfServer:
await site.start() await site.start()
def start_site_thread(self, app, address='localhost', port=8080, usessl=False):
#test for new thread and loop
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
runner = web.AppRunner(app)
self.runners.append(runner)
#await runner.setup()
loop.run_until_complete(runner.setup()) #for thread test
if usessl:
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key)
site = web.TCPSite(
runner,
host=address,
port=port,
ssl_context=ssl_ctx,
)
else:
site = web.TCPSite(
runner, host=address, port=port
)
#await site.start()
#for thread test
loop.run_until_complete(site.start())
loop.run_forever()
async def start_server(self): async def start_server(self):
try: try:
confserverlog.info( confserverlog.info(
@ -211,18 +361,8 @@ class ConfServer:
async def log_all_requests(self, request, handler): async def log_all_requests(self, request, handler):
if request._match_info.route.name not in self.excludelogging: 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: try:
postbody = None
if request.content_length: if request.content_length:
if request.content_type == "application/x-www-form-urlencoded": if request.content_type == "application/x-www-form-urlencoded":
postbody = await request.post() postbody = await request.post()
@ -236,33 +376,100 @@ class ConfServer:
else: else:
postbody = await request.post() postbody = await request.post()
else:
to_log["request"]["body"] = f"{postbody}" postbody = None
response = await handler(request) response = await handler(request)
if response is None: if response:
confserverlog.warning("Response was null!") if not "application/octet-stream" in response.content_type:
confserverlog.warning(json.dumps(to_log)) try:
return response 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"] = { "response": {
"status": f"{response.status}", #"response_body": f"{json.loads(response.body)}",
} "response_body": f"{json.loads(response.text)}",
if not "application/octet-stream" in response.content_type: "status": f"{response.status}",
to_log["response"]["body"] = f"{json.loads(response.body)}" }
}
except Exception as e:
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}",
},
confserverlog.debug(json.dumps(to_log)) "response": {
#"response_body": f"{json.loads(response.body)}",
"response_body": f"{(response.text)}",
"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))
return response return response
except web.HTTPNotFound as notfound: except web.HTTPNotFound as notfound:
confserverlog.debug("Request path {} not found".format(request.raw_path)) confserverlog.debug("Request path {} not found".format(request.raw_path))
confserverlog.debug(json.dumps(to_log)) 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))
return notfound return notfound
except Exception as e: except Exception as e:
confserverlog.exception("{}".format(e)) confserverlog.exception("{}".format(e))
confserverlog.error(json.dumps(to_log)) 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))
return e return e
else: else:
@ -466,26 +673,6 @@ class ConfServer:
except Exception as e: except Exception as e:
confserverlog.exception("{}".format(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): async def disconnect(self):
try: try:

View file

@ -1,13 +1,13 @@
#!/usr/bin/env python3 #!/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 json
import logging 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") bumperlog = logging.getLogger("bumper")
@ -32,10 +32,65 @@ def db_get():
db.table("clients", cache_size=0) db.table("clients", cache_size=0)
db.table("bots", cache_size=0) db.table("bots", cache_size=0)
db.table("tokens", cache_size=0) db.table("tokens", cache_size=0)
db.table("oauth", cache_size=0) db.table("config_proxymode", cache_size=0)
return db return db
def config_proxyMode_deleteTable():
opendb = db_get()
opendb.purge_table("config_proxymode")
def config_proxyMode_defaults():
defaults = [
{"type":"app","host":"gl-us-api.ecovacs.com","ip":"47.252.51.29","match":"gl-"},
{"type":"app","host":"gl-us-openapi.ecovacs.com","ip":"47.252.51.29"},
{"type":"app","host":"portal-ww.ecouser.net","ip":"47.88.66.164","match":"portal-"},
{"type":"app","host":"bigdata-northamerica.ecovacs.com","ip":"47.88.66.111"},
{"type":"app","host":"bigdata-international.ecovacs.com","ip":"47.88.132.151","match":"bigdata-"},
{"type":"app","host":"eco-us-api.ecovacs.com","ip":"47.89.135.130","match":"eco-"},
{"type":"app","host":"ecovacs.com","ip":"47.90.210.46"},
{"type":"app","host":"ecouser.net","ip":"116.62.93.217"},
{"type":"mqtt_server","host":"mq-ww.ecouser.net","ip":"47.254.52.46"},
]
opendb = db_get()
with opendb:
config = opendb.table("config_proxymode")
config.insert_multiple(defaults)
def config_proxyMode_getServerIP(type, host):
opendb = db_get()
with opendb:
proxyconfig = opendb.table("config_proxymode")
proxy = Query()
if type == "mqtt_server":
entry = proxyconfig.get((proxy.type == type))
else:
entry = proxyconfig.get((proxy.type == type) & (proxy.host == host))
if entry:
return entry["ip"]
else:
proxylist = proxyconfig.search(Query())
for proxy in proxylist: # check for sub matches
if "match" in proxy:
if proxy["match"] in host:
return proxy["ip"]
return None
def config_proxyMode_countEntries():
opendb = db_get()
with opendb:
config = opendb.table("config_proxymode")
return len(config)
def config_proxyMode_getall():
opendb = db_get()
with opendb:
config = opendb.table("config_proxymode")
return config.search(Query())
def user_add(userid): def user_add(userid):
newuser = BumperUser() newuser = BumperUser()
@ -203,55 +258,6 @@ 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(): def get_disconnected_xmpp_clients():
clients = db_get().table("clients") clients = db_get().table("clients")
Client = Query() Client = Query()
@ -264,8 +270,8 @@ def check_authcode(uid, authcode):
tmpauth = tokens.get( tmpauth = tokens.get(
(Query().authcode == authcode) (Query().authcode == authcode)
& ( # Match authcode & ( # Match authcode
(Query().userid == uid.replace("fuid_", "")) (Query().userid == uid.replace("fuid_", ""))
| (Query().userid == "fuid_{}".format(uid)) | (Query().userid == "fuid_{}".format(uid))
) # Userid with or without fuid_ ) # Userid with or without fuid_
) )
if tmpauth: if tmpauth:
@ -296,8 +302,8 @@ def check_token(uid, token):
tmpauth = tokens.get( tmpauth = tokens.get(
(Query().token == token) (Query().token == token)
& ( # Match token & ( # Match token
(Query().userid == uid.replace("fuid_", "")) (Query().userid == uid.replace("fuid_", ""))
| (Query().userid == "fuid_{}".format(uid)) | (Query().userid == "fuid_{}".format(uid))
) # Userid with or without fuid_ ) # Userid with or without fuid_
) )
if tmpauth: if tmpauth:
@ -325,7 +331,7 @@ def bot_add(sn, did, devclass, resource, company):
bot = bot_get(did) bot = bot_get(did)
if not bot: # Not existing bot in database if not bot: # Not existing bot in database
if ( 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 ): # try to prevent bad additions to the bot list
bumperlog.info( bumperlog.info(
"Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did) "Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did)
@ -339,6 +345,11 @@ def bot_remove(did):
if bot: if bot:
bots.remove(doc_ids=[bot.doc_id]) bots.remove(doc_ids=[bot.doc_id])
def bot_reset_connectionStatus():
bots = db_get().table("bots")
for bot in bots:
bot_set_mqtt(bot["did"], False)
bot_set_xmpp(bot["did"], False)
def bot_get(did): def bot_get(did):
bots = db_get().table("bots") bots = db_get().table("bots")
@ -352,21 +363,6 @@ def bot_toEcoVacsHome_JSON(bot): # EcoVacs Home
bot["UILogicId"] = botprod["product"]["UILogicId"] bot["UILogicId"] = botprod["product"]["UILogicId"]
bot["ota"] = botprod["product"]["ota"] bot["ota"] = botprod["product"]["ota"]
bot["icon"] = botprod["product"]["iconUrl"] 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( return json.dumps(
bot, default=lambda o: o.__dict__, sort_keys=False bot, default=lambda o: o.__dict__, sort_keys=False
) # , indent=4) ) # , indent=4)
@ -410,6 +406,11 @@ def client_add(userid, realm, resource):
bumperlog.info("Adding new client with resource {}".format(newclient.resource)) bumperlog.info("Adding new client with resource {}".format(newclient.resource))
client_full_upsert(newclient.asdict()) client_full_upsert(newclient.asdict())
def client_reset_connectionStatus():
clients = db_get().table("clients")
for client in clients:
client_set_mqtt(client["resource"], False)
client_set_xmpp(client["resource"], False)
def client_remove(resource): def client_remove(resource):
clients = db_get().table("clients") clients = db_get().table("clients")
@ -417,7 +418,6 @@ def client_remove(resource):
if client: if client:
clients.remove(doc_ids=[client.doc_id]) clients.remove(doc_ids=[client.doc_id])
def client_get(resource): def client_get(resource):
clients = db_get().table("clients") clients = db_get().table("clients")
Client = Query() Client = Query()

File diff suppressed because it is too large Load diff

View file

@ -14,10 +14,21 @@ import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
import bumper import bumper
from passlib.apps import custom_app_context as pwd_context from passlib.apps import custom_app_context as pwd_context
import ssl
import tempfile
from urllib.parse import urlparse, urlunparse
from hbmqtt.mqtt.protocol.client_handler import ClientProtocolHandler
from hbmqtt.adapters import StreamReaderAdapter, StreamWriterAdapter, WebSocketsReader, WebSocketsWriter
from websockets.uri import InvalidURI
from websockets.exceptions import InvalidHandshake
from hbmqtt.mqtt.protocol.handler import ProtocolHandlerException
from hbmqtt.mqtt.connack import CONNECTION_ACCEPTED
helperbotlog = logging.getLogger("helperbot") helperbotlog = logging.getLogger("helperbot")
boterrorlog = logging.getLogger("boterror") boterrorlog = logging.getLogger("boterror")
mqttserverlog = logging.getLogger("mqttserver") mqttserverlog = logging.getLogger("mqttserver")
proxymodelog = logging.getLogger("proxymode")
class MQTTHelperBot: class MQTTHelperBot:
@ -44,9 +55,7 @@ class MQTTHelperBot:
) )
await self.Client.subscribe( await self.Client.subscribe(
[ [
("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+", QOS_0), ("iot/#", QOS_0),
("iot/p2p/+", QOS_0),
("iot/atr/+", QOS_0),
] ]
) )
@ -216,8 +225,118 @@ class MQTTServer:
except Exception as e: except Exception as e:
mqttserverlog.exception("{}".format(e)) mqttserverlog.exception("{}".format(e))
class BumperProxyModeMQTTClient(MQTTClient):
ecohelpername = ""
async def _connect_coro(self): #Override default to ignore ssl verification
kwargs = dict()
# Decode URI attributes
uri_attributes = urlparse(self.session.broker_uri)
scheme = uri_attributes.scheme
secure = True if scheme in ('mqtts', 'wss') else False
self.session.username = self.session.username if self.session.username else uri_attributes.username
self.session.password = self.session.password if self.session.password else uri_attributes.password
self.session.remote_address = uri_attributes.hostname
self.session.remote_port = uri_attributes.port
if scheme in ('mqtt', 'mqtts') and not self.session.remote_port:
self.session.remote_port = 8883 if scheme == 'mqtts' else 1883
if scheme in ('ws', 'wss') and not self.session.remote_port:
self.session.remote_port = 443 if scheme == 'wss' else 80
if scheme in ('ws', 'wss'):
# Rewrite URI to conform to https://tools.ietf.org/html/rfc6455#section-3
uri = (scheme, self.session.remote_address + ":" + str(self.session.remote_port), uri_attributes[2],
uri_attributes[3], uri_attributes[4], uri_attributes[5])
self.session.broker_uri = urlunparse(uri)
# Init protocol handler
#if not self._handler:
self._handler = ClientProtocolHandler(self.plugins_manager, loop=self._loop)
if secure:
sc = ssl.create_default_context(
ssl.Purpose.SERVER_AUTH,
cafile=self.session.cafile,
capath=self.session.capath,
cadata=self.session.cadata)
if 'certfile' in self.config and 'keyfile' in self.config:
sc.load_cert_chain(self.config['certfile'], self.config['keyfile'])
if 'check_hostname' in self.config and isinstance(self.config['check_hostname'], bool):
sc.check_hostname = self.config['check_hostname']
sc.verify_mode = ssl.CERT_NONE #Ignore verify of cert
kwargs['ssl'] = sc
try:
reader = None
writer = None
self._connected_state.clear()
# Open connection
if scheme in ('mqtt', 'mqtts'):
conn_reader, conn_writer = \
await asyncio.open_connection(
self.session.remote_address,
self.session.remote_port, loop=self._loop, **kwargs)
reader = StreamReaderAdapter(conn_reader)
writer = StreamWriterAdapter(conn_writer)
elif scheme in ('ws', 'wss'):
websocket = await websockets.connect(
self.session.broker_uri,
subprotocols=['mqtt'],
loop=self._loop,
extra_headers=self.extra_headers,
**kwargs)
reader = WebSocketsReader(websocket)
writer = WebSocketsWriter(websocket)
# Start MQTT protocol
self._handler.attach(self.session, reader, writer)
return_code = await self._handler.mqtt_connect()
if return_code is not CONNECTION_ACCEPTED:
self.session.transitions.disconnect()
self.logger.warning("Connection rejected with code '%s'" % return_code)
exc = ConnectException("Connection rejected by broker")
exc.return_code = return_code
raise exc
else:
# Handle MQTT protocol
await self._handler.start()
self.session.transitions.connect()
self._connected_state.set()
self.logger.debug("connected to %s:%s" % (self.session.remote_address, self.session.remote_port))
return return_code
except InvalidURI as iuri:
self.logger.warning("connection failed: invalid URI '%s'" % self.session.broker_uri)
self.session.transitions.disconnect()
raise ConnectException("connection failed: invalid URI '%s'" % self.session.broker_uri, iuri)
except InvalidHandshake as ihs:
self.logger.warning("connection failed: invalid websocket handshake")
self.session.transitions.disconnect()
raise ConnectException("connection failed: invalid websocket handshake", ihs)
except (ProtocolHandlerException, ConnectionError, OSError) as e:
self.logger.warning("MQTT connection failed: %r" % e)
self.session.transitions.disconnect()
raise ConnectException(e)
async def get_msg(self):
try:
while self._connected_state._value:
message = await self.deliver_message()
msgdata = str(message.data.decode("utf-8"))
proxymodelog.info(f"MQTT Proxy Client - Message Received From Ecovacs - Topic: {message.topic} - Message: {msgdata}")
ttopic = message.topic.split("/")
self.ecohelpername = ttopic[3]
ttopic[3] = "proxyhelper"
ttopic_comb = "/".join(ttopic)
proxymodelog.info(f"MQTT Proxy Client - Converted Topic From {message.topic} TO {ttopic_comb}")
proxymodelog.info(f"MQTT Proxy Client - Proxy Forward Message to Helperbot - Topic: {ttopic_comb} - Message: {msgdata.encode()}")
await bumper.mqtt_helperbot.Client.publish(
ttopic_comb, msgdata.encode(), QOS_0
)
except Exception as e:
proxymodelog.error(f"MQTT Proxy Client - get_msg Exception - {e}")
class BumperMQTTServer_Plugin: class BumperMQTTServer_Plugin:
proxyclients = {}
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
try: try:
@ -232,6 +351,8 @@ class BumperMQTTServer_Plugin:
except Exception as e: except Exception as e:
mqttserverlog.exception("{}".format(e)) mqttserverlog.exception("{}".format(e))
async def authenticate(self, *args, **kwargs): async def authenticate(self, *args, **kwargs):
authenticated = False authenticated = False
@ -257,6 +378,32 @@ class BumperMQTTServer_Plugin:
mqttserverlog.info(f"Bumper Authentication Success - Bot - SN: {username} - DID: {didsplit[0]} - Class: {tmpbotdetail[0]}") mqttserverlog.info(f"Bumper Authentication Success - Bot - SN: {username} - DID: {didsplit[0]} - Class: {tmpbotdetail[0]}")
authenticated = True authenticated = True
if authenticated and bumper.bumper_proxy_mode:
mqtt_server = bumper.config_proxyMode_getServerIP("mqtt_server","")
if mqtt_server:
proxymodelog.info(f"MQTT Proxy Mode - Using server {mqtt_server}")
else:
proxymodelog.error(f"MQTT Proxy Mode - No server found! Load defaults or set mqtt_server in config_proxymode table!")
proxymodelog.exception(f"MQTT Proxy Mode - Exiting due to no MQTT Server configured!")
exit(1)
proxymodelog.info(f"MQTT Proxy Mode - Proxy Bot to MQTT - Client_id: {client_id} - Username: {username}")
self.proxyclients[client_id] = BumperProxyModeMQTTClient(
client_id=client_id, config={"check_hostname": False}
)
try:
await self.proxyclients[client_id].connect(
f"mqtts://{username}:{password}@{mqtt_server}:8883",
)
except Exception as e:
mqttserverlog.error(f"MQTT Proxy Mode - Exception connecting with proxy to ecovacs - {e}")
pass
proxymodelog.info(f"MQTT Proxy Mode - Proxy Bot Connected - Client_id: {client_id}")
asyncio.create_task(self.proxyclients[client_id].get_msg())
else: else:
tmpclientdetail = str(didsplit[1]).split("/") tmpclientdetail = str(didsplit[1]).split("/")
userid = didsplit[0] userid = didsplit[0]
@ -327,6 +474,20 @@ class BumperMQTTServer_Plugin:
except FileNotFoundError: except FileNotFoundError:
self.context.logger.warning(f"Password file {password_file} not found") self.context.logger.warning(f"Password file {password_file} not found")
async def on_broker_client_subscribed(self, client_id, topic, qos):
if bumper.bumper_proxy_mode: #if proxy mode, also subscribe on ecovacs server
if client_id in self.proxyclients:
await self.proxyclients[client_id].subscribe(
[
(topic, qos)
]
)
else:
proxymodelog.info(f"MQTT Proxy Mode - New MQTT Topic Subscription - Client: {client_id} - Topic: {topic}")
#return
#pass
async def on_broker_client_connected(self, client_id): async def on_broker_client_connected(self, client_id):
didsplit = str(client_id).split("@") didsplit = str(client_id).split("@")
@ -336,16 +497,39 @@ class BumperMQTTServer_Plugin:
bumper.bot_set_mqtt(bot["did"], True) bumper.bot_set_mqtt(bot["did"], True)
return return
clientresource = didsplit[1].split("/")[1] if len(didsplit) > 1:
client = bumper.client_get(clientresource) clientresource = didsplit[1].split("/")[1]
if client: client = bumper.client_get(clientresource)
bumper.client_set_mqtt(client["resource"], True) if client:
return bumper.client_set_mqtt(client["resource"], True)
return
async def on_broker_message_received(self, client_id, message): async def on_broker_message_received(self, client_id, message):
self.handle_helperbot_msg(client_id, message) await self.handle_helperbot_msg(client_id, message)
async def handle_helperbot_msg(self, client_id, message):
if bumper.bumper_proxy_mode:
if client_id in self.proxyclients:
msgdata = str(message.data.decode("utf-8"))
if not str(message.topic).split("/")[3] == "proxyhelper": # if from proxyhelper, don't send back to ecovacs...yet
if str(message.topic).split("/")[6] == "proxyhelper":
ttopic = message.topic.split("/")
ttopic[6] = self.proxyclients[client_id].ecohelpername
ttopic_join = "/".join(ttopic)
proxymodelog.info(f"MQTT Proxy Client - Bot Message Converted Topic From {message.topic} TO {ttopic_join} with message: {msgdata}")
else:
ttopic_join = message.topic
proxymodelog.info(f"MQTT Proxy Client - Bot Message From {ttopic_join} with message: {msgdata}")
try:
# Send back to ecovacs
proxymodelog.info(f"MQTT Proxy Client - Proxy Forward Message to Ecovacs - Topic: {ttopic_join} - Message: {msgdata.encode()}")
await self.proxyclients[client_id].publish(
ttopic_join, msgdata.encode(), message.qos
)
except Exception as e:
proxymodelog.error(f"MQTT Proxy Client - Forwarding to Ecovacs Exception - {e}")
def handle_helperbot_msg(self, client_id, message):
if str(message.topic).split("/")[6] == "helperbot": if str(message.topic).split("/")[6] == "helperbot":
# Response to command # Response to command
@ -407,6 +591,10 @@ class BumperMQTTServer_Plugin:
async def on_broker_client_disconnected(self, client_id): async def on_broker_client_disconnected(self, client_id):
if bumper.bumper_proxy_mode:
if client_id in self.proxyclients:
await self.proxyclients[client_id].disconnect()
didsplit = str(client_id).split("@") didsplit = str(client_id).split("@")
bot = bumper.bot_get(didsplit[0]) bot = bumper.bot_get(didsplit[0])
@ -414,8 +602,9 @@ class BumperMQTTServer_Plugin:
bumper.bot_set_mqtt(bot["did"], False) bumper.bot_set_mqtt(bot["did"], False)
return return
clientresource = didsplit[1].split("/")[1] if len(didsplit) > 1:
client = bumper.client_get(clientresource) clientresource = didsplit[1].split("/")[1]
if client: client = bumper.client_get(clientresource)
bumper.client_set_mqtt(client["resource"], False) if client:
return bumper.client_set_mqtt(client["resource"], False)
return

View file

@ -1,10 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import logging import asyncio
from aiohttp import web from aiohttp import web
from bumper import plugins from bumper import plugins
import logging
import bumper
from bumper.models import * from bumper.models import *
from bumper import plugins
from datetime import datetime, timedelta
class portal_api_appsvr(plugins.ConfServerApp): class portal_api_appsvr(plugins.ConfServerApp):
@ -15,14 +17,14 @@ class portal_api_appsvr(plugins.ConfServerApp):
self.sub_api = "portal_api" self.sub_api = "portal_api"
self.routes = [ 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/app.do", self.handle_appsvr_api, name="portal_api_appsvr_app"),
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 self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time
async def handle_appsvr_app(self, request): async def handle_appsvr_api(self, request):
if not request.method == "GET": # Skip GET for now if not request.method == "GET": # Skip GET for now
try: try:
@ -155,57 +157,5 @@ class portal_api_appsvr(plugins.ConfServerApp):
body = {"result": "fail", "todo": "result"} body = {"result": "fail", "todo": "result"}
return web.json_response(body) 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() plugin = portal_api_appsvr()

View file

@ -1,38 +0,0 @@
#!/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()

View file

@ -29,7 +29,7 @@ class portal_api_iot(plugins.ConfServerApp):
try: try:
json_body = json.loads(await request.text()) json_body = json.loads(await request.text())
randomid = "".join(random.sample(string.ascii_letters, 4)) randomid = "".join(random.sample(string.ascii_letters, 6))
did = "" did = ""
if "toId" in json_body: # Its a command if "toId" in json_body: # Its a command
did = json_body["toId"] did = json_body["toId"]

View file

@ -1,14 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import logging import asyncio
import random
import string
import xml.etree.ElementTree as ET
from aiohttp import web from aiohttp import web
from bumper import plugins from bumper import plugins
import logging
import bumper
from bumper.models import * from bumper.models import *
from bumper import plugins
from datetime import datetime, timedelta
import os
import string
import random
import xml.etree.ElementTree as ET
class portal_api_lg(plugins.ConfServerApp): class portal_api_lg(plugins.ConfServerApp):
@ -26,11 +28,10 @@ class portal_api_lg(plugins.ConfServerApp):
self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time
async def handle_lg_log(self, request): # EcoVacs Home async def handle_lg_log(self, request): # EcoVacs Home
randomid = "".join(random.sample(string.ascii_letters, 6))
try: try:
json_body = json.loads(await request.text()) json_body = json.loads(await request.text())
randomid = "".join(random.sample(string.ascii_letters, 6))
did = json_body["did"] did = json_body["did"]
botdetails = bumper.bot_get(did) botdetails = bumper.bot_get(did)
@ -52,7 +53,7 @@ class portal_api_lg(plugins.ConfServerApp):
json_body["payloadType"] = "x" json_body["payloadType"] = "x"
if not "payload" in json_body: if not "payload" in json_body:
# json_body["payload"] = "" #json_body["payload"] = ""
if json_body["td"] == "GetCleanLogs": if json_body["td"] == "GetCleanLogs":
json_body["td"] = "q" json_body["td"] = "q"
json_body["payload"] = '<ctl count="30"/>' json_body["payload"] = '<ctl count="30"/>'
@ -76,8 +77,8 @@ class portal_api_lg(plugins.ConfServerApp):
"area": l.attrib['a'], "area": l.attrib['a'],
"last": l.attrib['l'], "last": l.attrib['l'],
"cleanType": l.attrib['t'], "cleanType": l.attrib['t'],
# imageUrl allows for providing images of cleanings, something to look into later #imageUrl allows for providing images of cleanings, something to look into later
# "imageUrl": "https://localhost:8007", #"imageUrl": "https://localhost:8007",
} }
logs.append(cleanlog) logs.append(cleanlog)
body = { body = {
@ -97,12 +98,11 @@ class portal_api_lg(plugins.ConfServerApp):
json_body["toId"] json_body["toId"]
) )
) )
body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"}
return web.json_response(body)
except Exception as e: except Exception as e:
logging.exception("{}".format(e)) logging.exception("{}".format(e))
body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"}
return web.json_response(body)
plugin = portal_api_lg() plugin = portal_api_lg()

View file

@ -1,12 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import logging import asyncio
import os
from aiohttp import web from aiohttp import web
from bumper import plugins from bumper import plugins
import logging
import bumper
from bumper.models import * from bumper.models import *
from bumper import plugins
from datetime import datetime, timedelta
import os
class portal_api_pim(plugins.ConfServerApp): class portal_api_pim(plugins.ConfServerApp):
@ -16,12 +17,13 @@ class portal_api_pim(plugins.ConfServerApp):
self.sub_api = "portal_api" self.sub_api = "portal_api"
self.routes = [ self.routes = [
web.route("*", "/pim/product/getProductIotMap", self.handle_getProductIotMap, name="portal_api_pim_getProductIotMap"), 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/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/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/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/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 self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time
@ -41,7 +43,7 @@ class portal_api_pim(plugins.ConfServerApp):
try: try:
fileID = request.match_info.get("id", "") 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: except Exception as e:
logging.exception("{}".format(e)) logging.exception("{}".format(e))
@ -65,43 +67,15 @@ class portal_api_pim(plugins.ConfServerApp):
async def handle_getErrDetail(self, request): async def handle_getErrDetail(self, request):
try: try:
body = { body = {
"code": -1, "code": -1,
"data": [], "data": [],
"msg": "This errcode's detail is not exists" "msg": "This errcode's detail is not exists"
} }
return web.json_response(body) return web.json_response(body)
except Exception as e: 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() plugin = portal_api_pim()
confignetAllResponse = { confignetAllResponse = {
@ -2707,174 +2681,3 @@ configGroupsResponse = {
"contactUS": "helper" "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
}
}
}
]

View file

@ -1,39 +0,0 @@
#!/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()

View file

@ -1,10 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import logging import asyncio
from aiohttp import web from aiohttp import web
from bumper import plugins from bumper import plugins
import logging
import bumper
from bumper.models import * from bumper.models import *
from bumper import plugins
from datetime import datetime, timedelta
class v1_private_common(plugins.ConfServerApp): class v1_private_common(plugins.ConfServerApp):
@ -19,10 +21,6 @@ class v1_private_common(plugins.ConfServerApp):
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/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/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/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"),
] ]
@ -112,351 +110,6 @@ class v1_private_common(plugins.ConfServerApp):
except Exception as e: 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() 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"}]}

View file

@ -27,9 +27,6 @@ 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/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/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/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: # Direct register from app:
# /{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister # /{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister
#Register by email #Register by email
@ -147,37 +144,5 @@ class v1_private_user(plugins.ConfServerApp):
except Exception as e: except Exception as e:
logging.exception("{}".format(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() plugin = v1_private_user()

View file

@ -1,58 +0,0 @@
#!/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()

View file

@ -4,7 +4,3 @@ ecouser.net
*.ecouser.net *.ecouser.net
ecovacs.net ecovacs.net
*.ecovacs.net *.ecovacs.net
*.ww.ecouser.net
*.dc-eu.ww.ecouser.net
*.dc.ww.ecouser.net
*.area.ww.ecouser.net

View file

@ -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 ### Create a Root CA
1. Create csrconfig_ca.txt for use in later commands 1. Create csrconfig.txt for use in later commands
***csrconfig_ca.txt*** ***csrconfig.txt***
```` ````
[ req ] [ req ]
default_md = sha256 default_md = sha256
@ -70,9 +70,9 @@ keyUsage=critical,keyCertSign,cRLSign
basicConstraints=critical,CA:true,pathlen:1 basicConstraints=critical,CA:true,pathlen:1
```` ````
1. Create certconfig_ca.txt for use in later commands 1. Create certconfig.txt for use in later commands
***certconfig_ca.txt*** ***certconfig.txt***
```` ````
[ req ] [ req ]
default_md = sha256 default_md = sha256
@ -91,21 +91,21 @@ basicConstraints=critical,CA:true,pathlen:1
1. Generate the RSA private key 1. Generate the RSA private key
`openssl genrsa -out ca.key 4096` `openssl genpkey -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out priv.key`
1. Create the CSR 1. Create the CSR
`openssl req -new -nodes -key ca.key -config csrconfig_ca.txt -out ca.csr` `openssl req -new -nodes -key priv.key -config csrconfig.txt -out cert.csr`
1. Self-sign your CSR 1. Self-sign your CSR
`openssl req -x509 -nodes -in ca.csr -days 1095 -key ca.key -config certconfig_ca.txt -extensions req_ext -out ca.crt` `openssl req -x509 -nodes -in cert.csr -days 3650 -key priv.key -config certconfig.txt -extensions req_ext -out cert.crt`
### Create the Server Certificate ### Create the Server Certificate
1. Create csrconfig_bumper.txt for use in later commands 1. Create csrconfig.txt for use in later commands
***csrconfig_bumper.txt*** ***csrconfig.txt***
```` ````
[ req ] [ req ]
default_md = sha256 default_md = sha256
@ -127,15 +127,11 @@ DNS.2 = ecouser.net
DNS.3 = *.ecouser.net DNS.3 = *.ecouser.net
DNS.4 = ecovacs.net DNS.4 = ecovacs.net
DNS.5 = *.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_bumper.txt for use in later commands 1. Create certconfig.txt for use in later commands
***certconfig_bumper.txt*** ***certconfig.txt***
```` ````
[ req ] [ req ]
default_md = sha256 default_md = sha256
@ -159,23 +155,19 @@ DNS.2 = ecouser.net
DNS.3 = *.ecouser.net DNS.3 = *.ecouser.net
DNS.4 = ecovacs.net DNS.4 = ecovacs.net
DNS.5 = *.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 1. Generate the RSA private key
`openssl genrsa -out bumper.key 4096` `openssl genpkey -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out priv.key`
1. Create the CSR 1. Create the CSR
`openssl req -new -nodes -key bumper.key -config csrconfig_bumper.txt -out bumper.csr` `openssl req -new -nodes -key priv.key -config csrconfig.txt -out cert.csr`
1. Sign your CSR with a root CA cert 1. Sign your CSR with a root CA cert
`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` `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`
## Using a Custom CA/Self ## Using a Custom CA/Self

View file

@ -26,7 +26,6 @@ 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. **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. 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: Replacement Examples:
@ -47,7 +46,6 @@ Replacement Examples:
| `eco-{countrycode}-api.ecovacs.com` | Used for Login | | `eco-{countrycode}-api.ecovacs.com` | Used for Login |
| `gl-{countrycode}-api.ecovacs.com` | Used by EcoVacs Home app | | `gl-{countrycode}-api.ecovacs.com` | Used by EcoVacs Home app |
| `gl-{countrycode}-openapi.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-{countrycode}.ecouser.net` | Used for Login and Rest API |
| `portal-{region}.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 | | `portal-ww.ecouser.net` | Used for various Rest APIs |
@ -61,11 +59,4 @@ Replacement Examples:
| `recommender.ecovacs.com` | Used by Ecovacs Home app | | `recommender.ecovacs.com` | Used by Ecovacs Home app |
| `bigdata-international.ecovacs.com` | Telemetry/tracking | | `bigdata-international.ecovacs.com` | Telemetry/tracking |
| `bigdata-northamerica.ecovacs.com` | Telemetry/tracking | | `bigdata-northamerica.ecovacs.com` | Telemetry/tracking |
| `bigdata-europe.ecovacs.com` | Telemetry/tracking |
| `bigdata-{unknown regions}.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+) |

View file

@ -42,13 +42,3 @@ 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 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.

View file

@ -12,4 +12,3 @@ Bumper has a number of environment variables to help with custom deployments and
| BUMPER_LOGS | {full path to logs directory} | The directory where logs should be stored | | BUMPER_LOGS | {full path to logs directory} | The directory where logs should be stored |
| BUMPER_DATA | {full path to data directory} | The directory where persistent data should be stored (bumper.db) | | BUMPER_DATA | {full path to data directory} | The directory where persistent data should be stored (bumper.db) |
| BUMPER_DEBUG | true | Run Bumper with debug mode/logging | | BUMPER_DEBUG | true | Run Bumper with debug mode/logging |
| LOG_TO_STDOUT | true | Instead of logging to logs/, logs to to STDOUT |

View file

@ -1,45 +0,0 @@
---
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

View file

@ -1,41 +0,0 @@
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;
}
}

View file

@ -1,14 +0,0 @@
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