From bf2514cb09f90260f8f2255b81daa62bcead234d Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sat, 29 Jan 2022 17:24:00 +0100 Subject: [PATCH 01/10] add pre-commit --- .coveragerc | 6 + .gitignore | 4 - .pre-commit-config.yaml | 89 +++++ .travis.yml | 15 - .yamllint | 59 ++++ Pipfile | 33 -- Pipfile.lock | 723 ---------------------------------------- appveyor.yml | 25 -- bandit.yaml | 21 ++ mypy.ini | 19 ++ pylintrc | 84 +++++ requirements-dev.txt | 2 + requirements-test.txt | 19 ++ scripts/run-in-env.sh | 21 ++ 14 files changed, 320 insertions(+), 800 deletions(-) create mode 100644 .coveragerc create mode 100644 .pre-commit-config.yaml delete mode 100644 .travis.yml create mode 100644 .yamllint delete mode 100644 Pipfile delete mode 100644 Pipfile.lock delete mode 100644 appveyor.yml create mode 100644 bandit.yaml create mode 100644 mypy.ini create mode 100644 pylintrc create mode 100644 requirements-dev.txt create mode 100644 requirements-test.txt create mode 100755 scripts/run-in-env.sh diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..500e082 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +branch = True +source = bumper + +omit = + tests/* diff --git a/.gitignore b/.gitignore index 31682cc..60219ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,10 @@ # Ignore pycache __pycache__ -# Ignore .hidden files -.* - # Except . !.github/ !.gitignore !.dockerignore -!.travis.yml # Ignore items in test (report, cache, etc), except files starting with test tests/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..d2d0d97 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,89 @@ +ci: + skip: + # This steps run in the ci workflow. Keep in sync + - mypy + - pylint + +repos: + - repo: https://github.com/asottile/pyupgrade + rev: v2.31.0 + hooks: + - id: pyupgrade + args: [--py37-plus] + - repo: https://github.com/psf/black + rev: 21.12b0 + hooks: + - id: black + args: + - --safe + - --quiet + <<: &python-files-with-tests + files: ^((bumper|tests)/.+)?[^/]+\.py$ + - repo: https://github.com/codespell-project/codespell + rev: v2.1.0 + hooks: + - id: codespell + args: + - --ignore-words-list=hass,deebot + - --skip="./.*,*.csv,*.json" + - --quiet-level=2 + exclude_types: [csv, json] + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + additional_dependencies: + - flake8-docstrings==1.6.0 + - pydocstyle==6.1.1 + <<: &python-files + files: ^(bumper/.+)?[^/]+\.py$ + - repo: https://github.com/PyCQA/bandit + rev: 1.7.1 + hooks: + - id: bandit + args: + - --quiet + - --format=custom + - --configfile=bandit.yaml + <<: *python-files-with-tests + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: detect-private-key + - id: no-commit-to-branch + - id: requirements-txt-fixer + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.5.1 + hooks: + - id: prettier + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.26.3 + hooks: + - id: yamllint + - repo: local + hooks: + # Run mypy through our wrapper script in order to get the possible + # pyenv and/or virtualenv activated; it may not have been e.g. if + # committing from a GUI tool that was not launched from an activated + # shell. + - id: mypy + name: Check with mypy + entry: scripts/run-in-env.sh mypy + language: script + types: [python] + require_serial: true + <<: *python-files + - id: pylint + name: Check with pylint + entry: scripts/run-in-env.sh pylint + language: script + types: [python] + require_serial: true + <<: *python-files + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1b952c6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: python -# Enable python 3.7 without globally enabling sudo and dist: xenial for other build jobs -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true -install: - - pip install pipenv - - pipenv install --dev - -script: pipenv run python -m pytest --cov=./ tests - -after_success: - - codecov \ No newline at end of file diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..96b89d6 --- /dev/null +++ b/.yamllint @@ -0,0 +1,59 @@ +rules: + braces: + level: error + min-spaces-inside: 0 + max-spaces-inside: 1 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 + brackets: + level: error + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 + colons: + level: error + max-spaces-before: 0 + max-spaces-after: 1 + commas: + level: error + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + comments: + level: error + require-starting-space: true + min-spaces-from-content: 2 + comments-indentation: + level: error + document-end: + level: error + present: false + document-start: + level: error + present: false + empty-lines: + level: error + max: 1 + max-start: 0 + max-end: 1 + hyphens: + level: error + max-spaces-after: 1 + indentation: + level: error + spaces: 2 + indent-sequences: true + check-multi-line-strings: false + key-duplicates: + level: error + line-length: disable + new-line-at-end-of-file: + level: error + new-lines: + level: error + type: unix + trailing-spaces: + level: error + truthy: + disable diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 563961f..0000000 --- a/Pipfile +++ /dev/null @@ -1,33 +0,0 @@ -[[source]] -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -hbmqtt = "~=0.9" -aiohttp = "~=3.6" -tinydb = "~=3.15" -pyyaml = "~=5.2" -atomicwrites = "~=1.3" -yarl = "~=1.3.0" -multidict = "~=4.5.2" -aiohttp-jinja2 = "*" -jinja2 = "*" - -[dev-packages] -black = "*" -coverage = "*" -mock = "*" -pylint = "*" -pbr = "*" -pytest-aiohttp = "*" -pytest-cov = "*" -testfixtures = "*" -codecov = "*" -autoflake = "*" -pytest-env = "*" -pytest = "*" -pytest-asyncio = "*" - -[pipenv] -allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 0383d41..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,723 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "740f61688ce679591a1b01f41abc1fa979f8fb1552d598e68868bc47712ba937" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "aiohttp": { - "hashes": [ - "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", - "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", - "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", - "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", - "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", - "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", - "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", - "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", - "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", - "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", - "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", - "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" - ], - "index": "pypi", - "version": "==3.6.2" - }, - "aiohttp-jinja2": { - "hashes": [ - "sha256:2dfe29cfd278d07cd0a851afb98471bc8ce2a830968443e40d67636f3c035d79", - "sha256:3b4dfe1bfd5542e254a769c18cb58d62f7f92755fec127e38d0da3436900b240" - ], - "index": "pypi", - "version": "==1.2.0" - }, - "async-timeout": { - "hashes": [ - "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", - "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" - ], - "version": "==3.0.1" - }, - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "index": "pypi", - "version": "==1.3.0" - }, - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "version": "==19.3.0" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "docopt": { - "hashes": [ - "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" - ], - "version": "==0.6.2" - }, - "hbmqtt": { - "hashes": [ - "sha256:235fffa4645005536fefb9945084165d9e26cbf889b767f7d896aa929b99d49e", - "sha256:9886b1c8321d16e971376dc609b902e0c84118846642b5e09f08a4ca876a7f2a" - ], - "index": "pypi", - "version": "==0.9.5" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "version": "==2.8" - }, - "jinja2": { - "hashes": [ - "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", - "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" - ], - "index": "pypi", - "version": "==2.10.3" - }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" - ], - "version": "==1.1.1" - }, - "multidict": { - "hashes": [ - "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", - "sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3", - "sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef", - "sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b", - "sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73", - "sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc", - "sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3", - "sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd", - "sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351", - "sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941", - "sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d", - "sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1", - "sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b", - "sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a", - "sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3", - "sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7", - "sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0", - "sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0", - "sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014", - "sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5", - "sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036", - "sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d", - "sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a", - "sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce", - "sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1", - "sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a", - "sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9", - "sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7", - "sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b" - ], - "index": "pypi", - "version": "==4.5.2" - }, - "passlib": { - "hashes": [ - "sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177", - "sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8" - ], - "version": "==1.7.2" - }, - "pyyaml": { - "hashes": [ - "sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470", - "sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292", - "sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282", - "sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224", - "sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058", - "sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f", - "sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47", - "sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b", - "sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f", - "sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66", - "sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4" - ], - "index": "pypi", - "version": "==5.3b1" - }, - "six": { - "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" - ], - "version": "==1.13.0" - }, - "tinydb": { - "hashes": [ - "sha256:1087ade5300c47dbf9539d9f6dafd53115bd5e85a67d480d8188bdbfa2d9eaf4", - "sha256:f273d9b6d8b1b5e1d094a6eb8b72851b39b81099293344132c73332b60e3b893" - ], - "index": "pypi", - "version": "==3.15.2" - }, - "transitions": { - "hashes": [ - "sha256:2822e51bd4108bb9730cd883da0949d9948c2d8638b3fb78f19c80f6865dfa4c", - "sha256:b73015080833b753cbb4a10f51f8234924ddfbdbaf33539fee4e4f3abfff454d" - ], - "version": "==0.7.1" - }, - "websockets": { - "hashes": [ - "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", - "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", - "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", - "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", - "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", - "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", - "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", - "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", - "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", - "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", - "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", - "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", - "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", - "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", - "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", - "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", - "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", - "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", - "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", - "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", - "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", - "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" - ], - "version": "==8.1" - }, - "yarl": { - "hashes": [ - "sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9", - "sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f", - "sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb", - "sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320", - "sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842", - "sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0", - "sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829", - "sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310", - "sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4", - "sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8", - "sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1" - ], - "index": "pypi", - "version": "==1.3.0" - } - }, - "develop": { - "aiohttp": { - "hashes": [ - "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", - "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", - "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", - "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", - "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", - "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", - "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", - "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", - "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", - "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", - "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", - "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" - ], - "index": "pypi", - "version": "==3.6.2" - }, - "appdirs": { - "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" - ], - "version": "==1.4.3" - }, - "astroid": { - "hashes": [ - "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", - "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42" - ], - "version": "==2.3.3" - }, - "async-timeout": { - "hashes": [ - "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", - "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" - ], - "version": "==3.0.1" - }, - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "version": "==19.3.0" - }, - "autoflake": { - "hashes": [ - "sha256:680cb9dade101ed647488238ccb8b8bfb4369b53d58ba2c8cdf7d5d54e01f95b" - ], - "index": "pypi", - "version": "==1.3.1" - }, - "black": { - "hashes": [ - "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", - "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" - ], - "index": "pypi", - "version": "==19.10b0" - }, - "certifi": { - "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" - ], - "version": "==2019.11.28" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "version": "==7.0" - }, - "codecov": { - "hashes": [ - "sha256:8ed8b7c6791010d359baed66f84f061bba5bd41174bf324c31311e8737602788", - "sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83ef20507732bed4" - ], - "index": "pypi", - "version": "==2.0.15" - }, - "coverage": { - "hashes": [ - "sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10", - "sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4", - "sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1", - "sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8", - "sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c", - "sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a", - "sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae", - "sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1", - "sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d", - "sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef", - "sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085", - "sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9", - "sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96", - "sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314", - "sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08", - "sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489", - "sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b", - "sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6", - "sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e", - "sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba", - "sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1", - "sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205", - "sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692", - "sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407", - "sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5", - "sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e", - "sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06", - "sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1", - "sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47", - "sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b", - "sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df" - ], - "index": "pypi", - "version": "==5.0.1" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "version": "==2.8" - }, - "importlib-metadata": { - "hashes": [ - "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", - "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f" - ], - "markers": "python_version < '3.8'", - "version": "==1.3.0" - }, - "isort": { - "hashes": [ - "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", - "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" - ], - "version": "==4.3.21" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", - "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", - "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", - "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", - "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", - "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", - "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", - "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", - "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", - "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", - "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", - "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", - "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", - "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", - "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", - "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", - "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", - "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", - "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", - "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", - "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" - ], - "version": "==1.4.3" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "mock": { - "hashes": [ - "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3", - "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8" - ], - "index": "pypi", - "version": "==3.0.5" - }, - "more-itertools": { - "hashes": [ - "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", - "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" - ], - "version": "==8.0.2" - }, - "multidict": { - "hashes": [ - "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", - "sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3", - "sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef", - "sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b", - "sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73", - "sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc", - "sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3", - "sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd", - "sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351", - "sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941", - "sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d", - "sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1", - "sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b", - "sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a", - "sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3", - "sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7", - "sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0", - "sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0", - "sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014", - "sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5", - "sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036", - "sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d", - "sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a", - "sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce", - "sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1", - "sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a", - "sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9", - "sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7", - "sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b" - ], - "index": "pypi", - "version": "==4.5.2" - }, - "packaging": { - "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" - ], - "version": "==19.2" - }, - "pathspec": { - "hashes": [ - "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424", - "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96" - ], - "version": "==0.7.0" - }, - "pbr": { - "hashes": [ - "sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b", - "sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488" - ], - "index": "pypi", - "version": "==5.4.4" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "version": "==0.13.1" - }, - "py": { - "hashes": [ - "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", - "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" - ], - "version": "==1.8.1" - }, - "pyflakes": { - "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" - ], - "version": "==2.1.1" - }, - "pylint": { - "hashes": [ - "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", - "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4" - ], - "index": "pypi", - "version": "==2.4.4" - }, - "pyparsing": { - "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" - ], - "version": "==2.4.6" - }, - "pytest": { - "hashes": [ - "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa", - "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4" - ], - "index": "pypi", - "version": "==5.3.2" - }, - "pytest-aiohttp": { - "hashes": [ - "sha256:0b9b660b146a65e1313e2083d0d2e1f63047797354af9a28d6b7c9f0726fa33d", - "sha256:c929854339637977375838703b62fef63528598bc0a9d451639eba95f4aaa44f" - ], - "index": "pypi", - "version": "==0.3.0" - }, - "pytest-asyncio": { - "hashes": [ - "sha256:9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf", - "sha256:d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b" - ], - "index": "pypi", - "version": "==0.10.0" - }, - "pytest-cov": { - "hashes": [ - "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", - "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626" - ], - "index": "pypi", - "version": "==2.8.1" - }, - "pytest-env": { - "hashes": [ - "sha256:7e94956aef7f2764f3c147d216ce066bf6c42948bb9e293169b1b1c880a580c2" - ], - "index": "pypi", - "version": "==0.6.2" - }, - "regex": { - "hashes": [ - "sha256:032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d", - "sha256:0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8", - "sha256:106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e", - "sha256:1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588", - "sha256:27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9", - "sha256:29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b", - "sha256:4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae", - "sha256:57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540", - "sha256:724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63", - "sha256:77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885", - "sha256:78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea", - "sha256:7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8", - "sha256:8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e", - "sha256:a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716", - "sha256:adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1", - "sha256:c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b", - "sha256:cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd", - "sha256:d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f", - "sha256:d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3", - "sha256:ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147", - "sha256:faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656" - ], - "version": "==2019.12.20" - }, - "requests": { - "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" - ], - "version": "==2.22.0" - }, - "six": { - "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" - ], - "version": "==1.13.0" - }, - "testfixtures": { - "hashes": [ - "sha256:8f22100d4fb841b958f64e71c8820a32dc46f57d4d7e077777b932acd87b7327", - "sha256:9334f64d4210b734d04abff516d6ddaab7328306a0c4c1268ce4624df51c4f6d" - ], - "index": "pypi", - "version": "==6.10.3" - }, - "toml": { - "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" - ], - "version": "==0.10.0" - }, - "typed-ast": { - "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" - ], - "markers": "implementation_name == 'cpython' and python_version < '3.8'", - "version": "==1.4.0" - }, - "typing-extensions": { - "hashes": [ - "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", - "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", - "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" - ], - "version": "==3.7.4.1" - }, - "urllib3": { - "hashes": [ - "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", - "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" - ], - "version": "==1.25.7" - }, - "wcwidth": { - "hashes": [ - "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603" - ], - "version": "==0.1.8" - }, - "wrapt": { - "hashes": [ - "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" - ], - "version": "==1.11.2" - }, - "yarl": { - "hashes": [ - "sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9", - "sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f", - "sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb", - "sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320", - "sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842", - "sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0", - "sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829", - "sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310", - "sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4", - "sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8", - "sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1" - ], - "index": "pypi", - "version": "==1.3.0" - }, - "zipp": { - "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" - ], - "version": "==0.6.0" - } - } -} diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 9ea12c9..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,25 +0,0 @@ -environment: - - matrix: - - # For Python versions available on Appveyor, see - # http://www.appveyor.com/docs/installed-software#python - - - PYTHON: "C:\\Python37" - -install: - - pip install pipenv - - pipenv --python 3.7 - - pipenv install --dev - -build: off - -test_script: - # Put your test command here. - # Note that you must use the environment variable %PYTHON% to refer to - # the interpreter you're using - Appveyor does not do anything special - # to put the Python version you want to use on PATH. - - "pipenv run python -m pytest --cov=./ tests" - -after_test: - - "pipenv run python -m codecov" \ No newline at end of file diff --git a/bandit.yaml b/bandit.yaml new file mode 100644 index 0000000..568f77d --- /dev/null +++ b/bandit.yaml @@ -0,0 +1,21 @@ +# https://bandit.readthedocs.io/en/latest/config.html + +tests: + - B103 + - B108 + - B306 + - B307 + - B313 + - B314 + - B315 + - B316 + - B317 + - B318 + - B319 + - B320 + - B325 + - B601 + - B602 + - B604 + - B608 + - B609 diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..7aab8e3 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,19 @@ +[mypy] +python_version = 3.7 +show_error_codes = true +follow_imports = silent +ignore_missing_imports = true +strict_equality = true +warn_incomplete_stub = true +warn_redundant_casts = true +warn_unused_configs = true +warn_unused_ignores = true +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true \ No newline at end of file diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..79b9163 --- /dev/null +++ b/pylintrc @@ -0,0 +1,84 @@ +[MASTER] +ignore=tests +# Use a conservative default here; 2 should speed up most setups and not hurt +# any too bad. Override on command line as appropriate. +jobs=2 + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + useless-suppression, + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +# load-plugins=pylint_strict_informational + +# Pickle collected data for later comparisons. +persistent=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist=ciso8601, + cv2 + + +[BASIC] +good-names=i,j,k,ex,_,T,x,y,id + +[MESSAGES CONTROL] +# Reasons disabled: +# format - handled by black +# duplicate-code - unavoidable +# cyclic-import - doesn't test if both import on load +# too-many-* - are not enforced for the sake of readability +# abstract-method - with intro of async there are always methods missing +# inconsistent-return-statements - doesn't handle raise +# wrong-import-order - isort guards this +disable= + format, + abstract-class-little-used, + abstract-method, + cyclic-import, + duplicate-code, + inconsistent-return-statements, + too-many-instance-attributes, + wrong-import-order, + too-few-public-methods + +# enable useless-suppression temporarily every now and then to clean them up +enable= + useless-suppression, + use-symbolic-message-instead, + +[REPORTS] +score=no + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + +[FORMAT] +expected-line-ending-format=LF + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception + +[DESIGN] +max-parents=8 \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..3062f9b --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,2 @@ +-r requirements.txt +-r requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..1b34693 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,19 @@ +mypy==0.931 +pre-commit==2.17.0 +pylint==2.12.2 +pytest==6.2.5 +pytest-cov==3.0.0 +types-cachetools==4.2.9 +pytest-asyncio +pytest-aiohttp +testfixtures + +#pbr = "*" +#pytest-aiohttp = "*" +#pytest-cov = "*" +#testfixtures = "*" +#codecov = "*" +#autoflake = "*" +#pytest-env = "*" +#pytest = "*" +#pytest-asyncio = "*" \ No newline at end of file diff --git a/scripts/run-in-env.sh b/scripts/run-in-env.sh new file mode 100755 index 0000000..1a2b9be --- /dev/null +++ b/scripts/run-in-env.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eu + +# Activate pyenv and virtualenv if present, then run the specified command + +# pyenv, pyenv-virtualenv +if [ -s .python-version ]; then + PYENV_VERSION=$(head -n 1 .python-version) + export PYENV_VERSION +fi + +# other common virtualenvs +my_path=$(git rev-parse --show-toplevel) + +for venv in venv .venv .; do + if [ -f "${my_path}/${venv}/bin/activate" ]; then + . "${my_path}/${venv}/bin/activate" + fi +done + +exec "$@" From 04bd6b3ef0210956912ccb4a049b7e96d3919165 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 10:33:37 +0100 Subject: [PATCH 02/10] run pyupgrade --- bumper/__init__.py | 2 +- bumper/confserver.py | 52 ++++----- bumper/db.py | 30 ++--- bumper/models.py | 8 +- bumper/mqttserver.py | 12 +- .../bumper_confserver_portal_appsvr.py | 6 +- .../plugins/bumper_confserver_portal_dim.py | 6 +- .../plugins/bumper_confserver_portal_ecms.py | 2 +- .../plugins/bumper_confserver_portal_iot.py | 6 +- bumper/plugins/bumper_confserver_portal_lg.py | 8 +- .../plugins/bumper_confserver_portal_neng.py | 6 +- .../plugins/bumper_confserver_portal_pim.py | 12 +- .../plugins/bumper_confserver_portal_rapp.py | 2 +- .../plugins/bumper_confserver_portal_users.py | 2 +- .../bumper_confserver_upload_global.py | 2 +- .../bumper_confserver_v1_private_ad.py | 4 +- .../bumper_confserver_v1_private_campaign.py | 2 +- .../bumper_confserver_v1_private_common.py | 16 +-- .../bumper_confserver_v1_private_message.py | 4 +- .../bumper_confserver_v1_private_shop.py | 2 +- .../bumper_confserver_v1_private_user.py | 8 +- ...umper_confserver_v1_private_userSetting.py | 2 +- bumper/xmppserver.py | 76 ++++++------ tests/test_confserver.py | 2 +- tests/test_db.py | 4 +- tests/test_init.py | 4 +- tests/test_mqttserver.py | 4 +- tests/test_xmppserver.py | 108 +++++------------- tests/test_z_problem.py | 2 +- 29 files changed, 173 insertions(+), 221 deletions(-) diff --git a/bumper/__init__.py b/bumper/__init__.py index 1ec0c42..04080fa 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -183,7 +183,7 @@ async def shutdown(): bumperlog.info("Coroutine canceled") except Exception as e: - bumperlog.info("Exception: {}".format(e)) + bumperlog.info(f"Exception: {e}") finally: bumperlog.info("Shutdown complete") diff --git a/bumper/confserver.py b/bumper/confserver.py index 02f595a..8dd4918 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -127,7 +127,7 @@ class ConfServer: async def start_server(self): try: confserverlog.info( - "Starting ConfServer at {}:{}".format(self.address[0], self.address[1]) + f"Starting ConfServer at {self.address[0]}:{self.address[1]}" ) self.runner = web.AppRunner(self.app) await self.runner.setup() @@ -157,7 +157,7 @@ class ConfServer: pass except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") asyncio.create_task(bumper.shutdown()) async def stop_server(self): @@ -165,7 +165,7 @@ class ConfServer: await self.runner.shutdown() except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def handle_base(self, request): try: @@ -205,7 +205,7 @@ class ConfServer: return resp except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") @web.middleware async def log_all_requests(self, request, handler): @@ -231,7 +231,7 @@ class ConfServer: try: postbody = json.loads(await request.text()) except Exception as e: - confserverlog.error("Request body not json: {} - {}".format(e, e.doc)) + confserverlog.error(f"Request body not json: {e} - {e.doc}") postbody = e.doc else: @@ -256,12 +256,12 @@ class ConfServer: return response except web.HTTPNotFound as notfound: - confserverlog.debug("Request path {} not found".format(request.raw_path)) + confserverlog.debug(f"Request path {request.raw_path} not found") confserverlog.debug(json.dumps(to_log)) return notfound except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") confserverlog.error(json.dumps(to_log)) return e @@ -319,7 +319,7 @@ class ConfServer: return web.json_response({"status": "invalid service"}) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") pass async def handle_RemoveBot(self, request): @@ -332,7 +332,7 @@ class ConfServer: return web.json_response({"status": "successfully removed bot"}) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") pass async def handle_RemoveClient(self, request): @@ -345,7 +345,7 @@ class ConfServer: return web.json_response({"status": "successfully removed client"}) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") pass async def handle_login(self, request): @@ -354,7 +354,7 @@ class ConfServer: countrycode = request.match_info.get("country", "us") apptype = request.match_info.get("apptype", "") confserverlog.info( - "client with devid {} attempting login".format(user_devid) + f"client with devid {user_devid} attempting login" ) if bumper.use_auth: if ( @@ -417,7 +417,7 @@ class ConfServer: ) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def handle_lookup(self, request): try: @@ -464,7 +464,7 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def handle_newauth(self, request): # Bumper is only returning the submitted token. No reason yet to create another new token @@ -485,7 +485,7 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def disconnect(self): try: @@ -493,7 +493,7 @@ class ConfServer: await self.app.shutdown() except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") class ConfServer_GeneralFunctions: def __init__(self): @@ -514,16 +514,16 @@ class ConfServer: return tmpaccesstoken except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") def generate_authcode(self, user, countrycode, token): try: - tmpauthcode = "{}_{}".format(countrycode, uuid.uuid4().hex) + tmpauthcode = f"{countrycode}_{uuid.uuid4().hex}" bumper.user_add_authcode(user["userid"], token, tmpauthcode) return tmpauthcode except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def login(self, request): @@ -532,7 +532,7 @@ class ConfServer: countrycode = request.match_info.get("country", "us") apptype = request.match_info.get("apptype", "") confserverlog.info( - "client with devid {} attempting login".format(user_devid) + f"client with devid {user_devid} attempting login" ) if bumper.use_auth: if ( @@ -595,7 +595,7 @@ class ConfServer: ) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def get_AuthCode(self, request): @@ -660,7 +660,7 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") def check_token(self, apptype, countrycode, user, token): try: @@ -705,7 +705,7 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") def _auth_any(self, devid, apptype, country, request): try: @@ -752,7 +752,7 @@ class ConfServer: if "did" in bot: bumper.user_add_bot(tmpuser["userid"], bot["did"]) else: - confserverlog.error("No DID for bot: {}".format(bot)) + confserverlog.error(f"No DID for bot: {bot}") if "checkLogin" in request.path: # If request was to check a token do so checkToken = self.check_token( @@ -782,7 +782,7 @@ class ConfServer: return body except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") def getUserAccountInfo(self, request): @@ -851,7 +851,7 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def logout(self, request): try: @@ -875,4 +875,4 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception("{}".format(e)) \ No newline at end of file + confserverlog.exception(f"{e}") \ No newline at end of file diff --git a/bumper/db.py b/bumper/db.py index 8b930f9..075ada1 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -44,7 +44,7 @@ def user_add(userid): user = user_get(userid) if not user: - bumperlog.info("Adding new user with userid: {}".format(newuser.userid)) + bumperlog.info(f"Adding new user with userid: {newuser.userid}") user_full_upsert(newuser.asdict()) @@ -122,7 +122,7 @@ def user_remove_bot(userid, did): def user_get_tokens(userid): tokens = db_get().table("tokens") - return tokens.search((Query().userid == userid)) + return tokens.search(Query().userid == userid) def user_get_token(userid, token): @@ -136,7 +136,7 @@ def user_add_token(userid, token): tokens = opendb.table("tokens") tmptoken = tokens.get((Query().userid == userid) & (Query().token == token)) if not tmptoken: - bumperlog.debug("Adding token {} for userid {}".format(token, userid)) + bumperlog.debug(f"Adding token {token} for userid {userid}") tokens.insert( { "userid": userid, @@ -214,7 +214,7 @@ def revoke_expired_oauths(): oauth = OAuth(**i) if datetime.now() >= datetime.fromisoformat(oauth.expire_at): bumperlog.debug( - "Removing oauth {} due to expiration".format(oauth.access_token) + f"Removing oauth {oauth.access_token} due to expiration" ) table.remove(doc_ids=[i.doc_id]) @@ -228,7 +228,7 @@ def user_revoke_expired_oauths(userid): oauth = OAuth(**i) if datetime.now() >= datetime.fromisoformat(oauth.expire_at): bumperlog.debug( - "Removing oauth {} due to expiration".format(oauth.access_token) + f"Removing oauth {oauth.access_token} due to expiration" ) table.remove(doc_ids=[i.doc_id]) @@ -243,7 +243,7 @@ def user_add_oauth(userid) -> OAuth: return OAuth(**entry) else: oauth = OAuth.create_new(userid) - bumperlog.debug("Adding oauth {} for userid {}".format(oauth.access_token, userid)) + bumperlog.debug(f"Adding oauth {oauth.access_token} for userid {userid}") table.insert(oauth.toDB()) return oauth @@ -260,13 +260,13 @@ def get_disconnected_xmpp_clients(): def check_authcode(uid, authcode): - bumperlog.debug("Checking for authcode: {}".format(authcode)) + bumperlog.debug(f"Checking for authcode: {authcode}") tokens = db_get().table("tokens") tmpauth = tokens.get( (Query().authcode == authcode) & ( # Match authcode (Query().userid == uid.replace("fuid_", "")) - | (Query().userid == "fuid_{}".format(uid)) + | (Query().userid == f"fuid_{uid}") ) # Userid with or without fuid_ ) if tmpauth: @@ -276,10 +276,10 @@ def check_authcode(uid, authcode): def loginByItToken(authcode): - bumperlog.debug("Checking for authcode: {}".format(authcode)) + bumperlog.debug(f"Checking for authcode: {authcode}") tokens = db_get().table("tokens") tmpauth = tokens.get( - (Query().authcode == authcode) + Query().authcode == authcode # & ( # Match authcode # (Query().userid == uid.replace("fuid_", "")) # | (Query().userid == "fuid_{}".format(uid)) @@ -292,13 +292,13 @@ def loginByItToken(authcode): def check_token(uid, token): - bumperlog.debug("Checking for token: {}".format(token)) + bumperlog.debug(f"Checking for token: {token}") tokens = db_get().table("tokens") tmpauth = tokens.get( (Query().token == token) & ( # Match token (Query().userid == uid.replace("fuid_", "")) - | (Query().userid == "fuid_{}".format(uid)) + | (Query().userid == f"fuid_{uid}") ) # Userid with or without fuid_ ) if tmpauth: @@ -329,7 +329,7 @@ def bot_add(sn, did, devclass, resource, company): not devclass == "" or "@" not in sn or "tmp" not in sn ): # try to prevent bad additions to the bot list bumperlog.info( - "Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did) + f"Adding new bot with SN: {newbot.name} DID: {newbot.did}" ) bot_full_upsert(newbot.asdict()) @@ -379,7 +379,7 @@ def bot_full_upsert(vacbot): if "did" in vacbot: bots.upsert(vacbot, Bot.did == vacbot["did"]) else: - bumperlog.error("No DID in vacbot: {}".format(vacbot)) + bumperlog.error(f"No DID in vacbot: {vacbot}") def bot_set_nick(did, nick): @@ -408,7 +408,7 @@ def client_add(userid, realm, resource): client = client_get(resource) if not client: - bumperlog.info("Adding new client with resource {}".format(newclient.resource)) + bumperlog.info(f"Adding new client with resource {newclient.resource}") client_full_upsert(newclient.asdict()) diff --git a/bumper/models.py b/bumper/models.py index e547926..0a3c533 100644 --- a/bumper/models.py +++ b/bumper/models.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta import bumper -class VacBotDevice(object): +class VacBotDevice: def __init__( self, did="", vac_bot_device_class="", resource="", name="", nick="", company="" ): @@ -32,7 +32,7 @@ class VacBotDevice(object): } -class BumperUser(object): +class BumperUser: def __init__(self, userid=""): self.userid = userid self.devices = [] @@ -50,7 +50,7 @@ class GlobalVacBotDevice(VacBotDevice): # EcoVacs Home deviceName = "" -class VacBotClient(object): +class VacBotClient: def __init__(self, userid="", realm="", token=""): self.userid = userid self.realm = realm @@ -101,7 +101,7 @@ class OAuth: oauth = OAuth() oauth.userId = userId oauth.access_token = uuid.uuid4().hex - oauth.expire_at = "{}".format(datetime.utcnow() + timedelta(days=bumper.oauth_validity_days)) + oauth.expire_at = f"{datetime.utcnow() + timedelta(days=bumper.oauth_validity_days)}" oauth.refresh_token = uuid.uuid4().hex return oauth diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index 2dc9599..6b0286b 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -70,7 +70,7 @@ class MQTTHelperBot: ] ) except Exception as e: - helperbotlog.exception("{}".format(e)) + helperbotlog.exception(f"{e}") async def _wait_for_resp(self, command_dto: CommandDto, request_id: str): try: @@ -85,7 +85,7 @@ class MQTTHelperBot: except asyncio.CancelledError as e: helperbotlog.debug("wait_for_resp cancelled by asyncio", e, exc_info=True) except Exception as e: - helperbotlog.exception("{}".format(e)) + helperbotlog.exception(f"{e}") return { "id": request_id, @@ -118,7 +118,7 @@ class MQTTHelperBot: resp = await self._wait_for_resp(command_dto, requestid) return resp except Exception as e: - helperbotlog.exception("{}".format(e)) + helperbotlog.exception(f"{e}") return { "id": requestid, "errno": 500, @@ -185,7 +185,7 @@ class MQTTServer: self.broker = hbmqtt.broker.Broker(config=self.default_config) except Exception as e: - mqttserverlog.exception("{}".format(e)) + mqttserverlog.exception(f"{e}") async def broker_coro(self): mqttserverlog.info(f"Starting MQTT Server at {self._host}:{self._port}") @@ -199,7 +199,7 @@ class MQTTServer: pass except Exception as e: - mqttserverlog.exception("{}".format(e)) + mqttserverlog.exception(f"{e}") # asyncio.create_task(bumper.shutdown()) pass @@ -217,7 +217,7 @@ class BumperMQTTServer_Plugin: "'bumper' section not found in context configuration" ) except Exception as e: - mqttserverlog.exception("{}".format(e)) + mqttserverlog.exception(f"{e}") async def authenticate(self, *args, **kwargs): authenticated = False diff --git a/bumper/plugins/bumper_confserver_portal_appsvr.py b/bumper/plugins/bumper_confserver_portal_appsvr.py index a92ca66..b2ee984 100644 --- a/bumper/plugins/bumper_confserver_portal_appsvr.py +++ b/bumper/plugins/bumper_confserver_portal_appsvr.py @@ -149,7 +149,7 @@ class portal_api_appsvr(plugins.ConfServerApp): except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") # Return fail for GET body = {"result": "fail", "todo": "result"} @@ -189,7 +189,7 @@ class portal_api_appsvr(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_appsvr_oauth_callback(self, request): try: @@ -205,7 +205,7 @@ class portal_api_appsvr(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = portal_api_appsvr() diff --git a/bumper/plugins/bumper_confserver_portal_dim.py b/bumper/plugins/bumper_confserver_portal_dim.py index c9b5dd3..fd71d74 100644 --- a/bumper/plugins/bumper_confserver_portal_dim.py +++ b/bumper/plugins/bumper_confserver_portal_dim.py @@ -42,8 +42,8 @@ class portal_api_dim(plugins.ConfServerApp): json_body, randomid ) body = retcmd - logging.debug("Send Bot - {}".format(json_body)) - logging.debug("Bot Response - {}".format(body)) + logging.debug(f"Send Bot - {json_body}") + logging.debug(f"Bot Response - {body}") return web.json_response(body) else: # No response, send error back @@ -70,7 +70,7 @@ class portal_api_dim(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = portal_api_dim() diff --git a/bumper/plugins/bumper_confserver_portal_ecms.py b/bumper/plugins/bumper_confserver_portal_ecms.py index e408d18..152456d 100644 --- a/bumper/plugins/bumper_confserver_portal_ecms.py +++ b/bumper/plugins/bumper_confserver_portal_ecms.py @@ -32,7 +32,7 @@ class portal_api_ecms(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = portal_api_ecms() diff --git a/bumper/plugins/bumper_confserver_portal_iot.py b/bumper/plugins/bumper_confserver_portal_iot.py index 486fe37..f9721df 100644 --- a/bumper/plugins/bumper_confserver_portal_iot.py +++ b/bumper/plugins/bumper_confserver_portal_iot.py @@ -41,8 +41,8 @@ class portal_api_iot(plugins.ConfServerApp): json_body, randomid ) body = retcmd - logging.debug("Send Bot - {}".format(json_body)) - logging.debug("Bot Response - {}".format(body)) + logging.debug(f"Send Bot - {json_body}") + logging.debug(f"Bot Response - {body}") return web.json_response(body) else: # No response, send error back @@ -75,7 +75,7 @@ class portal_api_iot(plugins.ConfServerApp): except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = portal_api_iot() diff --git a/bumper/plugins/bumper_confserver_portal_lg.py b/bumper/plugins/bumper_confserver_portal_lg.py index 77b9709..015e7a0 100644 --- a/bumper/plugins/bumper_confserver_portal_lg.py +++ b/bumper/plugins/bumper_confserver_portal_lg.py @@ -64,8 +64,8 @@ class portal_api_lg(plugins.ConfServerApp): json_body, randomid ) body = retcmd - logging.debug("Send Bot - {}".format(json_body)) - logging.debug("Bot Response - {}".format(body)) + logging.debug(f"Send Bot - {json_body}") + logging.debug(f"Bot Response - {body}") logs = [] logsroot = ET.fromstring(retcmd["resp"]) if logsroot.attrib["ret"] == "ok": @@ -88,7 +88,7 @@ class portal_api_lg(plugins.ConfServerApp): else: body = {"ret": "ok", "logs": []} - logging.debug("lg logs return: {}".format(json.dumps(body))) + logging.debug(f"lg logs return: {json.dumps(body)}") return web.json_response(body) else: # No response, send error back @@ -99,7 +99,7 @@ class portal_api_lg(plugins.ConfServerApp): ) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} return web.json_response(body) diff --git a/bumper/plugins/bumper_confserver_portal_neng.py b/bumper/plugins/bumper_confserver_portal_neng.py index c64fe0f..d4938de 100644 --- a/bumper/plugins/bumper_confserver_portal_neng.py +++ b/bumper/plugins/bumper_confserver_portal_neng.py @@ -33,7 +33,7 @@ class portal_api_neng(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_neng_getShareMsgs(self, request): # EcoVacs Home try: @@ -65,7 +65,7 @@ class portal_api_neng(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_neng_getlist(self, request): # EcoVacs Home try: @@ -91,7 +91,7 @@ class portal_api_neng(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = portal_api_neng() diff --git a/bumper/plugins/bumper_confserver_portal_pim.py b/bumper/plugins/bumper_confserver_portal_pim.py index 2c7bee1..701a3d3 100644 --- a/bumper/plugins/bumper_confserver_portal_pim.py +++ b/bumper/plugins/bumper_confserver_portal_pim.py @@ -35,7 +35,7 @@ class portal_api_pim(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_pimFile(self, request): try: @@ -44,7 +44,7 @@ class portal_api_pim(plugins.ConfServerApp): return web.FileResponse(os.path.join(bumper.bumper_dir, "bumper", "web", "images", "robotvac_image.jpg")) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getConfignetAll(self, request): try: @@ -52,7 +52,7 @@ class portal_api_pim(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getConfigGroups(self, request): try: @@ -60,7 +60,7 @@ class portal_api_pim(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getErrDetail(self, request): try: @@ -72,7 +72,7 @@ class portal_api_pim(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_product_config_batch(self, request): try: @@ -99,7 +99,7 @@ class portal_api_pim(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = portal_api_pim() diff --git a/bumper/plugins/bumper_confserver_portal_rapp.py b/bumper/plugins/bumper_confserver_portal_rapp.py index ca5ef99..1cd8627 100644 --- a/bumper/plugins/bumper_confserver_portal_rapp.py +++ b/bumper/plugins/bumper_confserver_portal_rapp.py @@ -34,6 +34,6 @@ class api_rapp(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = api_rapp() diff --git a/bumper/plugins/bumper_confserver_portal_users.py b/bumper/plugins/bumper_confserver_portal_users.py index fd4d5f8..254fdbf 100644 --- a/bumper/plugins/bumper_confserver_portal_users.py +++ b/bumper/plugins/bumper_confserver_portal_users.py @@ -110,7 +110,7 @@ class portal_api_users(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") # Return fail for GET body = {"result": "fail", "todo": "result"} diff --git a/bumper/plugins/bumper_confserver_upload_global.py b/bumper/plugins/bumper_confserver_upload_global.py index f823ec5..8f6fa11 100644 --- a/bumper/plugins/bumper_confserver_upload_global.py +++ b/bumper/plugins/bumper_confserver_upload_global.py @@ -33,7 +33,7 @@ class upload_global(plugins.ConfServerApp): return web.FileResponse(os.path.join(bumper.bumper_dir,"bumper","web","images","robotvac_image.jpg")) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") diff --git a/bumper/plugins/bumper_confserver_v1_private_ad.py b/bumper/plugins/bumper_confserver_v1_private_ad.py index 2e7df5b..530057c 100644 --- a/bumper/plugins/bumper_confserver_v1_private_ad.py +++ b/bumper/plugins/bumper_confserver_v1_private_ad.py @@ -37,7 +37,7 @@ class v1_private_ad(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getBootScreen(self, request): # EcoVacs Home try: @@ -52,7 +52,7 @@ class v1_private_ad(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = v1_private_ad() diff --git a/bumper/plugins/bumper_confserver_v1_private_campaign.py b/bumper/plugins/bumper_confserver_v1_private_campaign.py index 468ca8c..c388e25 100644 --- a/bumper/plugins/bumper_confserver_v1_private_campaign.py +++ b/bumper/plugins/bumper_confserver_v1_private_campaign.py @@ -47,7 +47,7 @@ class v1_private_campaign(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = v1_private_campaign() diff --git a/bumper/plugins/bumper_confserver_v1_private_common.py b/bumper/plugins/bumper_confserver_v1_private_common.py index f84d944..ec2010d 100644 --- a/bumper/plugins/bumper_confserver_v1_private_common.py +++ b/bumper/plugins/bumper_confserver_v1_private_common.py @@ -48,7 +48,7 @@ class v1_private_common(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_checkAPPVersion(self, request): # EcoVacs Home try: @@ -73,7 +73,7 @@ class v1_private_common(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_uploadDeviceInfo(self, request): # EcoVacs Home try: @@ -88,7 +88,7 @@ class v1_private_common(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getSystemReminder(self, request): # EcoVacs Home try: @@ -110,7 +110,7 @@ class v1_private_common(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getConfig(self, request): try: @@ -132,7 +132,7 @@ class v1_private_common(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getAreas(self, request): try: @@ -147,7 +147,7 @@ class v1_private_common(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getAgreementURLBatch(self, request): # EcoVacs Home try: @@ -179,7 +179,7 @@ class v1_private_common(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getTimestamp(self, request): # EcoVacs Home try: @@ -197,7 +197,7 @@ class v1_private_common(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = v1_private_common() diff --git a/bumper/plugins/bumper_confserver_v1_private_message.py b/bumper/plugins/bumper_confserver_v1_private_message.py index 7e1d6d1..596ad67 100644 --- a/bumper/plugins/bumper_confserver_v1_private_message.py +++ b/bumper/plugins/bumper_confserver_v1_private_message.py @@ -38,7 +38,7 @@ class v1_private_message(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getMsgList(self, request): # EcoVacs Home try: @@ -53,7 +53,7 @@ class v1_private_message(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = v1_private_message() diff --git a/bumper/plugins/bumper_confserver_v1_private_shop.py b/bumper/plugins/bumper_confserver_v1_private_shop.py index 343f13a..4697b8c 100644 --- a/bumper/plugins/bumper_confserver_v1_private_shop.py +++ b/bumper/plugins/bumper_confserver_v1_private_shop.py @@ -42,7 +42,7 @@ class v1_private_shop(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = v1_private_shop() diff --git a/bumper/plugins/bumper_confserver_v1_private_user.py b/bumper/plugins/bumper_confserver_v1_private_user.py index ab04b54..8787d5b 100644 --- a/bumper/plugins/bumper_confserver_v1_private_user.py +++ b/bumper/plugins/bumper_confserver_v1_private_user.py @@ -76,7 +76,7 @@ class v1_private_user(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getUserMenuInfo(self, request): try: @@ -145,7 +145,7 @@ class v1_private_user(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_changeArea(self, request): try: @@ -162,7 +162,7 @@ class v1_private_user(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_acceptAgreementBatch(self, request): try: @@ -177,7 +177,7 @@ class v1_private_user(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = v1_private_user() diff --git a/bumper/plugins/bumper_confserver_v1_private_userSetting.py b/bumper/plugins/bumper_confserver_v1_private_userSetting.py index 3a7720f..9822545 100644 --- a/bumper/plugins/bumper_confserver_v1_private_userSetting.py +++ b/bumper/plugins/bumper_confserver_v1_private_userSetting.py @@ -52,7 +52,7 @@ class v1_private_userSetting(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") plugin = v1_private_userSetting() diff --git a/bumper/xmppserver.py b/bumper/xmppserver.py index eb7aed0..802320a 100644 --- a/bumper/xmppserver.py +++ b/bumper/xmppserver.py @@ -29,7 +29,7 @@ class XMPPServer: async def start_async_server(self): try: xmppserverlog.info( - "Starting XMPP Server at {}:{}".format(self.address[0], self.address[1]) + f"Starting XMPP Server at {self.address[0]}:{self.address[1]}" ) loop = asyncio.get_running_loop() @@ -49,7 +49,7 @@ class XMPPServer: pass except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") asyncio.create_task(bumper.shutdown()) def disconnect(self): @@ -71,7 +71,7 @@ class XMPPServer_Protocol(asyncio.Protocol): def connection_made(self, transport): if self.aclient: # Existing client... upgrading to TLS xmppserverlog.debug( - "Upgraded connection for {}".format(self.aclient.address) + f"Upgraded connection for {self.aclient.address}" ) self.aclient.transport = transport else: @@ -79,7 +79,7 @@ class XMPPServer_Protocol(asyncio.Protocol): self.aclient = aclient XMPPServer.clients.append(aclient) self.aclient.state = getattr(aclient, "CONNECT") - xmppserverlog.debug("New Connection from {}".format(aclient.address)) + xmppserverlog.debug(f"New Connection from {aclient.address}") def connection_lost(self, error): XMPPServer.clients.remove(self.aclient) @@ -119,7 +119,7 @@ class XMPPAsyncClient: self.uid = "" self.log_sent_message = True # Set to true to log sends self.log_incoming_data = True # Set to true to log sends - xmppserverlog.debug("new client with ip {}".format(self.address)) + xmppserverlog.debug(f"new client with ip {self.address}") def send(self, command): try: @@ -133,7 +133,7 @@ class XMPPAsyncClient: self.transport.write(command.encode()) except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") def _disconnect(self): try: @@ -149,7 +149,7 @@ class XMPPAsyncClient: self.transport.close() except Exception as e: - xmppserverlog.error("{}".format(e)) + xmppserverlog.error(f"{e}") def _tag_strip_uri(self, tag): try: @@ -158,7 +158,7 @@ class XMPPAsyncClient: return tag except Exception as e: - xmppserverlog.error("{}".format(e)) + xmppserverlog.error(f"{e}") def _set_state(self, state): try: @@ -182,7 +182,7 @@ class XMPPAsyncClient: self._disconnect() except Exception as e: - xmppserverlog.error("{}".format(e)) + xmppserverlog.error(f"{e}") def _handle_ctl(self, xml, data): try: @@ -244,7 +244,7 @@ class XMPPAsyncClient: ): ctl_to = xml.get("to") if not "from" in xml.attrib: - xml.attrib["from"] = "{}".format(self.bumper_jid) + xml.attrib["from"] = f"{self.bumper_jid}" rxmlstring = ET.tostring(xml).decode("utf-8") # clean up string to remove namespaces added by ET rxmlstring = rxmlstring.replace("xmlns:ns0=", "xmlns=") @@ -255,12 +255,12 @@ class XMPPAsyncClient: if client.type == self.BOT: if client.uid.lower() in ctl_to.lower(): xmppserverlog.debug( - "Sending ctl to bot: {}".format(rxmlstring) + f"Sending ctl to bot: {rxmlstring}" ) client.send(rxmlstring) except Exception as e: - xmppserverlog.error("{}".format(e)) + xmppserverlog.error(f"{e}") def _handle_ping(self, xml, data): try: @@ -276,7 +276,7 @@ class XMPPAsyncClient: pingto = xml.get("to") pingfrom = self.bumper_jid if not "from" in xml.attrib: - xml.attrib["from"] = "{}".format(pingfrom) + xml.attrib["from"] = f"{pingfrom}" pingstring = ET.tostring(xml).decode("utf-8") # clean up string to remove namespaces added by ET pingstring = pingstring.replace("xmlns:ns0=", "xmlns=") @@ -293,7 +293,7 @@ class XMPPAsyncClient: client.send(pingstring) except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") async def schedule_ping(self, time): if not self.state == 5: # disconnected @@ -308,7 +308,7 @@ class XMPPAsyncClient: try: ctl_to = xml.get("to") if not "from" in xml.attrib: - xml.attrib["from"] = "{}".format(self.bumper_jid) + xml.attrib["from"] = f"{self.bumper_jid}" if "errno" in data: xmppserverlog.error(f"Error from bot - {data}") if ( @@ -336,14 +336,14 @@ class XMPPAsyncClient: adduser = ''.format( uuid.uuid4(), adminuser, self.bumper_jid, newuser ) - xmppserverlog.debug("Adding User to bot - {}".format(adduser)) + xmppserverlog.debug(f"Adding User to bot - {adduser}") self.send(adduser) # Add user ACs - Manage users, settings, and clean (full access) adduseracs = ''.format( uuid.uuid4(), adminuser, self.bumper_jid, newuser ) - xmppserverlog.debug("Add User ACs to bot - {}".format(adduseracs)) + xmppserverlog.debug(f"Add User ACs to bot - {adduseracs}") self.send(adduseracs) # GetUserInfo - Just to confirm it set correctly @@ -395,7 +395,7 @@ class XMPPAsyncClient: client.send(rxmlstring) except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") def _handle_connect(self, data, xml=None): try: @@ -438,7 +438,7 @@ class XMPPAsyncClient: ): # Handle SASL Auth self._handle_sasl_auth(xml) else: - xmppserverlog.error("Couldn't handle: {}".format(xml)) + xmppserverlog.error(f"Couldn't handle: {xml}") elif self.state == self.INIT: if xml == None: @@ -465,10 +465,10 @@ class XMPPAsyncClient: if child == "bind": self._handle_bind(xml) else: - xmppserverlog.error("Couldn't handle: {}".format(xml)) + xmppserverlog.error(f"Couldn't handle: {xml}") except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") async def _handle_starttls(self, data): try: @@ -500,7 +500,7 @@ class XMPPAsyncClient: protocol.connection_made(new_transport) except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") def _handle_sasl_auth(self, xml): try: @@ -523,7 +523,7 @@ class XMPPAsyncClient: if self.devclass: # if there is a devclass it is a bot bumper.bot_add(self.uid, self.uid, self.devclass, "atom", "eco-legacy") self.type = self.BOT - xmppserverlog.info("bot authenticated SN: {}".format(self.uid)) + xmppserverlog.info(f"bot authenticated SN: {self.uid}") # Send response self.send( '' @@ -542,7 +542,7 @@ class XMPPAsyncClient: if auth: self.type = self.CONTROLLER bumper.client_add(self.uid, "bumper", self.clientresource) - xmppserverlog.info("client authenticated {}".format(self.uid)) + xmppserverlog.info(f"client authenticated {self.uid}") # Client authenticated, move to next state self._set_state("INIT") @@ -559,7 +559,7 @@ class XMPPAsyncClient: ) # Fail except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") def _handle_bind(self, xml): try: @@ -575,7 +575,7 @@ class XMPPAsyncClient: clientbindxml = xml.getchildren() clientresourcexml = clientbindxml[0].getchildren() if self.devclass: # its a bot - self.name = "XMPP_Client_{}_{}".format(self.uid, self.devclass) + self.name = f"XMPP_Client_{self.uid}_{self.devclass}" self.bumper_jid = "{}@{}.ecorobot.net/atom".format( self.uid, self.devclass ) @@ -589,7 +589,7 @@ class XMPPAsyncClient: ) elif len(clientresourcexml) > 0: self.clientresource = clientresourcexml[0].text - self.name = "XMPP_Client_{}".format(self.clientresource) + self.name = f"XMPP_Client_{self.clientresource}" self.bumper_jid = "{}@{}/{}".format( self.uid, XMPPServer.server_id, self.clientresource ) @@ -602,8 +602,8 @@ class XMPPAsyncClient: xml.get("id"), self.bumper_jid ) else: - self.name = "XMPP_Client_{}_{}".format(self.uid, self.address) - self.bumper_jid = "{}@{}".format(self.uid, XMPPServer.server_id) + self.name = f"XMPP_Client_{self.uid}_{self.address}" + self.bumper_jid = f"{self.uid}@{XMPPServer.server_id}" xmppserverlog.debug( "new client ({}:{} | {})".format( self.address[0], self.address[1], self.bumper_jid @@ -617,7 +617,7 @@ class XMPPAsyncClient: self.send(res) except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") def _handle_session(self, xml): res = ''.format(xml.get("id")) @@ -636,7 +636,7 @@ class XMPPAsyncClient: # Most likely a bot, possibly hello world in text # Send dummy return - self.send(' dummy '.format(self.bumper_jid)) + self.send(f' dummy ') # If it is a BOT, send extras if self.type == self.BOT: @@ -663,7 +663,7 @@ class XMPPAsyncClient: # Send dummy return self.send( - ' dummy '.format(self.bumper_jid) + f' dummy ' ) elif xml.get("type") == "unavailable": xmppserverlog.debug( @@ -682,7 +682,7 @@ class XMPPAsyncClient: ) # Send dummy return self.send( - ' dummy '.format(self.bumper_jid) + f' dummy ' ) def _parse_data(self, data): @@ -769,13 +769,13 @@ class XMPPAsyncClient: else: if not (newdata == "" or newdata == " "): xmppserverlog.error( - "xml parse error - {} - {}".format(newdata, e) + f"xml parse error - {newdata} - {e}" ) elif "not well-formed (invalid token)" in e.msg: # If a lone - client is signalling end of session/disconnect if not "" in newdata: - xmppserverlog.error("xml parse error - {} - {}".format(newdata, e)) + xmppserverlog.error(f"xml parse error - {newdata} - {e}") else: self.send("") # Close stream @@ -783,20 +783,20 @@ class XMPPAsyncClient: if "" in newdata: xmppserverlog.error( - "xml parse error - {} - {}".format(newdata, e) + f"xml parse error - {newdata} - {e}" ) else: self.send("") # Close stream self._set_state("DISCONNECT") except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") def _handle_iq(self, xml, data): diff --git a/tests/test_confserver.py b/tests/test_confserver.py index 2149673..ba0e16f 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -1,4 +1,4 @@ -import mock +from unittest import mock import bumper import asyncio import pytest diff --git a/tests/test_db.py b/tests/test_db.py index a23b368..2f306c5 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -79,7 +79,7 @@ def test_user_db(): { "userid": "testuser", "token": "token_1234", - "expiration": "{}".format(datetime.now() + timedelta(seconds=-10)), + "expiration": f"{datetime.now() + timedelta(seconds=-10)}", } ) # Add expired token db.close() @@ -93,7 +93,7 @@ def test_user_db(): { "userid": "testuser", "token": "token_1234", - "expiration": "{}".format(datetime.now() + timedelta(seconds=-10)), + "expiration": f"{datetime.now() + timedelta(seconds=-10)}", } ) # Add expired token db.close() diff --git a/tests/test_init.py b/tests/test_init.py index 67779e0..d84848f 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,5 +1,5 @@ -import mock -from mock import patch +from unittest import mock +from unittest.mock import patch import pytest from tinydb.storages import MemoryStorage from tinydb import TinyDB, Query diff --git a/tests/test_mqttserver.py b/tests/test_mqttserver.py index 70205bc..7fc0918 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -1,4 +1,4 @@ -import mock +from unittest import mock import bumper import asyncio import pytest @@ -398,7 +398,7 @@ async def test_mqttserver(): ) await test_client.Client.connect( - "mqtts://{}:{}/".format(test_client.address[0], test_client.address[1]), + f"mqtts://{test_client.address[0]}:{test_client.address[1]}/", cafile=bumper.ca_cert, ) assert ( diff --git a/tests/test_xmppserver.py b/tests/test_xmppserver.py index ef48025..5140338 100644 --- a/tests/test_xmppserver.py +++ b/tests/test_xmppserver.py @@ -1,4 +1,4 @@ -import mock +from unittest import mock import bumper import asyncio import pytest @@ -66,9 +66,7 @@ async def test_client_connect_no_starttls(*args, **kwargs): mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data) # Send connect stream from "client" - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient._parse_data(test_data) # Expect 2 calls to send @@ -88,9 +86,7 @@ async def test_client_connect_no_starttls(*args, **kwargs): mock_send.reset_mock() # Client sendss auth - Ignoring the starttls, we don't force this with bumper - test_data = 'AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl'.encode( - "utf-8" - ) + test_data = b'AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl' xmppclient._parse_data(test_data) assert ( @@ -109,7 +105,7 @@ async def test_client_end_stream(*args, **kwargs): mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data) # Send end stream from "client" - test_data = "".encode("utf-8") + test_data = b"" xmppclient._parse_data(test_data) # Expect 2 calls to send @@ -121,14 +117,14 @@ async def test_client_end_stream(*args, **kwargs): mock_send.reset_mock() # Send abnormal stream from "client" - test_data = "".encode("utf-8") + test_data = b"" xmppclient._parse_data(test_data) # Reset mock calls mock_send.reset_mock() # Send blank from "client" - test_data = "".encode("utf-8") + test_data = b"" xmppclient._parse_data(test_data) @@ -141,9 +137,7 @@ async def test_client_connect_starttls_called(*args, **kwargs): mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data) # Send connect stream from "client" - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient._parse_data(test_data) # Expect 2 calls to send @@ -165,7 +159,7 @@ async def test_client_connect_starttls_called(*args, **kwargs): mock_tls = xmppclient._handle_starttls = mock.Mock() # Send start tls from "client" - test_data = "".encode("utf-8") + test_data = b"" xmppclient._parse_data(test_data) # After upgrading connection, server tells client to proceed with auth again @@ -174,9 +168,7 @@ async def test_client_connect_starttls_called(*args, **kwargs): # After TLS is upgraded, Client establishes session again and will auth this time # Send connect stream from "client" - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient._parse_data(test_data) # Expect 2 calls to send @@ -195,9 +187,7 @@ async def test_client_connect_starttls_called(*args, **kwargs): mock_send.reset_mock() # Client sends auth - test_data = 'AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl'.encode( - "utf-8" - ) + test_data = b'AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl' xmppclient._parse_data(test_data) assert ( @@ -277,9 +267,7 @@ async def test_client_init(*args, **kwargs): mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data) # Send connect stream from "client" - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient._parse_data(test_data) # Expect 2 calls to send @@ -299,9 +287,7 @@ async def test_client_init(*args, **kwargs): mock_send.reset_mock() # Send bind from "client" - test_data = 'IOSF53D07BA'.encode( - "utf-8" - ) + test_data = b'IOSF53D07BA' xmppclient._parse_data(test_data) assert ( @@ -314,9 +300,7 @@ async def test_client_init(*args, **kwargs): mock_send.reset_mock() # Send set session from client - test_data = ''.encode( - "utf-8" - ) + test_data = b'' xmppclient._parse_data(test_data) assert xmppclient.state == xmppclient.READY # client moved to READY state @@ -329,7 +313,7 @@ async def test_client_init(*args, **kwargs): mock_send.reset_mock() # Send presense from client - test_data = ''.encode("utf-8") + test_data = b'' xmppclient._parse_data(test_data) assert ( @@ -347,9 +331,7 @@ async def test_bot_connect(*args, **kwargs): mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data) # Send connect stream from "bot" - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient._parse_data(test_data) # Expect 2 calls to send @@ -369,9 +351,7 @@ async def test_bot_connect(*args, **kwargs): mock_send.reset_mock() # Send auth from "bot" - test_data = "AEUwMDAwMDAwMDAwMDAwMDAxMjM0AGVuY3J5cHRlZF9wYXNz".encode( - "utf-8" - ) + test_data = b"AEUwMDAwMDAwMDAwMDAwMDAxMjM0AGVuY3J5cHRlZF9wYXNz" xmppclient._parse_data(test_data) assert ( @@ -394,9 +374,7 @@ async def test_bot_init(*args, **kwargs): mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data) # Send connect stream from "bot" - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient._parse_data(test_data) # Expect 2 calls to send @@ -416,9 +394,7 @@ async def test_bot_init(*args, **kwargs): mock_send.reset_mock() # Send bind from "bot" - test_data = "atom".encode( - "utf-8" - ) + test_data = b"atom" xmppclient._parse_data(test_data) assert ( @@ -431,9 +407,7 @@ async def test_bot_init(*args, **kwargs): mock_send.reset_mock() # Send set session from bot - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient._parse_data(test_data) assert xmppclient.state == xmppclient.READY # Bot moved to READY state @@ -445,9 +419,7 @@ async def test_bot_init(*args, **kwargs): mock_send.reset_mock() # Send presense from bot - test_data = "hello world".encode( - "utf-8" - ) + test_data = b"hello world" xmppclient._parse_data(test_data) assert ( @@ -467,9 +439,7 @@ async def test_ping_server(*args, **kwargs): mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data) # Ping from bot - test_data = ''.encode( - "utf-8" - ) + test_data = b'' xmppclient._parse_data(test_data) assert ( @@ -501,9 +471,7 @@ async def test_ping_client_to_client(*args, **kwargs): bumper.xmppserver.XMPPServer.clients.append(xmppclient2) # Ping from user to bot - test_data = ''.encode( - "utf-8" - ) + test_data = b'' xmppclient._parse_data(test_data) assert ( @@ -512,9 +480,7 @@ async def test_ping_client_to_client(*args, **kwargs): ) # ping response # Ping response from bot to user - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient2._parse_data(test_data) assert ( @@ -547,9 +513,7 @@ async def test_client_send_iq(*args, **kwargs): bumper.xmppserver.XMPPServer.clients.append(xmppclient2) # Roster IQ - Only seen from Android app so far - test_data = ''.encode( - "utf-8" - ) + test_data = b'' xmppclient._parse_data(test_data) assert ( @@ -561,9 +525,7 @@ async def test_client_send_iq(*args, **kwargs): mock_send.reset_mock() # Bot Command - test_data = ''.encode( - "utf-8" - ) + test_data = b'' xmppclient._parse_data(test_data) assert ( @@ -575,9 +537,7 @@ async def test_client_send_iq(*args, **kwargs): mock_send.reset_mock() # Bot response to query - test_data = ''.encode( - "utf-8" - ) + test_data = b'' xmppclient2._parse_data(test_data) assert ( @@ -589,9 +549,7 @@ async def test_client_send_iq(*args, **kwargs): mock_send.reset_mock() # Bot result - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient2._parse_data(test_data) assert ( @@ -603,9 +561,7 @@ async def test_client_send_iq(*args, **kwargs): mock_send.reset_mock() # Bot iq set - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient2._parse_data(test_data) assert ( @@ -617,9 +573,7 @@ async def test_client_send_iq(*args, **kwargs): mock_send.reset_mock() # Bot error report - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient2._parse_data(test_data) assert ( @@ -631,9 +585,7 @@ async def test_client_send_iq(*args, **kwargs): mock_send.reset_mock() # Bot "DorpError" to all - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient2._parse_data(test_data) assert ( mock_send.mock_calls[0].args[0] diff --git a/tests/test_z_problem.py b/tests/test_z_problem.py index 9600e50..8bdf5b8 100644 --- a/tests/test_z_problem.py +++ b/tests/test_z_problem.py @@ -1,4 +1,4 @@ -from mock import patch +from unittest.mock import patch import bumper From 57f7490c9270483392def72151f2dd095d4a29c4 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 11:41:56 +0100 Subject: [PATCH 03/10] fix tests --- .gitignore | 5 +- bumper/mqttserver.py | 14 ++-- tests/pytest.ini => pytest.ini | 3 + tests/__init__.py | 0 tests/const.py | 2 + tests/test_confserver.py | 28 +++---- tests/test_mqttserver.py | 133 ++++++++++++--------------------- tests/test_xmppserver.py | 64 ++++++++-------- 8 files changed, 111 insertions(+), 138 deletions(-) rename tests/pytest.ini => pytest.ini (82%) create mode 100644 tests/__init__.py create mode 100644 tests/const.py diff --git a/.gitignore b/.gitignore index 60219ab..19b97b7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,11 @@ __pycache__ !.dockerignore # Ignore items in test (report, cache, etc), except files starting with test -tests/* +tests/logs +tests/tmp.db !tests/test* !tests/test_certs -!tests/pytest.ini +!pytest.ini !tests/passwd !tests/passwd_bad diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index 6b0286b..f850ffc 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -42,19 +42,23 @@ class CommandDto: class MQTTHelperBot: Client = None - wait_resp_timeout_seconds = 60 - def __init__(self, host: str, port: int): - self._commands: MutableMapping[str, CommandDto] = TTLCache(maxsize=self.wait_resp_timeout_seconds * 60, - ttl=self.wait_resp_timeout_seconds + 10) + def __init__(self, host: str, port: int, timeout: float = 60): + self._commands: MutableMapping[str, CommandDto] = TTLCache(maxsize=timeout * 60, + ttl=timeout*1.1) self._host = host self._port = port self.client_id = "helperbot@bumper/helperbot" + self._timeout = timeout @property def commands(self) -> MutableMapping[str, CommandDto]: return self._commands + @property + def timeout(self)->float: + return self._timeout + async def start_helper_bot(self): try: if self.Client is None: @@ -74,7 +78,7 @@ class MQTTHelperBot: async def _wait_for_resp(self, command_dto: CommandDto, request_id: str): try: - payload = await asyncio.wait_for(command_dto.wait_for_response(), timeout=self.wait_resp_timeout_seconds) + payload = await asyncio.wait_for(command_dto.wait_for_response(), timeout=self.timeout) return { "id": request_id, "ret": "ok", diff --git a/tests/pytest.ini b/pytest.ini similarity index 82% rename from tests/pytest.ini rename to pytest.ini index 9ca2dc7..fe2b891 100644 --- a/tests/pytest.ini +++ b/pytest.ini @@ -3,3 +3,6 @@ env = D:BUMPER_CA=tests/test_certs/ca.crt D:BUMPER_CERT=tests/test_certs/bumper.crt D:BUMPER_KEY=tests/test_certs/bumper.key + +#log_cli=true +#log_level=DEBUG \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/const.py b/tests/const.py new file mode 100644 index 0000000..f2a229f --- /dev/null +++ b/tests/const.py @@ -0,0 +1,2 @@ +HOST= "127.0.0.1" +MQTT_PORT = 8883 diff --git a/tests/test_confserver.py b/tests/test_confserver.py index ba0e16f..058e40a 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -13,6 +13,8 @@ import logging from testfixtures import LogCapture from unittest.mock import MagicMock +from tests.const import HOST, MQTT_PORT + def create_confserver(): return bumper.ConfServer("127.0.0.1:11111", False) @@ -36,14 +38,14 @@ def remove_existing_db(): async def test_confserver_ssl(): - conf_server = bumper.ConfServer(("127.0.0.1", 111111), usessl=True) + conf_server = bumper.ConfServer((HOST, 111111), usessl=True) conf_server.confserver_app() asyncio.create_task(conf_server.start_server()) async def test_confserver_exceptions(): with LogCapture() as l: - conf_server = bumper.ConfServer(("127.0.0.1", 8007), usessl=True) + conf_server = bumper.ConfServer((HOST, 8007), usessl=True) conf_server.confserver_app() conf_server.site = web.TCPSite @@ -67,7 +69,7 @@ async def test_confserver_exceptions(): async def test_confserver_no_ssl(): - conf_server = bumper.ConfServer(("127.0.0.1", 111111), usessl=False) + conf_server = bumper.ConfServer((HOST, 111111), usessl=False) conf_server.confserver_app() asyncio.create_task(conf_server.start_server()) @@ -89,19 +91,18 @@ async def test_base(aiohttp_client): bumper.db = "tests/tmp.db" # Set db location for testing # Start MQTT - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") + mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") bumper.mqtt_server = mqtt_server await mqtt_server.broker_coro() # Start XMPP - xmpp_address = ("127.0.0.1", 5223) + xmpp_address = (HOST, 5223) xmpp_server = bumper.XMPPServer(xmpp_address) bumper.xmpp_server = xmpp_server await xmpp_server.start_async_server() # Start Helperbot - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() @@ -121,19 +122,18 @@ async def test_restartService(aiohttp_client): bumper.db = "tests/tmp.db" # Set db location for testing # Start MQTT - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") + mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") bumper.mqtt_server = mqtt_server await mqtt_server.broker_coro() # Start XMPP - xmpp_address = ("127.0.0.1", 5223) + xmpp_address = (HOST, 5223) xmpp_server = bumper.XMPPServer(xmpp_address) bumper.xmpp_server = xmpp_server await xmpp_server.start_async_server() # Start Helperbot - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() @@ -830,7 +830,7 @@ async def test_lg_logs(aiohttp_client): bumper.bot_set_mqtt("did_1234", True) confserver = create_confserver() client = await aiohttp_client(create_app) - bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot("127.0.0.1") + bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot(HOST, MQTT_PORT) # Test return get status command_getstatus_resp = { @@ -889,7 +889,7 @@ async def test_devmgr(aiohttp_client): bumper.db = "tests/tmp.db" # Set db location for testing confserver = create_confserver() client = await aiohttp_client(create_app) - bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot("127.0.0.1") + bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot(HOST, MQTT_PORT) # Test PollSCResult postbody = {"td": "PollSCResult"} @@ -945,7 +945,7 @@ async def test_dim_devmanager(aiohttp_client): bumper.db = "tests/tmp.db" # Set db location for testing confserver = create_confserver() client = await aiohttp_client(create_app) - bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot("127.0.0.1") + bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot(HOST, MQTT_PORT) # Test PollSCResult postbody = {"td": "PollSCResult"} diff --git a/tests/test_mqttserver.py b/tests/test_mqttserver.py index 7fc0918..aebbd8d 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -12,16 +12,16 @@ import logging from testfixtures import LogCapture import time +from tests.const import HOST, MQTT_PORT async def test_helperbot_message(): - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") + mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") await mqtt_server.broker_coro() with LogCapture() as l: # Test broadcast message - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start_helper_bot() assert ( mqtt_helperbot.Client._connected_state._value == True @@ -45,7 +45,7 @@ async def test_helperbot_message(): mqtt_helperbot.Client.disconnect() # Send command to bot - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start_helper_bot() assert ( mqtt_helperbot.Client._connected_state._value == True @@ -71,7 +71,7 @@ async def test_helperbot_message(): mqtt_helperbot.Client.disconnect() # Received response to command - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start_helper_bot() assert ( mqtt_helperbot.Client._connected_state._value == True @@ -97,7 +97,7 @@ async def test_helperbot_message(): mqtt_helperbot.Client.disconnect() # Received unknown message - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start_helper_bot() assert ( mqtt_helperbot.Client._connected_state._value == True @@ -124,7 +124,7 @@ async def test_helperbot_message(): mqtt_helperbot.Client.disconnect() # Received error message - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start_helper_bot() assert ( mqtt_helperbot.Client._connected_state._value == True @@ -151,78 +151,55 @@ async def test_helperbot_message(): async def test_helperbot_expire_message(): - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") + mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") await mqtt_server.broker_coro() - with LogCapture("helperbot") as l: + timeout = 0.1 + # Test broadcast message + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT,timeout) + bumper.mqtt_helperbot = mqtt_helperbot + await mqtt_helperbot.start_helper_bot() + assert ( + mqtt_helperbot.Client._connected_state._value == True + ) # Check helperbot is connected - # Test broadcast message - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) - bumper.mqtt_helperbot = mqtt_helperbot - await mqtt_helperbot.start_helper_bot() - assert ( - mqtt_helperbot.Client._connected_state._value == True - ) # Check helperbot is connected - - expire_msg_payload = '{"ret":"ok","ver":"0.13.5"}' - expire_msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" - currenttime = time.time() - mqtt_helperbot.command_responses.append( - { - "time": currenttime, - "topic": expire_msg_topic_name, - "payload": expire_msg_payload, - } - ) - - assert { + expire_msg_payload = '{"ret":"ok","ver":"0.13.5"}' + expire_msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" + currenttime = time.time() + request_id = "ABC" + data = { "time": currenttime, "topic": expire_msg_topic_name, "payload": expire_msg_payload, - } in mqtt_helperbot.command_responses # check message is in command_responses - - await asyncio.sleep(0.1) - mqtt_helperbot.expire_msg_seconds = ( - 0.1 - ) # Set expire message seconds to 0.1 so we don't wait 10 seconds - msg_payload = "" - msg_topic_name = "iot/atr/DustCaseST/bot_serial/ls1ok3/wC3g/x" - await mqtt_helperbot.Client.publish( - msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 - ) # Send another message to force get_msg + } - await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) + mqtt_helperbot.commands[request_id]= data + assert mqtt_helperbot.commands[request_id] == data - assert { - "time": currenttime, - "topic": expire_msg_topic_name, - "payload": expire_msg_payload, - } not in mqtt_helperbot.command_responses # check message was expired and removed from command_responses + await asyncio.sleep(0.1) + msg_payload = "" + msg_topic_name = "iot/atr/DustCaseST/bot_serial/ls1ok3/wC3g/x" + await mqtt_helperbot.Client.publish( + msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 + ) # Send another message to force get_msg - l.check_present( - ( - "helperbot", - "DEBUG", - "Pruning Message Due To Expiration - Message Topic: {}".format( - expire_msg_topic_name - ), - ) - ) # Check received message was logged - mqtt_helperbot.Client.disconnect() + await asyncio.sleep(timeout*2) + assert mqtt_helperbot.commands.get(request_id, None) == None + + await mqtt_helperbot.Client.disconnect() await mqtt_server.broker.shutdown() async def test_helperbot_sendcommand(): - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") + mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") await mqtt_server.broker_coro() - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + timeout = 0.1 + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT, timeout) bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() assert ( @@ -245,9 +222,6 @@ async def test_helperbot_sendcommand(): "realm": "ecouser.net", }, } - mqtt_helperbot.wait_resp_timeout_seconds = ( - 0.1 - ) # Override wait_resp_timeout (so we don't wait 10 seconds for timeout) commandresult = await mqtt_helperbot.send_command(cmdjson, "testfail") # Don't send a response, ensure timeout assert commandresult == { @@ -257,9 +231,6 @@ async def test_helperbot_sendcommand(): "ret": "fail", } # Check timeout - mqtt_helperbot.wait_resp_timeout_seconds = ( - 0.2 - ) # Override wait_resp_timeout (so we don't wait 10 seconds for timeout) # Send response beforehand msg_payload = '{"ret":"ok","ver":"0.13.5"}' msg_topic_name = ( @@ -296,9 +267,6 @@ async def test_helperbot_sendcommand(): }, } - mqtt_helperbot.wait_resp_timeout_seconds = ( - 0.2 - ) # Override wait_resp_timeout (so we don't wait 10 seconds for timeout) # Send response beforehand msg_payload = "" msg_topic_name = ( @@ -340,9 +308,6 @@ async def test_helperbot_sendcommand(): }, } - mqtt_helperbot.wait_resp_timeout_seconds = ( - 0.2 - ) # Override wait_resp_timeout (so we don't wait 10 seconds for timeout) # Send response beforehand msg_payload = '{"body":{"code":0,"data":{"area":0,"cid":"111","start":"1569378657","time":6,"type":"auto"},"msg":"ok"},"header":{"fwVer":"1.6.4","hwVer":"0.1.1","pri":1,"ts":"1569380074036","tzm":480,"ver":"0.0.1"}}' @@ -373,14 +338,13 @@ async def test_mqttserver(): bumper.db = "tests/tmp.db" # Set db location for testing - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd", allow_anonymous=True) + mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd", allow_anonymous=True) await mqtt_server.broker_coro() # Test helperbot connect - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) await mqtt_helperbot.start_helper_bot() assert ( mqtt_helperbot.Client._connected_state._value == True @@ -390,7 +354,7 @@ async def test_mqttserver(): # Test client connect bumper.user_add("user_123") # Add user to db bumper.client_add("user_123", "ecouser.net", "resource_123") # Add client to db - test_client = bumper.MQTTHelperBot(mqtt_address) + test_client = bumper.MQTTHelperBot(HOST, MQTT_PORT) test_client.client_id = "user_123@ecouser.net/resource_123" # await test_client.start_helper_bot() test_client.Client = hbmqtt.client.MQTTClient( @@ -398,7 +362,7 @@ async def test_mqttserver(): ) await test_client.Client.connect( - f"mqtts://{test_client.address[0]}:{test_client.address[1]}/", + f"mqtts://{HOST}:{MQTT_PORT}/", cafile=bumper.ca_cert, ) assert ( @@ -410,7 +374,7 @@ async def test_mqttserver(): ) # Check client is disconnected # Test fake_bot connect - fake_bot = bumper.MQTTHelperBot(mqtt_address) + fake_bot = bumper.MQTTHelperBot(HOST, MQTT_PORT) fake_bot.client_id = "bot_serial@ls1ok3/wC3g" await fake_bot.start_helper_bot() assert ( @@ -419,7 +383,7 @@ async def test_mqttserver(): await fake_bot.Client.disconnect() # Test file auth client connect - test_client = bumper.MQTTHelperBot(mqtt_address) + test_client = bumper.MQTTHelperBot(HOST, MQTT_PORT) test_client.client_id = "test-file-auth" # await test_client.start_helper_bot() test_client.Client = hbmqtt.client.MQTTClient( @@ -428,7 +392,7 @@ async def test_mqttserver(): # good user/pass await test_client.Client.connect( - f"mqtts://test-client:abc123!@{test_client.address[0]}:{test_client.address[1]}/", + f"mqtts://test-client:abc123!@{HOST}:{MQTT_PORT}/", cafile=bumper.ca_cert, cleansession=True ) @@ -444,7 +408,7 @@ async def test_mqttserver(): with LogCapture() as l: await test_client.Client.connect( - f"mqtts://test-client:notvalid!@{test_client.address[0]}:{test_client.address[1]}/", + f"mqtts://test-client:notvalid!@{HOST}:{MQTT_PORT}/", cafile=bumper.ca_cert, cleansession=True ) @@ -454,7 +418,7 @@ async def test_mqttserver(): ) # no username in file await test_client.Client.connect( - f"mqtts://test-client-noexist:notvalid!@{test_client.address[0]}:{test_client.address[1]}/", + f"mqtts://test-client-noexist:notvalid!@{HOST}:{MQTT_PORT}/", cafile=bumper.ca_cert, cleansession=True ) @@ -469,9 +433,8 @@ async def test_mqttserver(): async def test_nofileauth_mqttserver(): with LogCapture() as l: - - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd-notfound") + + mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd-notfound") await mqtt_server.broker_coro() await mqtt_server.broker.shutdown() diff --git a/tests/test_xmppserver.py b/tests/test_xmppserver.py index 5140338..776cd0d 100644 --- a/tests/test_xmppserver.py +++ b/tests/test_xmppserver.py @@ -73,12 +73,12 @@ async def test_client_connect_no_starttls(*args, **kwargs): assert mock_send.call_count == 2 # Server opens stream assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Server tells client available features assert ( - mock_send.mock_calls[1].args[0] + mock_send.mock_calls[1][1][0] == 'PLAIN' ) @@ -90,7 +90,7 @@ async def test_client_connect_no_starttls(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Client successfully authenticated assert xmppclient.state == xmppclient.INIT # Client moved to INIT state @@ -111,7 +111,7 @@ async def test_client_end_stream(*args, **kwargs): # Expect 2 calls to send assert mock_send.call_count == 1 # Server opens stream - assert mock_send.mock_calls[0].args[0] == "" + assert mock_send.mock_calls[0][1][0] == "" # Reset mock calls mock_send.reset_mock() @@ -144,12 +144,12 @@ async def test_client_connect_starttls_called(*args, **kwargs): assert mock_send.call_count == 2 # Server opens stream assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Server tells client available features assert ( - mock_send.mock_calls[1].args[0] + mock_send.mock_calls[1][1][0] == 'PLAIN' ) @@ -175,12 +175,12 @@ async def test_client_connect_starttls_called(*args, **kwargs): assert mock_send.call_count == 2 # Server opens stream assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Server tells client available features (without STARTTLS) assert ( - mock_send.mock_calls[1].args[0] + mock_send.mock_calls[1][1][0] == 'PLAIN' ) # Reset mock calls @@ -191,7 +191,7 @@ async def test_client_connect_starttls_called(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Client successfully authenticated assert xmppclient.state == xmppclient.INIT # Client moved to INIT state @@ -274,12 +274,12 @@ async def test_client_init(*args, **kwargs): assert mock_send.call_count == 2 # Server opens stream assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Server tells client binds assert ( - mock_send.mock_calls[1].args[0] + mock_send.mock_calls[1][1][0] == '' ) @@ -291,7 +291,7 @@ async def test_client_init(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == 'fuid_tmpuser@ecouser.net/IOSF53D07BA' ) # client successfully binded assert xmppclient.state == xmppclient.BIND # client moved to BIND state @@ -305,7 +305,7 @@ async def test_client_init(*args, **kwargs): assert xmppclient.state == xmppclient.READY # client moved to READY state assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # client ready @@ -317,7 +317,7 @@ async def test_client_init(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == ' dummy ' ) # client presence - dummy response @@ -338,12 +338,12 @@ async def test_bot_connect(*args, **kwargs): assert mock_send.call_count == 2 # Server opens stream assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Server tells client available features assert ( - mock_send.mock_calls[1].args[0] + mock_send.mock_calls[1][1][0] == 'PLAIN' ) @@ -355,7 +355,7 @@ async def test_bot_connect(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Bot successfully authenticated assert xmppclient.state == xmppclient.INIT # Bot moved to INIT state @@ -381,12 +381,12 @@ async def test_bot_init(*args, **kwargs): assert mock_send.call_count == 2 # Server opens stream assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # Server tells client binds assert ( - mock_send.mock_calls[1].args[0] + mock_send.mock_calls[1][1][0] == '' ) @@ -398,7 +398,7 @@ async def test_bot_init(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == 'E0000000000000001234@159.ecorobot.net/atom' ) # Bot successfully binded assert xmppclient.state == xmppclient.BIND # Bot moved to BIND state @@ -412,7 +412,7 @@ async def test_bot_init(*args, **kwargs): assert xmppclient.state == xmppclient.READY # Bot moved to READY state assert ( - mock_send.mock_calls[0].args[0] == '' + mock_send.mock_calls[0][1][0] == '' ) # Bot ready # Reset mock calls @@ -423,7 +423,7 @@ async def test_bot_init(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == ' dummy ' ) # bot presence - dummy response @@ -443,7 +443,7 @@ async def test_ping_server(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # ping response @@ -475,7 +475,7 @@ async def test_ping_client_to_client(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send2.mock_calls[0].args[0] + mock_send2.mock_calls[0][1][0] == '' ) # ping response @@ -484,7 +484,7 @@ async def test_ping_client_to_client(*args, **kwargs): xmppclient2._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # ping response @@ -517,7 +517,7 @@ async def test_client_send_iq(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # feature not implemented response @@ -529,7 +529,7 @@ async def test_client_send_iq(*args, **kwargs): xmppclient._parse_data(test_data) assert ( - mock_send2.mock_calls[0].args[0] + mock_send2.mock_calls[0][1][0] == '' ) # command was sent to bot @@ -541,7 +541,7 @@ async def test_client_send_iq(*args, **kwargs): xmppclient2._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to client @@ -553,7 +553,7 @@ async def test_client_send_iq(*args, **kwargs): xmppclient2._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to ecouser.net @@ -565,7 +565,7 @@ async def test_client_send_iq(*args, **kwargs): xmppclient2._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to ecouser.net @@ -577,7 +577,7 @@ async def test_client_send_iq(*args, **kwargs): xmppclient2._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to ecouser.net @@ -588,7 +588,7 @@ async def test_client_send_iq(*args, **kwargs): test_data = b"" xmppclient2._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to ecouser.net From 4102210ed0f4fe26274aba648391d8c5440ddfce Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 11:43:11 +0100 Subject: [PATCH 04/10] add ci --- .github/workflows/ci.yml | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e8720af --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: + - next + pull_request: + branches: + - next + +env: + DEFAULT_PYTHON: 3.7 + +jobs: + code-quality: + runs-on: "ubuntu-latest" + name: Check code quality + steps: + - uses: "actions/checkout@v2" + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v2.3.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + cache: "pip" + cache-dependency-path: "requirements*" + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install -r requirements-test.txt + # Following steps cannot run by pre-commit.ci as repo = local + - name: Run mypy + run: mypy bumper/ + - name: Pylint review + run: pylint bumper/ + + tests: + runs-on: "ubuntu-latest" + name: Run tests + steps: + - uses: "actions/checkout@v2" + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v2.3.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + cache: "pip" + cache-dependency-path: "requirements*" + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install -r requirements-test.txt + - name: Run pytest + run: pytest --cov=./ --cov-report=xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 + with: + fail_ci_if_error: true From a2157f7bc80416e62fc652376db52bfc17a30d40 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 11:48:52 +0100 Subject: [PATCH 05/10] add pytest-env --- requirements-test.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-test.txt b/requirements-test.txt index 1b34693..e5ef785 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -7,6 +7,7 @@ types-cachetools==4.2.9 pytest-asyncio pytest-aiohttp testfixtures +pytest-env #pbr = "*" #pytest-aiohttp = "*" From 80f778c031de65e937bee72a84fc81aa7563cb45 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 11:53:54 +0100 Subject: [PATCH 06/10] run black --- bumper/__init__.py | 31 +- bumper/__main__.py | 2 +- bumper/confserver.py | 300 +- bumper/db.py | 31 +- bumper/models.py | 525 +- bumper/mqttserver.py | 130 +- bumper/plugins.py | 5 +- .../bumper_confserver_portal_appsvr.py | 270 +- .../plugins/bumper_confserver_portal_dim.py | 26 +- .../plugins/bumper_confserver_portal_ecms.py | 19 +- .../plugins/bumper_confserver_portal_iot.py | 26 +- bumper/plugins/bumper_confserver_portal_lg.py | 15 +- .../plugins/bumper_confserver_portal_neng.py | 76 +- .../plugins/bumper_confserver_portal_pim.py | 4973 ++++++++--------- .../plugins/bumper_confserver_portal_rapp.py | 19 +- .../plugins/bumper_confserver_portal_users.py | 22 +- .../bumper_confserver_upload_global.py | 36 +- .../bumper_confserver_v1_global_auth.py | 15 +- .../bumper_confserver_v1_private_ad.py | 28 +- .../bumper_confserver_v1_private_campaign.py | 25 +- .../bumper_confserver_v1_private_common.py | 776 ++- .../bumper_confserver_v1_private_message.py | 31 +- .../bumper_confserver_v1_private_shop.py | 24 +- .../bumper_confserver_v1_private_user.py | 189 +- ...umper_confserver_v1_private_userSetting.py | 26 +- .../bumper_confserver_v2_private_user.py | 17 +- bumper/util.py | 12 +- bumper/xmppserver.py | 42 +- tests/const.py | 2 +- tests/test_confserver.py | 84 +- tests/test_init.py | 1 - tests/test_mqttserver.py | 142 +- tests/test_xmppserver.py | 5 +- 33 files changed, 4040 insertions(+), 3885 deletions(-) diff --git a/bumper/__init__.py b/bumper/__init__.py index 04080fa..fae5aad 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -66,7 +66,7 @@ sys.path.append(os.path.join(data_dir, "plugins")) discovered_plugins = { name: importlib.import_module(name) for finder, name, ispkg in pkgutil.iter_modules() - if name.startswith('bumper_') + if name.startswith("bumper_") } shutting_down = False @@ -104,9 +104,9 @@ async def start(): return if not ( - os.path.exists(ca_cert) - and os.path.exists(server_cert) - and os.path.exists(server_key) + os.path.exists(ca_cert) + and os.path.exists(server_cert) + and os.path.exists(server_key) ): bumperlog.fatal("Certificate(s) don't exist at paths specified") return @@ -143,9 +143,15 @@ async def start(): # Start web servers conf_server.confserver_app() asyncio.create_task( - conf_server.start_site(conf_server.app, address=bumper_listen, port=conf1_listen_port, usessl=True)) + 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)) + conf_server.start_site( + conf_server.app, address=bumper_listen, port=conf2_listen_port, usessl=False + ) + ) # Start maintenance while not shutting_down: @@ -200,18 +206,17 @@ def main(argv=None): try: if not ( - os.path.exists(ca_cert) - and os.path.exists(server_cert) - and os.path.exists(server_key) + os.path.exists(ca_cert) + and os.path.exists(server_cert) + and os.path.exists(server_key) ): msg = "No certs found! Please generate them (More infos in the docs)" bumperlog.fatal(msg) sys.exit(msg) - if not ( - os.path.exists(os.path.join(data_dir, "passwd")) - ): - with open(os.path.join(data_dir, "passwd"), 'w'): pass + if not (os.path.exists(os.path.join(data_dir, "passwd"))): + with open(os.path.join(data_dir, "passwd"), "w"): + pass parser = argparse.ArgumentParser() parser.add_argument( diff --git a/bumper/__main__.py b/bumper/__main__.py index 7434132..d2c45b2 100644 --- a/bumper/__main__.py +++ b/bumper/__main__.py @@ -1,4 +1,4 @@ import bumper if __name__ == "__main__": - bumper.main() \ No newline at end of file + bumper.main() diff --git a/bumper/confserver.py b/bumper/confserver.py index 8dd4918..e8fb066 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -13,6 +13,7 @@ from bumper import plugins from bumper.models import * from .util import get_logger + class aiohttp_filter(logging.Filter): def filter(self, record): if ( @@ -21,10 +22,7 @@ class aiohttp_filter(logging.Filter): record.levelno = 10 record.levelname = "DEBUG" - if ( - record.levelno == 10 - and get_logger("confserver").getEffectiveLevel() == 10 - ): + if record.levelno == 10 and get_logger("confserver").getEffectiveLevel() == 10: return True else: return False @@ -50,60 +48,75 @@ class ConfServer: return int(round(timetoconvert * 1000)) def confserver_app(self): - self.app = web.Application(loop=asyncio.get_event_loop(), middlewares=[ - self.log_all_requests, - ]) - aiohttp_jinja2.setup(self.app, loader=jinja2.FileSystemLoader(os.path.join(bumper.bumper_dir,"bumper","web","templates"))) + self.app = web.Application( + loop=asyncio.get_event_loop(), + 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("", self.handle_base, name="base"), - web.get("/bot/remove/{did}", self.handle_RemoveBot, name='remove-bot'), - web.get("/client/remove/{resource}", self.handle_RemoveClient, name='remove-client'), - web.get("/restart_{service}", self.handle_RestartService, name='restart-service'), + web.get("/bot/remove/{did}", self.handle_RemoveBot, name="remove-bot"), + web.get( + "/client/remove/{resource}", + self.handle_RemoveClient, + name="remove-client", + ), + web.get( + "/restart_{service}", + self.handle_RestartService, + name="restart-service", + ), web.post("/lookup.do", self.handle_lookup), web.post("/newauth.do", self.handle_newauth), ] ) # common api paths - api_v1 = {"prefix": "/v1/", "app": web.Application()} # for /v1/ - api_v2 = {"prefix": "/v2/", "app": web.Application()} # for /v2/ - portal_api = {"prefix": "/api/", "app": web.Application()} # for /api/ - upload_api = {"prefix": "/upload/", "app": web.Application()} # for /upload/ - + api_v1 = {"prefix": "/v1/", "app": web.Application()} # for /v1/ + api_v2 = {"prefix": "/v2/", "app": web.Application()} # for /v2/ + portal_api = {"prefix": "/api/", "app": web.Application()} # for /api/ + upload_api = {"prefix": "/upload/", "app": web.Application()} # for /upload/ + apis = { "api_v1": api_v1, "api_v2": api_v2, "portal_api": portal_api, "upload_api": upload_api, - } - + # Load plugins for plug in bumper.discovered_plugins: - if isinstance(bumper.discovered_plugins[plug].plugin, bumper.plugins.ConfServerApp): - plugin = bumper.discovered_plugins[plug].plugin - if plugin.plugin_type == "sub_api": # app or sub_api + if isinstance( + bumper.discovered_plugins[plug].plugin, bumper.plugins.ConfServerApp + ): + plugin = bumper.discovered_plugins[plug].plugin + if plugin.plugin_type == "sub_api": # app or sub_api if plugin.sub_api in apis: if plugin.routes: logging.debug(f"Adding confserver sub_api ({plugin.name})") apis[plugin.sub_api]["app"].add_routes(plugin.routes) - + elif plugin.plugin_type == "app": if plugin.path_prefix and plugin.app: logging.debug(f"Adding confserver plugin ({plugin.name})") - self.app.add_subapp(plugin.path_prefix, plugin.app) - - for api in apis: + self.app.add_subapp(plugin.path_prefix, plugin.app) + + for api in apis: self.app.add_subapp(apis[api]["prefix"], apis[api]["app"]) - #for resource in self.app.router.resources(): + # for resource in self.app.router.resources(): # print(resource) - - - 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) self.runners.append(runner) await runner.setup() @@ -118,9 +131,7 @@ class ConfServer: ) else: - site = web.TCPSite( - runner, host=address, port=port - ) + site = web.TCPSite(runner, host=address, port=port) await site.start() @@ -178,12 +189,14 @@ class ConfServer: mq_sessions = [] for sess in mqttserver._sessions: tmpsess = [] - tmpsess.append({ - "username": mqttserver._sessions[sess][0].username, - "client_id": mqttserver._sessions[sess][0].client_id, - "state": mqttserver._sessions[sess][0].transitions.state, - }) - + tmpsess.append( + { + "username": mqttserver._sessions[sess][0].username, + "client_id": mqttserver._sessions[sess][0].client_id, + "state": mqttserver._sessions[sess][0].transitions.state, + } + ) + mq_sessions.append(tmpsess) all = { "bots": bots, @@ -198,10 +211,10 @@ class ConfServer: ] }, ], - "xmpp_server": xmppserver - } - resp = aiohttp_jinja2.render_template('home.jinja2', request, context=all) - #return web.json_response(all) + "xmpp_server": xmppserver, + } + resp = aiohttp_jinja2.render_template("home.jinja2", request, context=all) + # return web.json_response(all) return resp except Exception as e: @@ -233,7 +246,7 @@ class ConfServer: except Exception as e: confserverlog.error(f"Request body not json: {e} - {e.doc}") postbody = e.doc - + else: postbody = await request.post() @@ -252,7 +265,7 @@ class ConfServer: to_log["response"]["body"] = f"{json.loads(response.body)}" confserverlog.debug(json.dumps(to_log)) - + return response except web.HTTPNotFound as notfound: @@ -263,7 +276,7 @@ class ConfServer: except Exception as e: confserverlog.exception(f"{e}") confserverlog.error(json.dumps(to_log)) - return e + return e else: return await handler(request) @@ -274,24 +287,26 @@ class ConfServer: asyncio.create_task(bumper.mqtt_helperbot.start_helper_bot()) async def restart_MQTT(self): - - if not (bumper.mqtt_server.broker.transitions.state == "stopped" or bumper.mqtt_server.broker.transitions.state == "not_started"): + + if not ( + bumper.mqtt_server.broker.transitions.state == "stopped" + or bumper.mqtt_server.broker.transitions.state == "not_started" + ): # close session writers - this was required so bots would reconnect properly after restarting - for sess in list(bumper.mqtt_server.broker._sessions): + for sess in list(bumper.mqtt_server.broker._sessions): sessobj = bumper.mqtt_server.broker._sessions[sess][1] if sessobj.session.transitions.state == "connected": await sessobj.writer.close() - #await bumper.mqtt_server.broker.shutdown() + # await bumper.mqtt_server.broker.shutdown() aloop = asyncio.get_event_loop() aloop.call_later( - 0.1, lambda: asyncio.create_task(bumper.mqtt_server.broker.shutdown()) + 0.1, lambda: asyncio.create_task(bumper.mqtt_server.broker.shutdown()) ) # In .1 seconds shutdown broker - aloop = asyncio.get_event_loop() aloop.call_later( - 1.5, lambda: asyncio.create_task(bumper.mqtt_server.broker_coro()) + 1.5, lambda: asyncio.create_task(bumper.mqtt_server.broker_coro()) ) # In 1.5 seconds start broker async def restart_XMPP(self): @@ -310,7 +325,7 @@ class ConfServer: aloop.call_later( 5, lambda: asyncio.create_task(self.restart_Helper()) ) # In 5 seconds restart Helperbot - + return web.json_response({"status": "complete"}) elif service == "XMPPServer": await self.restart_XMPP() @@ -333,29 +348,27 @@ class ConfServer: except Exception as e: confserverlog.exception(f"{e}") - pass + pass async def handle_RemoveClient(self, request): - try: + try: resource = request.match_info.get("resource", "") bumper.client_remove(resource) if bumper.client_get(resource): - return web.json_response({"status": "failed to remove client"}) + return web.json_response({"status": "failed to remove client"}) else: - return web.json_response({"status": "successfully removed client"}) + return web.json_response({"status": "successfully removed client"}) except Exception as e: confserverlog.exception(f"{e}") - pass + pass async def handle_login(self, request): try: user_devid = request.match_info.get("devid", "") countrycode = request.match_info.get("country", "us") apptype = request.match_info.get("apptype", "") - confserverlog.info( - f"client with devid {user_devid} attempting login" - ) + confserverlog.info(f"client with devid {user_devid} attempting login") if bumper.use_auth: if ( not user_devid == "" @@ -476,11 +489,7 @@ class ConfServer: confserverlog.debug(postbody) - body = { - "authCode": postbody["itToken"], - "result": "ok", - "todo": "result" - } + body = {"authCode": postbody["itToken"], "result": "ok", "todo": "result"} return web.json_response(body) @@ -500,13 +509,15 @@ class ConfServer: pass def get_milli_time(self, timetoconvert): - return int(round(timetoconvert * 1000)) + return int(round(timetoconvert * 1000)) class ConfServer_AuthHandler: def __init__(self): - self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + self.get_milli_time = ( + bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + ) pass - + def generate_token(self, user): try: tmpaccesstoken = uuid.uuid4().hex @@ -523,80 +534,80 @@ class ConfServer: return tmpauthcode except Exception as e: - confserverlog.exception(f"{e}") - + confserverlog.exception(f"{e}") async def login(self, request): - try: - user_devid = request.match_info.get("devid", "") - countrycode = request.match_info.get("country", "us") - apptype = request.match_info.get("apptype", "") - confserverlog.info( - f"client with devid {user_devid} attempting login" - ) - if bumper.use_auth: - if ( - not user_devid == "" - ): # Performing basic "auth" using devid, super insecure - user = bumper.user_by_deviceid(user_devid) - if "checkLogin" in request.path: - self.check_token( - apptype, countrycode, user, request.query["accessToken"] + try: + user_devid = request.match_info.get("devid", "") + countrycode = request.match_info.get("country", "us") + apptype = request.match_info.get("apptype", "") + confserverlog.info(f"client with devid {user_devid} attempting login") + if bumper.use_auth: + if ( + not user_devid == "" + ): # Performing basic "auth" using devid, super insecure + user = bumper.user_by_deviceid(user_devid) + if "checkLogin" in request.path: + self.check_token( + apptype, countrycode, user, request.query["accessToken"] + ) + else: + if "global_" in apptype: # EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(user["userid"]) + login_details.loginName = "fusername_{}".format( + user["userid"] ) + login_details.mobile = None + else: - if "global_" in apptype: # EcoVacs Home - login_details = EcoVacsHome_Login() - login_details.ucUid = "fuid_{}".format(user["userid"]) - login_details.loginName = "fusername_{}".format( - user["userid"] - ) - login_details.mobile = None + login_details = EcoVacs_Login() - else: - login_details = EcoVacs_Login() + # Deactivate old tokens and authcodes + bumper.user_revoke_expired_tokens(user["userid"]) - # Deactivate old tokens and authcodes - bumper.user_revoke_expired_tokens(user["userid"]) + login_details.accessToken = self.generate_token(user) + login_details.uid = "fuid_{}".format(user["userid"]) + login_details.username = "fusername_{}".format( + user["userid"] + ) + login_details.country = countrycode + login_details.email = "null@null.com" - login_details.accessToken = self.generate_token(user) - login_details.uid = "fuid_{}".format(user["userid"]) - login_details.username = "fusername_{}".format(user["userid"]) - login_details.country = countrycode - login_details.email = "null@null.com" + body = { + "code": API_ERRORS[RETURN_API_SUCCESS], + "data": json.loads(login_details.toJSON()), + # { + # "accessToken": self.generate_token(tmpuser), # Generate a token + # "country": countrycode, + # "email": "null@null.com", + # "uid": "fuid_{}".format(tmpuser["userid"]), + # "username": "fusername_{}".format(tmpuser["userid"]), + # }, + "msg": "操作成功", + "time": self.get_milli_time( + datetime.utcnow().timestamp() + ), + } - body = { - "code": API_ERRORS[RETURN_API_SUCCESS], - "data": json.loads(login_details.toJSON()), - # { - # "accessToken": self.generate_token(tmpuser), # Generate a token - # "country": countrycode, - # "email": "null@null.com", - # "uid": "fuid_{}".format(tmpuser["userid"]), - # "username": "fusername_{}".format(tmpuser["userid"]), - # }, - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } + return web.json_response(body) - return web.json_response(body) + body = { + "code": bumper.ERR_USER_NOT_ACTIVATED, + "data": None, + "msg": "当前密码错误", + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } - body = { - "code": bumper.ERR_USER_NOT_ACTIVATED, - "data": None, - "msg": "当前密码错误", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } + return web.json_response(body) - return web.json_response(body) - - else: - return web.json_response( - self._auth_any(user_devid, apptype, countrycode, request) - ) - - except Exception as e: - confserverlog.exception(f"{e}") + else: + return web.json_response( + self._auth_any(user_devid, apptype, countrycode, request) + ) + except Exception as e: + confserverlog.exception(f"{e}") async def get_AuthCode(self, request): try: @@ -660,7 +671,7 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception(f"{e}") + confserverlog.exception(f"{e}") def check_token(self, apptype, countrycode, user, token): try: @@ -705,7 +716,7 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception(f"{e}") + confserverlog.exception(f"{e}") def _auth_any(self, devid, apptype, country, request): try: @@ -719,7 +730,9 @@ class ConfServer: if "global_" in apptype: # EcoVacs Home login_details = EcoVacsHome_Login() login_details.ucUid = "fuid_{}".format(tmpuser["userid"]) - login_details.loginName = "fusername_{}".format(tmpuser["userid"]) + login_details.loginName = "fusername_{}".format( + tmpuser["userid"] + ) login_details.mobile = None else: login_details = EcoVacs_Login() @@ -736,7 +749,9 @@ class ConfServer: if "global_" in apptype: # EcoVacs Home login_details = EcoVacsHome_Login() login_details.ucUid = "fuid_{}".format(tmpuser["userid"]) - login_details.loginName = "fusername_{}".format(tmpuser["userid"]) + login_details.loginName = "fusername_{}".format( + tmpuser["userid"] + ) login_details.mobile = None else: login_details = EcoVacs_Login() @@ -754,7 +769,9 @@ class ConfServer: else: confserverlog.error(f"No DID for bot: {bot}") - if "checkLogin" in request.path: # If request was to check a token do so + if ( + "checkLogin" in request.path + ): # If request was to check a token do so checkToken = self.check_token( apptype, countrycode, tmpuser, request.query["accessToken"] ) @@ -782,8 +799,7 @@ class ConfServer: return body except Exception as e: - confserverlog.exception(f"{e}") - + confserverlog.exception(f"{e}") def getUserAccountInfo(self, request): try: @@ -851,7 +867,7 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception(f"{e}") + confserverlog.exception(f"{e}") async def logout(self, request): try: @@ -859,7 +875,9 @@ class ConfServer: if not user_devid == "": user = bumper.user_by_deviceid(user_devid) if user: - if bumper.check_token(user["userid"], request.query["accessToken"]): + if bumper.check_token( + user["userid"], request.query["accessToken"] + ): # Deactivate old tokens and authcodes bumper.user_revoke_token( user["userid"], request.query["accessToken"] @@ -875,4 +893,4 @@ class ConfServer: return web.json_response(body) except Exception as e: - confserverlog.exception(f"{e}") \ No newline at end of file + confserverlog.exception(f"{e}") diff --git a/bumper/db.py b/bumper/db.py index 075ada1..09e99ef 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -8,7 +8,13 @@ from tinydb import TinyDB, Query import bumper from .util import get_logger -from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts, OAuth +from bumper.models import ( + VacBotClient, + VacBotDevice, + BumperUser, + EcoVacsHomeProducts, + OAuth, +) bumperlog = get_logger("bumper") @@ -265,8 +271,8 @@ def check_authcode(uid, authcode): tmpauth = tokens.get( (Query().authcode == authcode) & ( # Match authcode - (Query().userid == uid.replace("fuid_", "")) - | (Query().userid == f"fuid_{uid}") + (Query().userid == uid.replace("fuid_", "")) + | (Query().userid == f"fuid_{uid}") ) # Userid with or without fuid_ ) if tmpauth: @@ -279,7 +285,8 @@ def loginByItToken(authcode): bumperlog.debug(f"Checking for authcode: {authcode}") tokens = db_get().table("tokens") tmpauth = tokens.get( - Query().authcode == authcode + Query().authcode + == authcode # & ( # Match authcode # (Query().userid == uid.replace("fuid_", "")) # | (Query().userid == "fuid_{}".format(uid)) @@ -297,8 +304,8 @@ def check_token(uid, token): tmpauth = tokens.get( (Query().token == token) & ( # Match token - (Query().userid == uid.replace("fuid_", "")) - | (Query().userid == f"fuid_{uid}") + (Query().userid == uid.replace("fuid_", "")) + | (Query().userid == f"fuid_{uid}") ) # Userid with or without fuid_ ) if tmpauth: @@ -326,11 +333,9 @@ def bot_add(sn, did, devclass, resource, company): bot = bot_get(did) if not bot: # Not existing bot in database if ( - not devclass == "" or "@" not in sn or "tmp" not in sn + not devclass == "" or "@" not in sn or "tmp" not in sn ): # try to prevent bad additions to the bot list - bumperlog.info( - f"Adding new bot with SN: {newbot.name} DID: {newbot.did}" - ) + bumperlog.info(f"Adding new bot with SN: {newbot.name} DID: {newbot.did}") bot_full_upsert(newbot.asdict()) @@ -357,7 +362,11 @@ def bot_toEcoVacsHome_JSON(bot): # EcoVacs Home 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["product_category"] = ( + "DEEBOT" + if botprod["product"]["name"].startswith("DEEBOT") + else "UNKNOWN" + ) # bot["updateInfo"] = { # "changeLog": "", # "needUpdate": False diff --git a/bumper/models.py b/bumper/models.py index 0a3c533..2a79321 100644 --- a/bumper/models.py +++ b/bumper/models.py @@ -8,7 +8,7 @@ import bumper class VacBotDevice: def __init__( - self, did="", vac_bot_device_class="", resource="", name="", nick="", company="" + self, did="", vac_bot_device_class="", resource="", name="", nick="", company="" ): self.vac_bot_device_class = vac_bot_device_class self.company = company @@ -101,7 +101,9 @@ class OAuth: oauth = OAuth() oauth.userId = userId oauth.access_token = uuid.uuid4().hex - oauth.expire_at = f"{datetime.utcnow() + timedelta(days=bumper.oauth_validity_days)}" + oauth.expire_at = ( + f"{datetime.utcnow() + timedelta(days=bumper.oauth_validity_days)}" + ) oauth.refresh_token = uuid.uuid4().hex return oauth @@ -110,8 +112,11 @@ class OAuth: def toResponse(self): data = self.__dict__ - data["expire_at"] = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time( - datetime.fromisoformat(self.expire_at).timestamp()) + data[ + "expire_at" + ] = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time( + datetime.fromisoformat(self.expire_at).timestamp() + ) return data @@ -132,10 +137,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2d996c4d60de0001eaf2b5" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2d996c4d60de0001eaf2b5", + }, }, { "classid": "vsc5ia", @@ -151,10 +156,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c874326280fda0001770d2a" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c874326280fda0001770d2a", + }, }, { "classid": "zi1uwd", @@ -170,10 +175,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5da834a8d66cd10001f58265" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5da834a8d66cd10001f58265", + }, }, { "classid": "12baap", @@ -189,10 +194,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425981269020008a9627b" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425981269020008a9627b", + }, }, { "classid": "02uwxm", @@ -208,10 +213,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1", + }, }, { "classid": "eyi9jv", @@ -227,10 +232,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7b65f176f7f10001e9a0c2" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7b65f176f7f10001e9a0c2", + }, }, { "classid": "9rft3c", @@ -246,10 +251,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6062795ad18cbd0008e2fce8" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6062795ad18cbd0008e2fce8", + }, }, { "classid": "141", @@ -265,10 +270,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c2aa64d60de0001eaf1f6" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c2aa64d60de0001eaf1f6", + }, }, { "classid": "fqxoiu", @@ -284,10 +289,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605b059217c95b0008ff20d4" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605b059217c95b0008ff20d4", + }, }, { "classid": "u6eqoa", @@ -303,10 +308,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425a11269020008a9627d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425a11269020008a9627d", + }, }, { "classid": "dl8fht", @@ -322,10 +327,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea", + }, }, { "classid": "yna5xi", @@ -341,10 +346,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606278df4a84d700082b39f1" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606278df4a84d700082b39f1", + }, }, { "classid": "123", @@ -360,10 +365,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c150dba13eb00013feaae" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c150dba13eb00013feaae", + }, }, { "classid": "140", @@ -379,10 +384,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c152f4d60de0001eaf1f4" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c152f4d60de0001eaf1f4", + }, }, { "classid": "q1v5dn", @@ -398,10 +403,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d83375f6b6a570001569e26" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d83375f6b6a570001569e26", + }, }, { "classid": "16wdph", @@ -417,10 +422,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d280ce3350e7a0001e84c95" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d280ce3350e7a0001e84c95", + }, }, { "classid": "rvo6ev", @@ -436,10 +441,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426feb0a931000860fad5" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426feb0a931000860fad5", + }, }, { "classid": "09m4bu", @@ -455,10 +460,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ef31b8cee3c1200075b6f67" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ef31b8cee3c1200075b6f67", + }, }, { "classid": "y2qy3m", @@ -474,10 +479,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425784a84d700082b39f6" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425784a84d700082b39f6", + }, }, { "classid": "0xyhhr", @@ -493,10 +498,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d117d4f0ac6ad00012b792d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d117d4f0ac6ad00012b792d", + }, }, { "classid": "ipzjy0", @@ -512,10 +517,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426c64a84d700082b39fa" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426c64a84d700082b39fa", + }, }, { "classid": "hsgwhi", @@ -531,10 +536,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e731a4a06f6de700464c69d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e731a4a06f6de700464c69d", + }, }, { "classid": "h18jkh", @@ -550,10 +555,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8e8d146482551d72530e47" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8e8d146482551d72530e47", + }, }, { "classid": "126", @@ -569,10 +574,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32ab2e9e9270001354b3d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32ab2e9e9270001354b3d", + }, }, { "classid": "x5d34r", @@ -588,10 +593,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605053e7fc527c00087fda1e" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605053e7fc527c00087fda1e", + }, }, { "classid": "ls1ok3", @@ -607,10 +612,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839", + }, }, { "classid": "y79a7u", @@ -626,10 +631,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7", + }, }, { "classid": "vi829v", @@ -645,10 +650,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606278d3fc527c00087fdb08" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606278d3fc527c00087fdb08", + }, }, { "classid": "55aiho", @@ -664,10 +669,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605be27250928b0007c13264" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605be27250928b0007c13264", + }, }, { "classid": "gd4uut", @@ -683,10 +688,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8da019032edd9008c66bf0" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8da019032edd9008c66bf0", + }, }, { "classid": "130", @@ -702,10 +707,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7640de51dd0001fee131" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7640de51dd0001fee131", + }, }, { "classid": "2pv572", @@ -721,10 +726,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d1474632a6bd50001b5b6f3" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d1474632a6bd50001b5b6f3", + }, }, { "classid": "xb83mv", @@ -740,10 +745,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d3fe649de51dd0001fee0de" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d3fe649de51dd0001fee0de", + }, }, { "classid": "ar5bjb", @@ -759,10 +764,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e58a2df36e8f39e318f031d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e58a2df36e8f39e318f031d", + }, }, { "classid": "7j1tu6", @@ -778,10 +783,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064260545505e0008e5cb49" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064260545505e0008e5cb49", + }, }, { "classid": "ts2ofl", @@ -797,10 +802,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425dfd18cbd0008e2fcf3" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425dfd18cbd0008e2fcf3", + }, }, { "classid": "125", @@ -816,10 +821,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c14414d60de0001eaf1f2" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c14414d60de0001eaf1f2", + }, }, { "classid": "jh3ry2", @@ -835,10 +840,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6049b34d1269020008a95aef" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6049b34d1269020008a95aef", + }, }, { "classid": "wlqdkp", @@ -854,10 +859,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064263ad18cbd0008e2fcf4" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064263ad18cbd0008e2fcf4", + }, }, { "classid": "c0lwyn", @@ -873,10 +878,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425ab4a84d700082b39f7" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425ab4a84d700082b39f7", + }, }, { "classid": "emzppx", @@ -892,10 +897,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c931fef280fda0001770d7e" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c931fef280fda0001770d7e", + }, }, { "classid": "115", @@ -911,10 +916,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cf711aeb0acfc000179ff8a" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cf711aeb0acfc000179ff8a", + }, }, { "classid": "jr3pqa", @@ -930,10 +935,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769", + }, }, { "classid": "142", @@ -949,10 +954,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca1ca79e9e9270001354b2d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca1ca79e9e9270001354b2d", + }, }, { "classid": "aqdd5p", @@ -968,10 +973,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cb7cfbab72c4d00010e5fc7" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cb7cfbab72c4d00010e5fc7", + }, }, { "classid": "152", @@ -987,10 +992,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7628de51dd0001fee12f" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7628de51dd0001fee12f", + }, }, { "classid": "d0cnel", @@ -1006,10 +1011,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d157f9f77a3a60001051f69" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d157f9f77a3a60001051f69", + }, }, { "classid": "u4h1uk", @@ -1025,10 +1030,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425bed18cbd0008e2fcf2" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425bed18cbd0008e2fcf2", + }, }, { "classid": "3ab24g", @@ -1044,10 +1049,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ef31b80f5dcdf000767cf4d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ef31b80f5dcdf000767cf4d", + }, }, { "classid": "9akc61", @@ -1063,10 +1068,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c932067280fda0001770d7f" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c932067280fda0001770d7f", + }, }, { "classid": "159", @@ -1082,10 +1087,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7606de51dd0001fee12d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7606de51dd0001fee12d", + }, }, { "classid": "b742vd", @@ -1101,10 +1106,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8e93a7032edd3f5ec66d4a" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e8e93a7032edd3f5ec66d4a", + }, }, { "classid": "uv242z", @@ -1120,10 +1125,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9", + }, }, { "classid": "155", @@ -1139,10 +1144,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cd4ca505b032200015a455d" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5cd4ca505b032200015a455d", + }, }, { "classid": "1qdu4z", @@ -1158,10 +1163,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e71c7df298f0d9cabfef86f" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e71c7df298f0d9cabfef86f", + }, }, { "classid": "4zfacv", @@ -1177,10 +1182,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c778731280fda0001770ba0" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c778731280fda0001770ba0", + }, }, { "classid": "nq9yhl", @@ -1196,10 +1201,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426ebb0a931000860fad4" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426ebb0a931000860fad4", + }, }, { "classid": "m7lqzi", @@ -1215,10 +1220,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c63a5ba13eb00013feab7" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c63a5ba13eb00013feab7", + }, }, { "classid": "r8ead0", @@ -1234,10 +1239,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c93204b63023c0001e7faa7" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5c93204b63023c0001e7faa7", + }, }, { "classid": "jjccwk", @@ -1253,10 +1258,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d3aa309ba13eb00013feb69" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5d3aa309ba13eb00013feb69", + }, }, { "classid": "129", @@ -1272,10 +1277,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca31df112851900016858c0" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca31df112851900016858c0", + }, }, { "classid": "165", @@ -1291,10 +1296,10 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32a1012851900016858c6" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32a1012851900016858c6", + }, }, { "classid": "jffnlf", @@ -1310,10 +1315,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e53207a26be71596c4b55cd" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5e53207a26be71596c4b55cd", + }, }, { "classid": "d4v1pm", @@ -1329,10 +1334,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426254a84d700082b39f9" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606426254a84d700082b39f9", + }, }, { "classid": "34vhpm", @@ -1348,10 +1353,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605050031269020008a95af9" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/605050031269020008a95af9", + }, }, { "classid": "tpnwyu", @@ -1367,10 +1372,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6050503e1269020008a95afa" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6050503e1269020008a95afa", + }, }, { "classid": "wgxm70", @@ -1386,10 +1391,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5edf2bbedb28cc00062f8bd7" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5edf2bbedb28cc00062f8bd7", + }, }, { "classid": "1zqysa", @@ -1405,10 +1410,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064258d1269020008a9627a" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6064258d1269020008a9627a", + }, }, { "classid": "chmi0g", @@ -1424,10 +1429,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425821269020008a96279" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/606425821269020008a96279", + }, }, { "classid": "p5nx9u", @@ -1443,10 +1448,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5f59e774c0f03a0008ee72e0" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5f59e774c0f03a0008ee72e0", + }, }, { "classid": "n6cwdb", @@ -1462,10 +1467,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627a92fc527c00087fdb0a" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627a92fc527c00087fdb0a", + }, }, { "classid": "lhbd50", @@ -1481,10 +1486,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/603f51243b03f50007b6c2ca" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/603f51243b03f50007b6c2ca", + }, }, { "classid": "ucn2xe", @@ -1500,10 +1505,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/603f510e3b03f50007b6c2c9" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/603f510e3b03f50007b6c2c9", + }, }, { "classid": "0bdtzz", @@ -1519,10 +1524,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5fa105bbd16a99000667eb52" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5fa105bbd16a99000667eb52", + }, }, { "classid": "r5zxjr", @@ -1538,10 +1543,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627c09b0a931000860facd" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627c09b0a931000860facd", + }, }, { "classid": "r5y7re", @@ -1557,10 +1562,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627a9cb0a931000860fac7" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627a9cb0a931000860fac7", + }, }, { "classid": "snxbvc", @@ -1576,10 +1581,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bbfb0a931000860fac9" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bbfb0a931000860fac9", + }, }, { "classid": "7bryc5", @@ -1595,10 +1600,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5fb474d4d16a99000667edd9" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5fb474d4d16a99000667edd9", + }, }, { "classid": "b2jqs4", @@ -1614,10 +1619,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb27d4cb3a0006679047" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb27d4cb3a0006679047", + }, }, { "classid": "yu362x", @@ -1633,10 +1638,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bde50928b0007c13273" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bde50928b0007c13273", + }, }, { "classid": "ifbw08", @@ -1652,10 +1657,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb47d4cb3a0006679048" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb47d4cb3a0006679048", + }, }, { "classid": "85as7h", @@ -1671,10 +1676,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb585f437d0008e0e00c" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5feaeb585f437d0008e0e00c", + }, }, { "classid": "ty84oi", @@ -1690,10 +1695,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bcafc527c00087fdb0c" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bcafc527c00087fdb0c", + }, }, { "classid": "36xnxf", @@ -1709,10 +1714,10 @@ EcoVacsHomeProducts = [ "share": True, "tmjl": False, "assistant": True, - "alexa": True + "alexa": True, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bd517c95b0008ff20ec" - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/60627bd517c95b0008ff20ec", + }, }, { "classid": "2pj946", @@ -1728,11 +1733,11 @@ EcoVacsHomeProducts = [ "share": False, "tmjl": False, "assistant": False, - "alexa": False + "alexa": False, }, - "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6049b3631269020008a95af0" - } - } + "iconUrl": "https://portal-ww.ecouser.net/api/pim/file/get/6049b3631269020008a95af0", + }, + }, ] RETURN_API_SUCCESS = "0000" diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index f850ffc..8b9a90f 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -22,7 +22,6 @@ boterrorlog = get_logger("boterror") class CommandDto: - def __init__(self, payload_type: str) -> None: self._payload_type = payload_type self._event = asyncio.Event() @@ -44,8 +43,9 @@ class MQTTHelperBot: Client = None def __init__(self, host: str, port: int, timeout: float = 60): - self._commands: MutableMapping[str, CommandDto] = TTLCache(maxsize=timeout * 60, - ttl=timeout*1.1) + self._commands: MutableMapping[str, CommandDto] = TTLCache( + maxsize=timeout * 60, ttl=timeout * 1.1 + ) self._host = host self._port = port self.client_id = "helperbot@bumper/helperbot" @@ -56,16 +56,20 @@ class MQTTHelperBot: return self._commands @property - def timeout(self)->float: + def timeout(self) -> float: return self._timeout async def start_helper_bot(self): try: if self.Client is None: - self.Client = MQTTClient(client_id=self.client_id, - config={"check_hostname": False, "reconnect_retries": 20}) + self.Client = MQTTClient( + client_id=self.client_id, + config={"check_hostname": False, "reconnect_retries": 20}, + ) - await self.Client.connect(f"mqtts://{self._host}:{self._port}/", cafile=bumper.ca_cert) + await self.Client.connect( + f"mqtts://{self._host}:{self._port}/", cafile=bumper.ca_cert + ) await self.Client.subscribe( [ ("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+", QOS_0), @@ -78,12 +82,10 @@ class MQTTHelperBot: async def _wait_for_resp(self, command_dto: CommandDto, request_id: str): try: - payload = await asyncio.wait_for(command_dto.wait_for_response(), timeout=self.timeout) - return { - "id": request_id, - "ret": "ok", - "resp": payload - } + payload = await asyncio.wait_for( + command_dto.wait_for_response(), timeout=self.timeout + ) + return {"id": request_id, "ret": "ok", "resp": payload} except asyncio.TimeoutError: helperbotlog.debug("wait_for_resp timeout reached") except asyncio.CancelledError as e: @@ -143,9 +145,7 @@ class MQTTServer: self._port = port # Default config opts - passwd_file = os.path.join( - os.path.join(bumper.data_dir, "passwd") - ) + passwd_file = os.path.join(os.path.join(bumper.data_dir, "passwd")) # For file auth, set user:hash in passwd file see # (https://hbmqtt.readthedocs.io/en/latest/references/hbmqtt.html#configuration-example) @@ -156,7 +156,9 @@ class MQTTServer: passwd_file = kwargs["password_file"] elif key == "allow_anonymous": - allow_anon = kwargs["allow_anonymous"] # Set to True to allow anonymous authentication + allow_anon = kwargs[ + "allow_anonymous" + ] # Set to True to allow anonymous authentication # The below adds a plugin to the hbmqtt.broker.plugins without having to futz with setup.py distribution = pkg_resources.Distribution("hbmqtt.broker.plugins") @@ -181,7 +183,9 @@ class MQTTServer: "auth": { "allow-anonymous": allow_anon, "password-file": passwd_file, - "plugins": ["bumper"], # Bumper plugin provides auth and handling of bots/clients connecting + "plugins": [ + "bumper" + ], # Bumper plugin provides auth and handling of bots/clients connecting }, "topic-check": {"enabled": False}, } @@ -235,7 +239,7 @@ class BumperMQTTServer_Plugin: if "@" in client_id: didsplit = str(client_id).split("@") if not ( # if ecouser or bumper aren't in details it is a bot - "ecouser" in didsplit[1] or "bumper" in didsplit[1] + "ecouser" in didsplit[1] or "bumper" in didsplit[1] ): tmpbotdetail = str(didsplit[1]).split("/") bumper.bot_add( @@ -245,8 +249,10 @@ class BumperMQTTServer_Plugin: tmpbotdetail[1], "eco-ng", ) - mqttserverlog.info(f"Bumper Authentication Success - Bot - SN: {username} - DID: {didsplit[0]}" - f" - Class: {tmpbotdetail[0]}") + mqttserverlog.info( + f"Bumper Authentication Success - Bot - SN: {username} - DID: {didsplit[0]}" + f" - Class: {tmpbotdetail[0]}" + ) authenticated = True else: tmpclientdetail = str(didsplit[1]).split("/") @@ -255,27 +261,40 @@ class BumperMQTTServer_Plugin: resource = tmpclientdetail[1] if userid == "helperbot": - mqttserverlog.info(f"Bumper Authentication Success - Helperbot: {client_id}") + mqttserverlog.info( + f"Bumper Authentication Success - Helperbot: {client_id}" + ) authenticated = True - elif bumper.check_authcode(didsplit[0], password) or not bumper.use_auth: + elif ( + bumper.check_authcode(didsplit[0], password) + or not bumper.use_auth + ): bumper.client_add(userid, realm, resource) - mqttserverlog.info(f"Bumper Authentication Success - Client - Username: {username} - " - f"ClientID: {client_id}") + mqttserverlog.info( + f"Bumper Authentication Success - Client - Username: {username} - " + f"ClientID: {client_id}" + ) authenticated = True - # Check for File Auth - if username and not authenticated: # If there is a username and it isn't already authenticated + # Check for File Auth + if ( + username and not authenticated + ): # If there is a username and it isn't already authenticated hash = self._users.get(username, None) if hash: # If there is a matching entry in passwd, check hash authenticated = pwd_context.verify(password, hash) if authenticated: mqttserverlog.info( - f"File Authentication Success - Username: {username} - ClientID: {client_id}") + f"File Authentication Success - Username: {username} - ClientID: {client_id}" + ) else: - mqttserverlog.info(f"File Authentication Failed - Username: {username} - ClientID: {client_id}") + mqttserverlog.info( + f"File Authentication Failed - Username: {username} - ClientID: {client_id}" + ) else: mqttserverlog.info( - f"File Authentication Failed - No Entry for Username: {username} - ClientID: {client_id}") + f"File Authentication Failed - No Entry for Username: {username} - ClientID: {client_id}" + ) except Exception as e: mqttserverlog.exception( @@ -285,28 +304,39 @@ class BumperMQTTServer_Plugin: # Check for allow anonymous allow_anonymous = self.auth_config.get("allow-anonymous", True) - if allow_anonymous and not authenticated: # If anonymous auth is allowed and it isn't already authenticated + if ( + allow_anonymous and not authenticated + ): # If anonymous auth is allowed and it isn't already authenticated authenticated = True self.context.logger.debug( - f"Anonymous Authentication Success: config allows anonymous - Username: {username}") - mqttserverlog.info(f"Anonymous Authentication Success: config allows anonymous - Username: {username}") + f"Anonymous Authentication Success: config allows anonymous - Username: {username}" + ) + mqttserverlog.info( + f"Anonymous Authentication Success: config allows anonymous - Username: {username}" + ) return authenticated def _read_password_file(self): - password_file = self.auth_config.get('password-file', None) + password_file = self.auth_config.get("password-file", None) if password_file: try: with open(password_file) as f: - self.context.logger.debug(f"Reading user database from {password_file}") + self.context.logger.debug( + f"Reading user database from {password_file}" + ) for l in f: line = l.strip() - if not line.startswith('#'): # Allow comments in files + if not line.startswith("#"): # Allow comments in files (username, pwd_hash) = line.split(sep=":", maxsplit=3) if username: self._users[username] = pwd_hash - self.context.logger.debug(f"user: {username} - hash: {pwd_hash}") - self.context.logger.debug(f"{(len(self._users))} user(s) read from file {password_file}") + self.context.logger.debug( + f"user: {username} - hash: {pwd_hash}" + ) + self.context.logger.debug( + f"{(len(self._users))} user(s) read from file {password_file}" + ) except FileNotFoundError: self.context.logger.warning(f"Password file {password_file} not found") @@ -332,20 +362,32 @@ class BumperMQTTServer_Plugin: data_decoded = str(message.data.decode("utf-8")) if topic_split[6] == "helperbot": # Response to command - helperbotlog.debug(f"Received Response - Topic: {topic} - Message: {data_decoded}") + helperbotlog.debug( + f"Received Response - Topic: {topic} - Message: {data_decoded}" + ) if topic_split[10] in bumper.mqtt_helperbot.commands: - bumper.mqtt_helperbot.commands[topic_split[10]].add_response(data_decoded) + bumper.mqtt_helperbot.commands[topic_split[10]].add_response( + data_decoded + ) elif topic_split[3] == "helperbot": # Helperbot sending command - helperbotlog.debug(f"Send Command - Topic: {topic} - Message: {data_decoded}") + helperbotlog.debug( + f"Send Command - Topic: {topic} - Message: {data_decoded}" + ) elif topic_split[1] == "atr": # Broadcast message received on atr if topic_split[2] == "errors": - boterrorlog.error(f"Received Error - Topic: {topic} - Message: {data_decoded}") + boterrorlog.error( + f"Received Error - Topic: {topic} - Message: {data_decoded}" + ) else: - helperbotlog.debug(f"Received Broadcast - Topic: {topic} - Message: {data_decoded}") + helperbotlog.debug( + f"Received Broadcast - Topic: {topic} - Message: {data_decoded}" + ) else: - helperbotlog.debug(f"Received Message - Topic: {topic} - Message: {data_decoded}") + helperbotlog.debug( + f"Received Message - Topic: {topic} - Message: {data_decoded}" + ) async def on_broker_client_disconnected(self, client_id): self._set_client_connected(client_id, False) diff --git a/bumper/plugins.py b/bumper/plugins.py index d1cbc9b..e3934f3 100644 --- a/bumper/plugins.py +++ b/bumper/plugins.py @@ -2,12 +2,11 @@ import asyncio from aiohttp import web -class ConfServerApp(): + +class ConfServerApp: name = None plugin_type = None path_prefix = None app = None sub_api = None routes = None - - diff --git a/bumper/plugins/bumper_confserver_portal_appsvr.py b/bumper/plugins/bumper_confserver_portal_appsvr.py index b2ee984..60dbb85 100644 --- a/bumper/plugins/bumper_confserver_portal_appsvr.py +++ b/bumper/plugins/bumper_confserver_portal_appsvr.py @@ -8,152 +8,167 @@ from bumper.models import * class portal_api_appsvr(plugins.ConfServerApp): - def __init__(self): self.name = "portal_api_appsvr" self.plugin_type = "sub_api" self.sub_api = "portal_api" self.routes = [ - web.route("*", "/appsvr/app.do", self.handle_appsvr_app, name="portal_api_appsvr_app"), - web.route("*", "/appsvr/service/list", self.handle_appsvr_service_list, name="portal_api_appsvr_service_list"), - web.route("*", "/appsvr/oauth_callback", self.handle_appsvr_oauth_callback, name="portal_api_appsvr_oauth_callback"), + web.route( + "*", + "/appsvr/app.do", + self.handle_appsvr_app, + name="portal_api_appsvr_app", + ), + web.route( + "*", + "/appsvr/service/list", + self.handle_appsvr_service_list, + name="portal_api_appsvr_service_list", + ), + web.route( + "*", + "/appsvr/oauth_callback", + self.handle_appsvr_oauth_callback, + name="portal_api_appsvr_oauth_callback", + ), ] - self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + self.get_milli_time = ( + bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + ) async def handle_appsvr_app(self, request): - if not request.method == "GET": # Skip GET for now - try: + if not request.method == "GET": # Skip GET for now + try: - body = {} - postbody = {} - if request.content_type == "application/x-www-form-urlencoded": - postbody = await request.post() + body = {} + postbody = {} + if request.content_type == "application/x-www-form-urlencoded": + postbody = await request.post() - else: - postbody = json.loads(await request.text()) + else: + postbody = json.loads(await request.text()) - todo = postbody["todo"] + todo = postbody["todo"] - if todo == "GetGlobalDeviceList": # EcoVacs Home - bots = bumper.db_get().table("bots").all() - botlist = [] - for bot in bots: - if bot["class"] != "": - b = bumper.bot_toEcoVacsHome_JSON(bot) - if ( - not b is None - ): # Happens if the bot isn't on the EcoVacs Home list - botlist.append(json.loads(b)) + if todo == "GetGlobalDeviceList": # EcoVacs Home + bots = bumper.db_get().table("bots").all() + botlist = [] + for bot in bots: + if bot["class"] != "": + b = bumper.bot_toEcoVacsHome_JSON(bot) + if ( + not b is None + ): # Happens if the bot isn't on the EcoVacs Home list + botlist.append(json.loads(b)) - body = { - "code": 0, - "devices": botlist, - "ret": "ok", - "todo": "result", - } + body = { + "code": 0, + "devices": botlist, + "ret": "ok", + "todo": "result", + } - return web.json_response(body) + return web.json_response(body) - #elif todo == "GetShareDeviceList": - # example response - # { - # "code": 0, - # "devices": [ - # { - # "deviceName": "DEEBOT 900 Series (Cleaner Cleaner)", - # "did": "did", - # "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839", - # "mid": "ls1ok3", - # "ownUsers": { - # "isMe": true, - # "nickname": "user@gmail.com", - # "user": "cg****" - # }, - # "resource": "wC3g", - # "share": true, - # "shareUsers": [] - # } - # ], - # "ret": "ok", - # "todo": "result" - # } + # elif todo == "GetShareDeviceList": + # example response + # { + # "code": 0, + # "devices": [ + # { + # "deviceName": "DEEBOT 900 Series (Cleaner Cleaner)", + # "did": "did", + # "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839", + # "mid": "ls1ok3", + # "ownUsers": { + # "isMe": true, + # "nickname": "user@gmail.com", + # "user": "cg****" + # }, + # "resource": "wC3g", + # "share": true, + # "shareUsers": [] + # } + # ], + # "ret": "ok", + # "todo": "result" + # } - # if shared shareUsers - # "shareUsers": [ - # { - # "isMe": false, - # "nickname": "user@gmail.com", - # "status": "sharing", - # "user": "eafg****" - # } - # ] + # if shared shareUsers + # "shareUsers": [ + # { + # "isMe": false, + # "nickname": "user@gmail.com", + # "status": "sharing", + # "user": "eafg****" + # } + # ] - #elif todo == "ShareDevice": - # example post - # { - # "todo": "ShareDevice", - # "accountType": "EMAIL", - # "auth": { - # "realm": "ecouser.net", - # "resource": "res", - # "token": "token***", - # "userid": "cg***", - # "with": "users" - # }, - # "country": "US", - # "did": "did", - # "resource": "wC3g", - # "username": "email to share to" - # } + # elif todo == "ShareDevice": + # example post + # { + # "todo": "ShareDevice", + # "accountType": "EMAIL", + # "auth": { + # "realm": "ecouser.net", + # "resource": "res", + # "token": "token***", + # "userid": "cg***", + # "with": "users" + # }, + # "country": "US", + # "did": "did", + # "resource": "wC3g", + # "username": "email to share to" + # } - #fail response (no user) - # { - # "todo": "result", - # "code": -3, - # "errno": -3, - # "ret": "fail" - # } + # fail response (no user) + # { + # "todo": "result", + # "code": -3, + # "errno": -3, + # "ret": "fail" + # } - #success response - #{"ret":"ok","code":0,"todo":"result"} + # success response + # {"ret":"ok","code":0,"todo":"result"} - #elif todo == "ShareUnRegisterDevice": - # example post - # { - # "todo": "ShareUnRegisterDevice", - # "account": "email to share to", - # "auth": { - # "realm": "ecouser.net", - # "resource": "res", - # "token": "toke", - # "userid": "userid", - # "with": "users" - # }, - # "country": "US", - # "did": "did", - # "lang": "EN", - # "mid": "ls1ok3" - # } - # example response - # { - # "todo": "result", - # "code": 0, - # "data": { - # "mailContent": "

Hey there,

\n\t\t\tCheck out my new awesome robot vacuum: DEEBOT 900 Series!
\n\t\t\tDownload the ECOVACS HOME App and sign up with your email: brian@bmartin.net, so you and I can control this kick-ass robot together.

\n\t\t\tIOS: https://itunes.apple.com/us/app/ecovacs-home/id1329458504?l=zh&ls=1&mt=8
\n\t\t\tAndroid: https://play.google.com/store/apps/details?id=com.eco.global.app

\n\t\t\tThis invitation is valid within 7 days.
\n\t\t\tIf I'm sending to the wrong person, please ignore this email.

Thank you.

", - # "mailTitle": "I'm sharing my DEEBOT and you're invited!" - # }, - # "ret": "ok" - # } + # elif todo == "ShareUnRegisterDevice": + # example post + # { + # "todo": "ShareUnRegisterDevice", + # "account": "email to share to", + # "auth": { + # "realm": "ecouser.net", + # "resource": "res", + # "token": "toke", + # "userid": "userid", + # "with": "users" + # }, + # "country": "US", + # "did": "did", + # "lang": "EN", + # "mid": "ls1ok3" + # } + # example response + # { + # "todo": "result", + # "code": 0, + # "data": { + # "mailContent": "

Hey there,

\n\t\t\tCheck out my new awesome robot vacuum: DEEBOT 900 Series!
\n\t\t\tDownload the ECOVACS HOME App and sign up with your email: brian@bmartin.net, so you and I can control this kick-ass robot together.

\n\t\t\tIOS: https://itunes.apple.com/us/app/ecovacs-home/id1329458504?l=zh&ls=1&mt=8
\n\t\t\tAndroid: https://play.google.com/store/apps/details?id=com.eco.global.app

\n\t\t\tThis invitation is valid within 7 days.
\n\t\t\tIf I'm sending to the wrong person, please ignore this email.

Thank you.

", + # "mailTitle": "I'm sharing my DEEBOT and you're invited!" + # }, + # "ret": "ok" + # } + except Exception as e: + logging.exception(f"{e}") - except Exception as e: - logging.exception(f"{e}") - - # Return fail for GET - body = {"result": "fail", "todo": "result"} - return web.json_response(body) + # Return fail for GET + body = {"result": "fail", "todo": "result"} + return web.json_response(body) async def handle_appsvr_service_list(self, request): try: @@ -176,15 +191,10 @@ class portal_api_appsvr(plugins.ConfServerApp): "magw": "api-app.ecouser.net", "msgcloud": "msg-eu.ecouser.net:5223", "ngiotLb": "jmq-ngiot-eu.ecouser.net", - "rop": "api-rop.ecouser.net" + "rop": "api-rop.ecouser.net", } - body = { - "code": 0, - "data": data, - "ret": "ok", - "todo": "result" - } + body = {"code": 0, "data": data, "ret": "ok", "todo": "result"} return web.json_response(body) @@ -199,7 +209,7 @@ class portal_api_appsvr(plugins.ConfServerApp): "code": 0, "data": oauth.toResponse(), "ret": "ok", - "todo": "result" + "todo": "result", } return web.json_response(body) diff --git a/bumper/plugins/bumper_confserver_portal_dim.py b/bumper/plugins/bumper_confserver_portal_dim.py index fd71d74..9f4aa29 100644 --- a/bumper/plugins/bumper_confserver_portal_dim.py +++ b/bumper/plugins/bumper_confserver_portal_dim.py @@ -12,19 +12,23 @@ import random class portal_api_dim(plugins.ConfServerApp): - def __init__(self): self.name = "portal_api_dimr" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "portal_api" - + self.routes = [ - - web.route("*", "/dim/devmanager.do", self.handle_dim_devmanager, name="portal_api_dim_devmanager"), - + web.route( + "*", + "/dim/devmanager.do", + self.handle_dim_devmanager, + name="portal_api_dim_devmanager", + ), ] - 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_dim_devmanager(self, request): # Used in EcoVacs Home App try: @@ -66,11 +70,11 @@ class portal_api_dim(plugins.ConfServerApp): return web.json_response(body) if json_body["td"] == "ReceiveShareDevice": # EcoVacs Home - body = {"ret":"ok"} - return web.json_response(body) + body = {"ret": "ok"} + return web.json_response(body) except Exception as e: - logging.exception(f"{e}") + logging.exception(f"{e}") + plugin = portal_api_dim() - diff --git a/bumper/plugins/bumper_confserver_portal_ecms.py b/bumper/plugins/bumper_confserver_portal_ecms.py index 152456d..ead6976 100644 --- a/bumper/plugins/bumper_confserver_portal_ecms.py +++ b/bumper/plugins/bumper_confserver_portal_ecms.py @@ -8,26 +8,27 @@ 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"), + 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 + 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 - } + body = {"code": 0, "data": [], "message": "success", "success": True} return web.json_response(body) diff --git a/bumper/plugins/bumper_confserver_portal_iot.py b/bumper/plugins/bumper_confserver_portal_iot.py index f9721df..85c4e61 100644 --- a/bumper/plugins/bumper_confserver_portal_iot.py +++ b/bumper/plugins/bumper_confserver_portal_iot.py @@ -10,21 +10,26 @@ from datetime import datetime, timedelta import string import random -class portal_api_iot(plugins.ConfServerApp): +class portal_api_iot(plugins.ConfServerApp): def __init__(self): self.name = "portal_api_iot" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "portal_api" - - self.routes = [ - - web.route("*", "/iot/devmanager.do", self.handle_devmanager_botcommand, name="portal_api_iot_devmanager"), + self.routes = [ + web.route( + "*", + "/iot/devmanager.do", + self.handle_devmanager_botcommand, + name="portal_api_iot_devmanager", + ), ] - 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_devmanager_botcommand(self, request): try: json_body = json.loads(await request.text()) @@ -70,12 +75,11 @@ class portal_api_iot(plugins.ConfServerApp): return web.json_response(body) if json_body["td"] == "PreWifiConfig": # EcoVacs Home - body = {"ret":"ok"} + body = {"ret": "ok"} return web.json_response(body) - except Exception as e: logging.exception(f"{e}") -plugin = portal_api_iot() +plugin = portal_api_iot() diff --git a/bumper/plugins/bumper_confserver_portal_lg.py b/bumper/plugins/bumper_confserver_portal_lg.py index 015e7a0..85afac5 100644 --- a/bumper/plugins/bumper_confserver_portal_lg.py +++ b/bumper/plugins/bumper_confserver_portal_lg.py @@ -11,19 +11,18 @@ from bumper.models import * class portal_api_lg(plugins.ConfServerApp): - def __init__(self): self.name = "portal_api_lg" self.plugin_type = "sub_api" self.sub_api = "portal_api" self.routes = [ - web.route("*", "/lg/log.do", self.handle_lg_log, name="portal_api_lg_log"), - ] - 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 randomid = "".join(random.sample(string.ascii_letters, 6)) @@ -72,10 +71,10 @@ class portal_api_lg(plugins.ConfServerApp): cleanlogs = logsroot.getchildren() for l in cleanlogs: cleanlog = { - "ts": l.attrib['s'], - "area": l.attrib['a'], - "last": l.attrib['l'], - "cleanType": l.attrib['t'], + "ts": l.attrib["s"], + "area": l.attrib["a"], + "last": l.attrib["l"], + "cleanType": l.attrib["t"], # imageUrl allows for providing images of cleanings, something to look into later # "imageUrl": "https://localhost:8007", } diff --git a/bumper/plugins/bumper_confserver_portal_neng.py b/bumper/plugins/bumper_confserver_portal_neng.py index d4938de..a591006 100644 --- a/bumper/plugins/bumper_confserver_portal_neng.py +++ b/bumper/plugins/bumper_confserver_portal_neng.py @@ -10,21 +10,35 @@ from datetime import datetime, timedelta class portal_api_neng(plugins.ConfServerApp): - def __init__(self): self.name = "portal_api_neng" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "portal_api" - - self.routes = [ - - web.route("*", "/neng/message/hasUnreadMsg", self.handle_neng_hasUnreadMessage, name="portal_api_neng_hasUnreadMessage"), - web.route("*", "/neng/message/getShareMsgs", self.handle_neng_getShareMsgs, name="portal_api_neng_getShareMsgs"), - web.route("*", "/neng/message/getlist", self.handle_neng_getlist, name="portal_api_neng_getlist"), + self.routes = [ + web.route( + "*", + "/neng/message/hasUnreadMsg", + self.handle_neng_hasUnreadMessage, + name="portal_api_neng_hasUnreadMessage", + ), + web.route( + "*", + "/neng/message/getShareMsgs", + self.handle_neng_getShareMsgs, + name="portal_api_neng_getShareMsgs", + ), + web.route( + "*", + "/neng/message/getlist", + self.handle_neng_getlist, + name="portal_api_neng_getlist", + ), ] - 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_neng_hasUnreadMessage(self, request): # EcoVacs Home try: @@ -37,7 +51,7 @@ class portal_api_neng(plugins.ConfServerApp): async def handle_neng_getShareMsgs(self, request): # EcoVacs Home try: - body = {"code":0,"data":{"hasNext":False,"msgs":[]}} + body = {"code": 0, "data": {"hasNext": False, "msgs": []}} # share msg response # { @@ -65,33 +79,33 @@ class portal_api_neng(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception(f"{e}") + logging.exception(f"{e}") async def handle_neng_getlist(self, request): # EcoVacs Home try: - body = {"code":0,"data":{"hasNext":False,"msgs":[]}} + body = {"code": 0, "data": {"hasNext": False, "msgs": []}} - #Sample Message - # { - # "id": "5da0ac9d636aec5107627ac4", - # "ts": 1570811036877, - # "did": "bot did", - # "cid": "ls1ok3", - # "name": "DEEBOT 900 Series", - # "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839", - # "eventTypeId": "5aab824bb62ce30001f9a702", - # "title": "DEEBOT is off the floor.", - # "body": "DEEBOT is off the floor. Please put it back.", - # "read": false, - # "UILogicId": "D_900", - # "type": "web", - # "url": "https://portal-ww.ecouser.net/api/pim/eventdetail.html?id=5ba21e44aed83800015b9ca8" # Off the floor instructions - # } + # Sample Message + # { + # "id": "5da0ac9d636aec5107627ac4", + # "ts": 1570811036877, + # "did": "bot did", + # "cid": "ls1ok3", + # "name": "DEEBOT 900 Series", + # "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839", + # "eventTypeId": "5aab824bb62ce30001f9a702", + # "title": "DEEBOT is off the floor.", + # "body": "DEEBOT is off the floor. Please put it back.", + # "read": false, + # "UILogicId": "D_900", + # "type": "web", + # "url": "https://portal-ww.ecouser.net/api/pim/eventdetail.html?id=5ba21e44aed83800015b9ca8" # Off the floor instructions + # } return web.json_response(body) except Exception as e: - logging.exception(f"{e}") - -plugin = portal_api_neng() + logging.exception(f"{e}") + +plugin = portal_api_neng() diff --git a/bumper/plugins/bumper_confserver_portal_pim.py b/bumper/plugins/bumper_confserver_portal_pim.py index 701a3d3..287d727 100644 --- a/bumper/plugins/bumper_confserver_portal_pim.py +++ b/bumper/plugins/bumper_confserver_portal_pim.py @@ -9,22 +9,53 @@ from bumper.models import * class portal_api_pim(plugins.ConfServerApp): - def __init__(self): self.name = "portal_api_pim" self.plugin_type = "sub_api" self.sub_api = "portal_api" self.routes = [ - web.route("*", "/pim/product/getProductIotMap", self.handle_getProductIotMap, name="portal_api_pim_getProductIotMap"), - web.route("*", "/pim/file/get/{id}", self.handle_pimFile, name="portal_api_pim_file"), - web.route("*", "/pim/product/getConfignetAll", self.handle_getConfignetAll, name="portal_api_pim_getConfignetAll"), - web.route("*", "/pim/product/getConfigGroups", self.handle_getConfigGroups, name="portal_api_pim_getConfigGroups"), - web.route("*", "/pim/dictionary/getErrDetail", self.handle_getErrDetail, name="portal_api_pim_getErrDetail"), - web.route("*", "/pim/product/software/config/batch", self.handle_product_config_batch, name="portal_api_pim_product_config_batch"), + web.route( + "*", + "/pim/product/getProductIotMap", + self.handle_getProductIotMap, + name="portal_api_pim_getProductIotMap", + ), + web.route( + "*", + "/pim/file/get/{id}", + self.handle_pimFile, + name="portal_api_pim_file", + ), + web.route( + "*", + "/pim/product/getConfignetAll", + self.handle_getConfignetAll, + name="portal_api_pim_getConfignetAll", + ), + web.route( + "*", + "/pim/product/getConfigGroups", + self.handle_getConfigGroups, + name="portal_api_pim_getConfigGroups", + ), + web.route( + "*", + "/pim/dictionary/getErrDetail", + self.handle_getErrDetail, + name="portal_api_pim_getErrDetail", + ), + web.route( + "*", + "/pim/product/software/config/batch", + self.handle_product_config_batch, + name="portal_api_pim_product_config_batch", + ), ] - self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + self.get_milli_time = ( + bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + ) async def handle_getProductIotMap(self, request): try: @@ -41,7 +72,11 @@ class portal_api_pim(plugins.ConfServerApp): try: fileID = request.match_info.get("id", "") - return web.FileResponse(os.path.join(bumper.bumper_dir, "bumper", "web", "images", "robotvac_image.jpg")) + return web.FileResponse( + os.path.join( + bumper.bumper_dir, "bumper", "web", "images", "robotvac_image.jpg" + ) + ) except Exception as e: logging.exception(f"{e}") @@ -67,7 +102,7 @@ class portal_api_pim(plugins.ConfServerApp): body = { "code": -1, "data": [], - "msg": "This errcode's detail is not exists" + "msg": "This errcode's detail is not exists", } return web.json_response(body) @@ -86,16 +121,9 @@ class portal_api_pim(plugins.ConfServerApp): # not found in productConfigBatch # some devices don't have any product configuration - data.append({ - "cfg": {}, - "pid": pid - }) + data.append({"cfg": {}, "pid": pid}) - body = { - "code": 200, - "data": data, - "message": "success" - } + body = {"code": 200, "data": data, "message": "success"} return web.json_response(body) except Exception as e: @@ -105,2607 +133,2278 @@ class portal_api_pim(plugins.ConfServerApp): plugin = portal_api_pim() confignetAllResponse = { - "code": 0, - "data": [ - { - "groupId": "5ae147f27ccd1a0001e1f69c", - "sort": 60, - "groupName": "DEEBOT OZMO Slim10 Series", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1715-0201", - "mid": "02uwxm", - "ota": False, - "supportVer": { - "Android": "1.0.0", - "IOS": "1.0.0" - }, - "smartTypes": [], - "steps": [ + "code": 0, + "data": [ { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" + "groupId": "5ae147f27ccd1a0001e1f69c", + "sort": 60, + "groupName": "DEEBOT OZMO Slim10 Series", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1715-0201", + "mid": "02uwxm", + "ota": False, + "supportVer": {"Android": "1.0.0", "IOS": "1.0.0"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1", }, { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1" + "groupId": "5ca32b5de9e9270001354b3f", + "sort": 80, + "groupName": "DEEBOT OZMO 601", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1629-0203", + "mid": "159", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7606de51dd0001fee12d", + }, + { + "groupId": "5ca32b5de9e9270001354b3f", + "sort": 80, + "groupName": "DEEBOT", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1629-0203", + "mid": "159", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7628de51dd0001fee12f", + }, + { + "groupId": "5ca32b5de9e9270001354b3f", + "sort": 80, + "groupName": "DEEBOT OZMO 610 Series", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1629-0203", + "mid": "159", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7640de51dd0001fee131", + }, + { + "groupId": "5cae9662e9e9270001354b55", + "sort": 150, + "groupName": "DEEBOT M80 Pro", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1638-0102", + "mid": "125", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c14414d60de0001eaf1f2", + }, + { + "groupId": "5bbedcd922d57f00018c13b6", + "sort": 30, + "groupName": "DEEBOT OZMO/PRO 930 Series", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "HK_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1803-0101", + "mid": "115", + "ota": False, + "supportVer": {"Android": "1.1.8", "IOS": "1.1.8"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf711aeb0acfc000179ff8a", + }, + { + "groupId": "5b6560760506b100015c8867", + "sort": 130, + "groupName": "DEEBOT 900 Series", + "isPopular": False, + "belongApp": ["ecoglobal", "ecodeebot"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1711-0201", + "mid": "ls1ok3", + "ota": False, + "supportVer": {"Android": "1.0.7", "IOS": "1.0.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839", + }, + { + "groupId": "5b6560760506b100015c8867", + "sort": 130, + "groupName": "DEEBOT 910", + "isPopular": False, + "belongApp": ["ecoglobal", "ecodeebot"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1711-0201", + "mid": "ls1ok3", + "ota": False, + "supportVer": {"Android": "1.0.7", "IOS": "1.0.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c778731280fda0001770ba0", + }, + { + "groupId": "5cae9793e9e9270001354b57", + "sort": 160, + "groupName": "DEEBOT M81 Pro", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1638-0101", + "mid": "141", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c2aa64d60de0001eaf1f6", + }, + { + "groupId": "5c19a835a1e6ee0001782245", + "sort": 20, + "groupName": "DEEBOT OZMO 920 Series", + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 1, + "materialNo": "110-1819-0101", + "mid": "vi829v", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": ["MQ_AP"], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c9c7995e9e9270001354ab4", + }, + { + "groupId": "5bc8187422d57f00018c13ba", + "sort": 50, + "groupName": "DEEBOT OZMO 960", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1803-0101", + "mid": "gd4uut", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c7384767b93c700013f12e7", + }, + { + "groupId": "5b04bf1d7ccd1a0001e1f6a6", + "sort": 10, + "groupName": "DEEBOT OZMO 900 Series", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1810-0101", + "mid": "y79a7u", + "ota": False, + "supportVer": {"Android": "1.0.0", "IOS": "1.0.0"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7", + }, + { + "groupId": "5b04bf1d7ccd1a0001e1f6a6", + "sort": 10, + "groupName": "DEEBOT OZMO 905", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1810-0101", + "mid": "y79a7u", + "ota": False, + "supportVer": {"Android": "1.0.0", "IOS": "1.0.0"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d1474632a6bd50001b5b6f3", + }, + { + "groupId": "5c763de8280fda0001770b7f", + "sort": 100, + "groupName": "DEEBOT 500", + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0163", + "mid": "vsc5ia", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c874326280fda0001770d2a", + }, + { + "groupId": "5c763de8280fda0001770b7f", + "sort": 100, + "groupName": "DEEBOT 501", + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0163", + "mid": "vsc5ia", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c931fef280fda0001770d7e", + }, + { + "groupId": "5c763de8280fda0001770b7f", + "sort": 100, + "groupName": "DEEBOT 502", + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0163", + "mid": "vsc5ia", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c93204b63023c0001e7faa7", + }, + { + "groupId": "5c763de8280fda0001770b7f", + "sort": 100, + "groupName": "DEEBOT 505", + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0163", + "mid": "vsc5ia", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c932067280fda0001770d7f", + }, + { + "groupId": "5cae9aa5e9e9270001354b5d", + "sort": 230, + "groupName": "DEEBOT Slim2 Series", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1639-0102", + "mid": "123", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c150dba13eb00013feaae", + }, + { + "groupId": "5cae9aa5e9e9270001354b5d", + "sort": 230, + "groupName": "DEEBOT Slim Neo", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1639-0102", + "mid": "123", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c152f4d60de0001eaf1f4", + }, + { + "groupId": "5c19a8a0ddfc1f0001ede8e0", + "sort": 40, + "groupName": "DEEBOT OZMO 950 Series", + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 1, + "materialNo": "110-1820-0101", + "mid": "yna5xi", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": ["MQ_AP"], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5caafd7e1285190001685965", + }, + { + "groupId": "5ca4711412851900016858cb", + "sort": 70, + "groupName": "DEEBOT OZMO 700", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 1, + "materialNo": "110-1825-0201", + "mid": "0xyhhr", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d117d4f0ac6ad00012b792d", + }, + { + "groupId": "5ca4711412851900016858cb", + "sort": 70, + "groupName": "DEEBOT OZMO 750", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 1, + "materialNo": "110-1825-0201", + "mid": "0xyhhr", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d3aa309ba13eb00013feb69", + }, + { + "groupId": "5acb0f2e7c295c0001876eb4", + "sort": 110, + "groupName": "DEEBOT 600 Series", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0170", + "mid": "dl8fht", + "ota": False, + "supportVer": {"Android": "1.0.0", "IOS": "1.0.0"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e36591fd5d0001892541", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b8cd75f76f7f10001e9a0de", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea", + }, + { + "groupId": "5acb0f2e7c295c0001876eb4", + "sort": 110, + "groupName": "DEEBOT 661", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0170", + "mid": "dl8fht", + "ota": False, + "supportVer": {"Android": "1.0.0", "IOS": "1.0.0"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e36591fd5d0001892541", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b8cd75f76f7f10001e9a0de", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d280ce3350e7a0001e84c95", + }, + { + "groupId": "5ca31ce8e9e9270001354b33", + "sort": 170, + "groupName": "DEEBOT M86", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1628-0101", + "mid": "129", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca31df112851900016858c0", + }, + { + "groupId": "5d2460f244af360001383992", + "sort": 9, + "groupName": "DEEBOT U3", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "939393939393", + "mid": "xb83mv", + "ota": False, + "supportVer": {"Android": "1.1.8", "IOS": "1.1.8"}, + "smartTypes": ["MQ_AP"], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d3fe649de51dd0001fee0de", + }, + { + "groupId": "5d2460f244af360001383992", + "sort": 9, + "groupName": "DEEBOT U3 LINE FRIENDS", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "939393939393", + "mid": "xb83mv", + "ota": False, + "supportVer": {"Android": "1.1.8", "IOS": "1.1.8"}, + "smartTypes": ["MQ_AP"], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5da834a8d66cd10001f58265", + }, + { + "groupId": "5ca1c9a3e9e9270001354b2b", + "sort": 240, + "groupName": "DEEBOT Mini2", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1640-0101", + "mid": "142", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca1ca79e9e9270001354b2d", + }, + { + "groupId": "5ca31e8212851900016858c2", + "sort": 140, + "groupName": "DEEBOT N79", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0136", + "mid": "126", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32ab2e9e9270001354b3d", + }, + { + "groupId": "5ca31e8212851900016858c2", + "sort": 140, + "groupName": "DEEBOT N79S/SE", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0136", + "mid": "126", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cd4ca505b032200015a455d", + }, + { + "groupId": "5ca31e8212851900016858c2", + "sort": 140, + "groupName": "DEEBOT N79T/W", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0136", + "mid": "126", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32a1012851900016858c6", + }, + { + "groupId": "5ae197be7ccd1a0001e1f6a1", + "sort": 120, + "groupName": "DEEBOT 711", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0205", + "mid": "uv242z", + "ota": False, + "supportVer": {"Android": "1.0.5", "IOS": "1.0.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769", + }, + { + "groupId": "5ae197be7ccd1a0001e1f6a1", + "sort": 120, + "groupName": "DEEBOT 710", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0205", + "mid": "uv242z", + "ota": False, + "supportVer": {"Android": "1.0.5", "IOS": "1.0.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9", + }, + { + "groupId": "5ae197be7ccd1a0001e1f6a1", + "sort": 120, + "groupName": "DEEBOT 715", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0205", + "mid": "uv242z", + "ota": False, + "supportVer": {"Android": "1.0.5", "IOS": "1.0.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b7b65f176f7f10001e9a0c2", + }, + { + "groupId": "5ae197be7ccd1a0001e1f6a1", + "sort": 120, + "groupName": "DEEBOT 711s", + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0205", + "mid": "uv242z", + "ota": False, + "supportVer": {"Android": "1.0.5", "IOS": "1.0.5"}, + "smartTypes": [], + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d157f9f77a3a60001051f69", + }, + ], + "msg": "success", + "configFAQ": { + "wifiFAQUrl": "https://portal-ww.ecouser.net/api/pim/wififaq.html?lang=en&defaultLang=en", + "notFoundAPUrl": "https://portal-ww.ecouser.net/api/pim/findWifi.html?lang=en&defaultLang=en", + "configFailedUrl": "https://portal-ww.ecouser.net/api/pim/configfail.html?lang=en&defaultLang=en", + "contactUS": "helper", }, - { - "groupId": "5ca32b5de9e9270001354b3f", - "sort": 80, - "groupName": "DEEBOT OZMO 601", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1629-0203", - "mid": "159", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7606de51dd0001fee12d" - }, - { - "groupId": "5ca32b5de9e9270001354b3f", - "sort": 80, - "groupName": "DEEBOT", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1629-0203", - "mid": "159", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7628de51dd0001fee12f" - }, - { - "groupId": "5ca32b5de9e9270001354b3f", - "sort": 80, - "groupName": "DEEBOT OZMO 610 Series", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1629-0203", - "mid": "159", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b7640de51dd0001fee131" - }, - { - "groupId": "5cae9662e9e9270001354b55", - "sort": 150, - "groupName": "DEEBOT M80 Pro", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1638-0102", - "mid": "125", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c14414d60de0001eaf1f2" - }, - { - "groupId": "5bbedcd922d57f00018c13b6", - "sort": 30, - "groupName": "DEEBOT OZMO/PRO 930 Series", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "HK_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1803-0101", - "mid": "115", - "ota": False, - "supportVer": { - "Android": "1.1.8", - "IOS": "1.1.8" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf711aeb0acfc000179ff8a" - }, - { - "groupId": "5b6560760506b100015c8867", - "sort": 130, - "groupName": "DEEBOT 900 Series", - "isPopular": False, - "belongApp": [ - "ecoglobal", - "ecodeebot" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1711-0201", - "mid": "ls1ok3", - "ota": False, - "supportVer": { - "Android": "1.0.7", - "IOS": "1.0.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839" - }, - { - "groupId": "5b6560760506b100015c8867", - "sort": 130, - "groupName": "DEEBOT 910", - "isPopular": False, - "belongApp": [ - "ecoglobal", - "ecodeebot" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1711-0201", - "mid": "ls1ok3", - "ota": False, - "supportVer": { - "Android": "1.0.7", - "IOS": "1.0.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c778731280fda0001770ba0" - }, - { - "groupId": "5cae9793e9e9270001354b57", - "sort": 160, - "groupName": "DEEBOT M81 Pro", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1638-0101", - "mid": "141", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c2aa64d60de0001eaf1f6" - }, - { - "groupId": "5c19a835a1e6ee0001782245", - "sort": 20, - "groupName": "DEEBOT OZMO 920 Series", - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 1, - "materialNo": "110-1819-0101", - "mid": "vi829v", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [ - "MQ_AP" - ], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c9c7995e9e9270001354ab4" - }, - { - "groupId": "5bc8187422d57f00018c13ba", - "sort": 50, - "groupName": "DEEBOT OZMO 960", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1803-0101", - "mid": "gd4uut", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c7384767b93c700013f12e7" - }, - { - "groupId": "5b04bf1d7ccd1a0001e1f6a6", - "sort": 10, - "groupName": "DEEBOT OZMO 900 Series", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1810-0101", - "mid": "y79a7u", - "ota": False, - "supportVer": { - "Android": "1.0.0", - "IOS": "1.0.0" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7" - }, - { - "groupId": "5b04bf1d7ccd1a0001e1f6a6", - "sort": 10, - "groupName": "DEEBOT OZMO 905", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1810-0101", - "mid": "y79a7u", - "ota": False, - "supportVer": { - "Android": "1.0.0", - "IOS": "1.0.0" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d1474632a6bd50001b5b6f3" - }, - { - "groupId": "5c763de8280fda0001770b7f", - "sort": 100, - "groupName": "DEEBOT 500", - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0163", - "mid": "vsc5ia", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c874326280fda0001770d2a" - }, - { - "groupId": "5c763de8280fda0001770b7f", - "sort": 100, - "groupName": "DEEBOT 501", - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0163", - "mid": "vsc5ia", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c931fef280fda0001770d7e" - }, - { - "groupId": "5c763de8280fda0001770b7f", - "sort": 100, - "groupName": "DEEBOT 502", - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0163", - "mid": "vsc5ia", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c93204b63023c0001e7faa7" - }, - { - "groupId": "5c763de8280fda0001770b7f", - "sort": 100, - "groupName": "DEEBOT 505", - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0163", - "mid": "vsc5ia", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c932067280fda0001770d7f" - }, - { - "groupId": "5cae9aa5e9e9270001354b5d", - "sort": 230, - "groupName": "DEEBOT Slim2 Series", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1639-0102", - "mid": "123", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c150dba13eb00013feaae" - }, - { - "groupId": "5cae9aa5e9e9270001354b5d", - "sort": 230, - "groupName": "DEEBOT Slim Neo", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1639-0102", - "mid": "123", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c152f4d60de0001eaf1f4" - }, - { - "groupId": "5c19a8a0ddfc1f0001ede8e0", - "sort": 40, - "groupName": "DEEBOT OZMO 950 Series", - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 1, - "materialNo": "110-1820-0101", - "mid": "yna5xi", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [ - "MQ_AP" - ], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5caafd7e1285190001685965" - }, - { - "groupId": "5ca4711412851900016858cb", - "sort": 70, - "groupName": "DEEBOT OZMO 700", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 1, - "materialNo": "110-1825-0201", - "mid": "0xyhhr", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d117d4f0ac6ad00012b792d" - }, - { - "groupId": "5ca4711412851900016858cb", - "sort": 70, - "groupName": "DEEBOT OZMO 750", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 1, - "materialNo": "110-1825-0201", - "mid": "0xyhhr", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d3aa309ba13eb00013feb69" - }, - { - "groupId": "5acb0f2e7c295c0001876eb4", - "sort": 110, - "groupName": "DEEBOT 600 Series", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0170", - "mid": "dl8fht", - "ota": False, - "supportVer": { - "Android": "1.0.0", - "IOS": "1.0.0" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e36591fd5d0001892541", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b8cd75f76f7f10001e9a0de", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea" - }, - { - "groupId": "5acb0f2e7c295c0001876eb4", - "sort": 110, - "groupName": "DEEBOT 661", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0170", - "mid": "dl8fht", - "ota": False, - "supportVer": { - "Android": "1.0.0", - "IOS": "1.0.0" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e36591fd5d0001892541", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b8cd75f76f7f10001e9a0de", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d280ce3350e7a0001e84c95" - }, - { - "groupId": "5ca31ce8e9e9270001354b33", - "sort": 170, - "groupName": "DEEBOT M86", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1628-0101", - "mid": "129", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca31df112851900016858c0" - }, - { - "groupId": "5d2460f244af360001383992", - "sort": 9, - "groupName": "DEEBOT U3", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "939393939393", - "mid": "xb83mv", - "ota": False, - "supportVer": { - "Android": "1.1.8", - "IOS": "1.1.8" - }, - "smartTypes": [ - "MQ_AP" - ], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d3fe649de51dd0001fee0de" - }, - { - "groupId": "5d2460f244af360001383992", - "sort": 9, - "groupName": "DEEBOT U3 LINE FRIENDS", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "939393939393", - "mid": "xb83mv", - "ota": False, - "supportVer": { - "Android": "1.1.8", - "IOS": "1.1.8" - }, - "smartTypes": [ - "MQ_AP" - ], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5da834a8d66cd10001f58265" - }, - { - "groupId": "5ca1c9a3e9e9270001354b2b", - "sort": 240, - "groupName": "DEEBOT Mini2", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1640-0101", - "mid": "142", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca1ca79e9e9270001354b2d" - }, - { - "groupId": "5ca31e8212851900016858c2", - "sort": 140, - "groupName": "DEEBOT N79", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0136", - "mid": "126", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32ab2e9e9270001354b3d" - }, - { - "groupId": "5ca31e8212851900016858c2", - "sort": 140, - "groupName": "DEEBOT N79S/SE", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0136", - "mid": "126", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cd4ca505b032200015a455d" - }, - { - "groupId": "5ca31e8212851900016858c2", - "sort": 140, - "groupName": "DEEBOT N79T/W", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0136", - "mid": "126", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca32a1012851900016858c6" - }, - { - "groupId": "5ae197be7ccd1a0001e1f6a1", - "sort": 120, - "groupName": "DEEBOT 711", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0205", - "mid": "uv242z", - "ota": False, - "supportVer": { - "Android": "1.0.5", - "IOS": "1.0.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769" - }, - { - "groupId": "5ae197be7ccd1a0001e1f6a1", - "sort": 120, - "groupName": "DEEBOT 710", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0205", - "mid": "uv242z", - "ota": False, - "supportVer": { - "Android": "1.0.5", - "IOS": "1.0.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9" - }, - { - "groupId": "5ae197be7ccd1a0001e1f6a1", - "sort": 120, - "groupName": "DEEBOT 715", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0205", - "mid": "uv242z", - "ota": False, - "supportVer": { - "Android": "1.0.5", - "IOS": "1.0.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b7b65f176f7f10001e9a0c2" - }, - { - "groupId": "5ae197be7ccd1a0001e1f6a1", - "sort": 120, - "groupName": "DEEBOT 711s", - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0205", - "mid": "uv242z", - "ota": False, - "supportVer": { - "Android": "1.0.5", - "IOS": "1.0.5" - }, - "smartTypes": [], - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d157f9f77a3a60001051f69" - } - ], - "msg": "success", - "configFAQ": { - "wifiFAQUrl": "https://portal-ww.ecouser.net/api/pim/wififaq.html?lang=en&defaultLang=en", - "notFoundAPUrl": "https://portal-ww.ecouser.net/api/pim/findWifi.html?lang=en&defaultLang=en", - "configFailedUrl": "https://portal-ww.ecouser.net/api/pim/configfail.html?lang=en&defaultLang=en", - "contactUS": "helper" - } } configGroupsResponse = { - "code": 0, - "data": [ - { - "sort": 1, - "id": "5c19a743e916ba00019a4e32", - "name": "DEEBOT OZMO", - "robots": [ + "code": 0, + "data": [ { - "groupId": "5d2460f244af360001383992", - "category": "", - "groupName": "DEEBOT U3", - "sort": 9, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "939393939393", - "mid": "xb83mv", - "ota": False, - "supportVer": { - "Android": "1.1.8", - "IOS": "1.1.8" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d3fe66cea1a2e0001d2f243", - "products": [ - "5d246180350e7a0001e84bea", - "5d78f4e878d8b60001e23edc" - ], - "smartTypes": [ - "MQ_AP", - "MQ_AP" - ], - "seriesId": "5c19a743e916ba00019a4e32" + "sort": 1, + "id": "5c19a743e916ba00019a4e32", + "name": "DEEBOT OZMO", + "robots": [ + { + "groupId": "5d2460f244af360001383992", + "category": "", + "groupName": "DEEBOT U3", + "sort": 9, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "939393939393", + "mid": "xb83mv", + "ota": False, + "supportVer": {"Android": "1.1.8", "IOS": "1.1.8"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d3fe66cea1a2e0001d2f243", + "products": [ + "5d246180350e7a0001e84bea", + "5d78f4e878d8b60001e23edc", + ], + "smartTypes": ["MQ_AP", "MQ_AP"], + "seriesId": "5c19a743e916ba00019a4e32", + }, + { + "groupId": "5b04bf1d7ccd1a0001e1f6a6", + "category": "", + "groupName": "DEEBOT OZMO 900 Series", + "sort": 10, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1810-0101", + "mid": "y79a7u", + "ota": False, + "supportVer": {"Android": "1.0.0", "IOS": "1.0.0"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d1472a62a6bd50001b5b6f2", + "products": [ + "5b04c0227ccd1a0001e1f6a8", + "5d1474630ac6ad00012b7940", + ], + "smartTypes": [], + "seriesId": "5c19a743e916ba00019a4e32", + }, + { + "groupId": "5c19a835a1e6ee0001782245", + "category": "", + "groupName": "DEEBOT OZMO 920 Series", + "sort": 20, + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 1, + "materialNo": "110-1819-0101", + "mid": "vi829v", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c9c79c1e9e9270001354ab5", + "products": ["5c19a8f3a1e6ee0001782247"], + "smartTypes": ["MQ_AP"], + "seriesId": "5c19a743e916ba00019a4e32", + }, + { + "groupId": "5bbedcd922d57f00018c13b6", + "category": "", + "groupName": "DEEBOT OZMO/PRO 930 Series", + "sort": 30, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "HK_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1803-0101", + "mid": "115", + "ota": False, + "supportVer": {"Android": "1.1.8", "IOS": "1.1.8"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf66652b0acfc000179ff83", + "products": [ + "5bbedd2822d57f00018c13b7", + "5cd4dd385b032200015a4561", + ], + "smartTypes": [], + "seriesId": "5c19a743e916ba00019a4e32", + }, + { + "groupId": "5c19a8a0ddfc1f0001ede8e0", + "category": "", + "groupName": "DEEBOT OZMO 950 Series", + "sort": 40, + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 1, + "materialNo": "110-1820-0101", + "mid": "yna5xi", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5caafd981285190001685966", + "products": ["5c19a91ca1e6ee000178224a"], + "smartTypes": ["MQ_AP"], + "seriesId": "5c19a743e916ba00019a4e32", + }, + { + "groupId": "5bc8187422d57f00018c13ba", + "category": "", + "groupName": "DEEBOT OZMO 960 Series", + "sort": 50, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1803-0101", + "mid": "gd4uut", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d0899b00ac6ad00012b78f7", + "products": ["5bc8189d68142800016a6937"], + "smartTypes": [], + "seriesId": "5c19a743e916ba00019a4e32", + }, + { + "groupId": "5ae147f27ccd1a0001e1f69c", + "category": "", + "groupName": "DEEBOT OZMO Slim10 Series", + "sort": 60, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1715-0201", + "mid": "02uwxm", + "ota": False, + "supportVer": {"Android": "1.0.0", "IOS": "1.0.0"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ebc34822f0b00013a2e1a", + "products": ["5ae1481e7ccd1a0001e1f69e"], + "smartTypes": [], + "seriesId": "5c19a743e916ba00019a4e32", + }, + { + "groupId": "5ca4711412851900016858cb", + "category": "", + "groupName": "DEEBOT OZMO 700 Series", + "sort": 70, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 1, + "materialNo": "110-1825-0201", + "mid": "0xyhhr", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", + "retryText": "", + "confirmText": "I've heard the sound", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d117d680ac6ad00012b792e", + "products": [ + "5ca4716312851900016858cd", + "5ce7870cd85b4d0001775db9", + ], + "smartTypes": [], + "seriesId": "5c19a743e916ba00019a4e32", + }, + { + "groupId": "5ca32b5de9e9270001354b3f", + "category": "", + "groupName": "DEEBOT OZMO 600 Series", + "sort": 80, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1629-0203", + "mid": "159", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b75a8ea1a2e0001d2f28e", + "products": [ + "5ca32bc2e9e9270001354b41", + "5cbd97b961526a00019799bd", + "5cae98d01285190001685974", + ], + "smartTypes": [], + "seriesId": "5c19a743e916ba00019a4e32", + }, + ], }, { - "groupId": "5b04bf1d7ccd1a0001e1f6a6", - "category": "", - "groupName": "DEEBOT OZMO 900 Series", - "sort": 10, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1810-0101", - "mid": "y79a7u", - "ota": False, - "supportVer": { - "Android": "1.0.0", - "IOS": "1.0.0" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d1472a62a6bd50001b5b6f2", - "products": [ - "5b04c0227ccd1a0001e1f6a8", - "5d1474630ac6ad00012b7940" - ], - "smartTypes": [], - "seriesId": "5c19a743e916ba00019a4e32" + "sort": 2, + "id": "5c2599b6a1e6ee0001782328", + "name": "DEEBOT", + "robots": [ + { + "groupId": "5c763de8280fda0001770b7f", + "category": "", + "groupName": "DEEBOT 500 Series", + "sort": 100, + "isPopular": True, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0163", + "mid": "vsc5ia", + "ota": False, + "supportVer": {"Android": "1.1.5", "IOS": "1.1.5"}, + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf6668bda73e90001dc3b98", + "products": [ + "5c763eba280fda0001770b81", + "5c763f35280fda0001770b84", + "5c763f63280fda0001770b88", + "5c763f8263023c0001e7f855", + ], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5acb0f2e7c295c0001876eb4", + "category": "", + "groupName": "DEEBOT 600 Series", + "sort": 110, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0170", + "mid": "dl8fht", + "ota": False, + "supportVer": {"Android": "1.0.0", "IOS": "1.0.0"}, + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e36591fd5d0001892541", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b8cd75f76f7f10001e9a0de", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf6669ab0acfc000179ff85", + "products": [ + "5acb0fa87c295c0001876ecf", + "5d280ce344af3600013839ab", + ], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5ae197be7ccd1a0001e1f6a1", + "category": "", + "groupName": "DEEBOT 700 Series", + "sort": 120, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0205", + "mid": "uv242z", + "ota": False, + "supportVer": {"Android": "1.0.5", "IOS": "1.0.5"}, + "steps": [ + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "image", + "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ec2b1822f0b00013a2e1c", + "products": [ + "5b43077b8bc457000140363e", + "5b5149b4ac0b87000148c128", + "5b7b65f364e1680001a08b54", + "5ceba1c6d85b4d0001776986", + ], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5b6560760506b100015c8867", + "category": "", + "groupName": "DEEBOT 900 Series", + "sort": 130, + "isPopular": False, + "belongApp": ["ecoglobal", "ecodeebot"], + "smartType": "MQ_AP", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1711-0201", + "mid": "ls1ok3", + "ota": False, + "supportVer": {"Android": "1.0.7", "IOS": "1.0.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d1472b70ac6ad00012b793f", + "products": [ + "5b6561060506b100015c8868", + "5bf2596f23244a00013f2f13", + ], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5ca31e8212851900016858c2", + "category": "", + "groupName": "DEEBOT N79 Series", + "sort": 140, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "702-0000-0136", + "mid": "126", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf666bdda73e90001dc3b9a", + "products": [ + "5ca32ab212851900016858c7", + "5cce893813afb7000195d6af", + "5ca32a11e9e9270001354b39", + ], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5cae9662e9e9270001354b55", + "category": "", + "groupName": "DEEBOT M80 Pro", + "sort": 150, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1638-0102", + "mid": "125", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c1000ba13eb00013feaa4", + "products": ["5cae9703128519000168596a"], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5cae9793e9e9270001354b57", + "category": "", + "groupName": "DEEBOT M81 Pro", + "sort": 160, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1638-0101", + "mid": "141", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c10224d60de0001eaf1ea", + "products": ["5cae97c9128519000168596f"], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5ca31ce8e9e9270001354b33", + "category": "", + "groupName": "DEEBOT M86", + "sort": 170, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1628-0101", + "mid": "129", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf66704da73e90001dc3b9b", + "products": ["5ca31df1e9e9270001354b35"], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5cd4df825b032200015a4567", + "category": "", + "groupName": "DEEBOT M87", + "sort": 180, + "isPopular": False, + "belongApp": ["ecodeebot"], + "smartType": "SCM0", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1517-1001", + "mid": "121", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8ea21d85b4d0001776489", + "products": [], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5cd4dfc9f542e00001dc2df8", + "category": "", + "groupName": "DEEBOT M88", + "sort": 190, + "isPopular": False, + "belongApp": ["ecodeebot"], + "smartType": "SCM0", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1517-0701", + "mid": "107", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8eaa5d85b4d000177648a", + "products": [], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5cd4e077f542e00001dc2dfb", + "category": "", + "groupName": "DEEBOT R95", + "sort": 200, + "isPopular": False, + "belongApp": ["ecodeebot"], + "smartType": "SCM0", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1412-0001", + "mid": "113", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8eb7091fd5d000189254a", + "products": [], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5cd4e0b25b032200015a4569", + "category": "", + "groupName": "DEEBOT R96", + "sort": 210, + "isPopular": False, + "belongApp": ["ecodeebot"], + "smartType": "SCM0", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1510-0501", + "mid": "118", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8ebd791fd5d000189254b", + "products": [], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5cd4e0e15b032200015a456c", + "category": "", + "groupName": "DEEBOT R98", + "sort": 220, + "isPopular": False, + "belongApp": ["ecodeebot"], + "smartType": "SCM0", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1510-0601", + "mid": "117", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8ec13d85b4d000177648b", + "products": [], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5cae9aa5e9e9270001354b5d", + "category": "", + "groupName": "DEEBOT Slim Series", + "sort": 230, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1639-0102", + "mid": "123", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c11fa4d60de0001eaf1ef", + "products": [ + "5cae9b201285190001685977", + "5cd43b4cf542e00001dc2dec", + ], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + { + "groupId": "5ca1c9a3e9e9270001354b2b", + "category": "", + "groupName": "DEEBOT Mini2", + "sort": 240, + "isPopular": False, + "belongApp": ["ecoglobal"], + "smartType": "SPA", + "failCount": 0, + "checkTips": 0, + "materialNo": "110-1640-0101", + "mid": "142", + "ota": False, + "supportVer": {"Android": "1.1.7", "IOS": "1.1.7"}, + "steps": [ + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + { + "guideImageType": "", + "guideImageUrl": "", + "title": "", + "guideText": "", + "retryText": "", + "confirmText": "", + "btnText": "", + }, + ], + "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca1c901e9e9270001354b2a", + "products": ["5ca1ca7a12851900016858bd"], + "smartTypes": [], + "seriesId": "5c2599b6a1e6ee0001782328", + }, + ], }, - { - "groupId": "5c19a835a1e6ee0001782245", - "category": "", - "groupName": "DEEBOT OZMO 920 Series", - "sort": 20, - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 1, - "materialNo": "110-1819-0101", - "mid": "vi829v", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5c9c79c1e9e9270001354ab5", - "products": [ - "5c19a8f3a1e6ee0001782247" - ], - "smartTypes": [ - "MQ_AP" - ], - "seriesId": "5c19a743e916ba00019a4e32" - }, - { - "groupId": "5bbedcd922d57f00018c13b6", - "category": "", - "groupName": "DEEBOT OZMO/PRO 930 Series", - "sort": 30, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "HK_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1803-0101", - "mid": "115", - "ota": False, - "supportVer": { - "Android": "1.1.8", - "IOS": "1.1.8" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf66652b0acfc000179ff83", - "products": [ - "5bbedd2822d57f00018c13b7", - "5cd4dd385b032200015a4561" - ], - "smartTypes": [], - "seriesId": "5c19a743e916ba00019a4e32" - }, - { - "groupId": "5c19a8a0ddfc1f0001ede8e0", - "category": "", - "groupName": "DEEBOT OZMO 950 Series", - "sort": 40, - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 1, - "materialNo": "110-1820-0101", - "mid": "yna5xi", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5caafd981285190001685966", - "products": [ - "5c19a91ca1e6ee000178224a" - ], - "smartTypes": [ - "MQ_AP" - ], - "seriesId": "5c19a743e916ba00019a4e32" - }, - { - "groupId": "5bc8187422d57f00018c13ba", - "category": "", - "groupName": "DEEBOT OZMO 960 Series", - "sort": 50, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1803-0101", - "mid": "gd4uut", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d0899b00ac6ad00012b78f7", - "products": [ - "5bc8189d68142800016a6937" - ], - "smartTypes": [], - "seriesId": "5c19a743e916ba00019a4e32" - }, - { - "groupId": "5ae147f27ccd1a0001e1f69c", - "category": "", - "groupName": "DEEBOT OZMO Slim10 Series", - "sort": 60, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1715-0201", - "mid": "02uwxm", - "ota": False, - "supportVer": { - "Android": "1.0.0", - "IOS": "1.0.0" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ebc34822f0b00013a2e1a", - "products": [ - "5ae1481e7ccd1a0001e1f69e" - ], - "smartTypes": [], - "seriesId": "5c19a743e916ba00019a4e32" - }, - { - "groupId": "5ca4711412851900016858cb", - "category": "", - "groupName": "DEEBOT OZMO 700 Series", - "sort": 70, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 1, - "materialNo": "110-1825-0201", - "mid": "0xyhhr", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "Briefly press the RESET button for 1 second and then release. You will hear that DEEBOT is ready for network setup.", - "retryText": "", - "confirmText": "I've heard the sound", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d117d680ac6ad00012b792e", - "products": [ - "5ca4716312851900016858cd", - "5ce7870cd85b4d0001775db9" - ], - "smartTypes": [], - "seriesId": "5c19a743e916ba00019a4e32" - }, - { - "groupId": "5ca32b5de9e9270001354b3f", - "category": "", - "groupName": "DEEBOT OZMO 600 Series", - "sort": 80, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1629-0203", - "mid": "159", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d4b75a8ea1a2e0001d2f28e", - "products": [ - "5ca32bc2e9e9270001354b41", - "5cbd97b961526a00019799bd", - "5cae98d01285190001685974" - ], - "smartTypes": [], - "seriesId": "5c19a743e916ba00019a4e32" - } - ] + ], + "msg": "success", + "configFAQ": { + "wifiFAQUrl": "https://portal-ww.ecouser.net/api/pim/wififaq.html?lang=en&defaultLang=en", + "notFoundAPUrl": "https://portal-ww.ecouser.net/api/pim/findWifi.html?lang=en&defaultLang=en", + "configFailedUrl": "https://portal-ww.ecouser.net/api/pim/configfail.html?lang=en&defaultLang=en", + "contactUS": "helper", }, - { - "sort": 2, - "id": "5c2599b6a1e6ee0001782328", - "name": "DEEBOT", - "robots": [ - { - "groupId": "5c763de8280fda0001770b7f", - "category": "", - "groupName": "DEEBOT 500 Series", - "sort": 100, - "isPopular": True, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0163", - "mid": "vsc5ia", - "ota": False, - "supportVer": { - "Android": "1.1.5", - "IOS": "1.1.5" - }, - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5df9d6b7c783810001d3d06b", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5dfc34d15d21490001700e14", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf6668bda73e90001dc3b98", - "products": [ - "5c763eba280fda0001770b81", - "5c763f35280fda0001770b84", - "5c763f63280fda0001770b88", - "5c763f8263023c0001e7f855" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5acb0f2e7c295c0001876eb4", - "category": "", - "groupName": "DEEBOT 600 Series", - "sort": 110, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0170", - "mid": "dl8fht", - "ota": False, - "supportVer": { - "Android": "1.0.0", - "IOS": "1.0.0" - }, - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e36591fd5d0001892541", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b8cd75f76f7f10001e9a0de", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf6669ab0acfc000179ff85", - "products": [ - "5acb0fa87c295c0001876ecf", - "5d280ce344af3600013839ab" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5ae197be7ccd1a0001e1f6a1", - "category": "", - "groupName": "DEEBOT 700 Series", - "sort": 120, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0205", - "mid": "uv242z", - "ota": False, - "supportVer": { - "Android": "1.0.5", - "IOS": "1.0.5" - }, - "steps": [ - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5b7fc85976f7f10001e9a0d0", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "image", - "guideImageUrl": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8e5d5d85b4d0001776484", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5b5ec2b1822f0b00013a2e1c", - "products": [ - "5b43077b8bc457000140363e", - "5b5149b4ac0b87000148c128", - "5b7b65f364e1680001a08b54", - "5ceba1c6d85b4d0001776986" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5b6560760506b100015c8867", - "category": "", - "groupName": "DEEBOT 900 Series", - "sort": 130, - "isPopular": False, - "belongApp": [ - "ecoglobal", - "ecodeebot" - ], - "smartType": "MQ_AP", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1711-0201", - "mid": "ls1ok3", - "ota": False, - "supportVer": { - "Android": "1.0.7", - "IOS": "1.0.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d1472b70ac6ad00012b793f", - "products": [ - "5b6561060506b100015c8868", - "5bf2596f23244a00013f2f13" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5ca31e8212851900016858c2", - "category": "", - "groupName": "DEEBOT N79 Series", - "sort": 140, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "702-0000-0136", - "mid": "126", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf666bdda73e90001dc3b9a", - "products": [ - "5ca32ab212851900016858c7", - "5cce893813afb7000195d6af", - "5ca32a11e9e9270001354b39" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5cae9662e9e9270001354b55", - "category": "", - "groupName": "DEEBOT M80 Pro", - "sort": 150, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1638-0102", - "mid": "125", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c1000ba13eb00013feaa4", - "products": [ - "5cae9703128519000168596a" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5cae9793e9e9270001354b57", - "category": "", - "groupName": "DEEBOT M81 Pro", - "sort": 160, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1638-0101", - "mid": "141", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c10224d60de0001eaf1ea", - "products": [ - "5cae97c9128519000168596f" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5ca31ce8e9e9270001354b33", - "category": "", - "groupName": "DEEBOT M86", - "sort": 170, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1628-0101", - "mid": "129", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5cf66704da73e90001dc3b9b", - "products": [ - "5ca31df1e9e9270001354b35" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5cd4df825b032200015a4567", - "category": "", - "groupName": "DEEBOT M87", - "sort": 180, - "isPopular": False, - "belongApp": [ - "ecodeebot" - ], - "smartType": "SCM0", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1517-1001", - "mid": "121", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8ea21d85b4d0001776489", - "products": [], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5cd4dfc9f542e00001dc2df8", - "category": "", - "groupName": "DEEBOT M88", - "sort": 190, - "isPopular": False, - "belongApp": [ - "ecodeebot" - ], - "smartType": "SCM0", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1517-0701", - "mid": "107", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8eaa5d85b4d000177648a", - "products": [], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5cd4e077f542e00001dc2dfb", - "category": "", - "groupName": "DEEBOT R95", - "sort": 200, - "isPopular": False, - "belongApp": [ - "ecodeebot" - ], - "smartType": "SCM0", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1412-0001", - "mid": "113", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8eb7091fd5d000189254a", - "products": [], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5cd4e0b25b032200015a4569", - "category": "", - "groupName": "DEEBOT R96", - "sort": 210, - "isPopular": False, - "belongApp": [ - "ecodeebot" - ], - "smartType": "SCM0", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1510-0501", - "mid": "118", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8ebd791fd5d000189254b", - "products": [], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5cd4e0e15b032200015a456c", - "category": "", - "groupName": "DEEBOT R98", - "sort": 220, - "isPopular": False, - "belongApp": [ - "ecodeebot" - ], - "smartType": "SCM0", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1510-0601", - "mid": "117", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ce8ec13d85b4d000177648b", - "products": [], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5cae9aa5e9e9270001354b5d", - "category": "", - "groupName": "DEEBOT Slim Series", - "sort": 230, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1639-0102", - "mid": "123", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5d2c11fa4d60de0001eaf1ef", - "products": [ - "5cae9b201285190001685977", - "5cd43b4cf542e00001dc2dec" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - }, - { - "groupId": "5ca1c9a3e9e9270001354b2b", - "category": "", - "groupName": "DEEBOT Mini2", - "sort": 240, - "isPopular": False, - "belongApp": [ - "ecoglobal" - ], - "smartType": "SPA", - "failCount": 0, - "checkTips": 0, - "materialNo": "110-1640-0101", - "mid": "142", - "ota": False, - "supportVer": { - "Android": "1.1.7", - "IOS": "1.1.7" - }, - "steps": [ - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - }, - { - "guideImageType": "", - "guideImageUrl": "", - "title": "", - "guideText": "", - "retryText": "", - "confirmText": "", - "btnText": "" - } - ], - "icon": "https://portal-ww.ecouser.net/api/pim/file/get/5ca1c901e9e9270001354b2a", - "products": [ - "5ca1ca7a12851900016858bd" - ], - "smartTypes": [], - "seriesId": "5c2599b6a1e6ee0001782328" - } - ] - } - ], - "msg": "success", - "configFAQ": { - "wifiFAQUrl": "https://portal-ww.ecouser.net/api/pim/wififaq.html?lang=en&defaultLang=en", - "notFoundAPUrl": "https://portal-ww.ecouser.net/api/pim/findWifi.html?lang=en&defaultLang=en", - "configFailedUrl": "https://portal-ww.ecouser.net/api/pim/configfail.html?lang=en&defaultLang=en", - "contactUS": "helper" - } } productConfigBatch = [ @@ -2717,9 +2416,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5e8e8d8a032edd8457c66bfb", @@ -2729,9 +2428,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5c19a91ca1e6ee000178224a", @@ -2741,9 +2440,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5e8e8d2a032edd3c03c66bf7", @@ -2753,9 +2452,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5de0d86ed88546000195239a", @@ -2765,9 +2464,9 @@ productConfigBatch = [ "video": True, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5c19a8f3a1e6ee0001782247", @@ -2777,9 +2476,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5e698a6306f6de52c264c61b", @@ -2789,9 +2488,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5e699a4106f6de83ea64c620", @@ -2801,9 +2500,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5edd998afdd6a30008da039b", @@ -2813,9 +2512,9 @@ productConfigBatch = [ "video": True, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5edd9a4075f2fc000636086c", @@ -2825,9 +2524,9 @@ productConfigBatch = [ "video": True, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5ed5e4d3a719ea460ec3216c", @@ -2837,9 +2536,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5f88195e6cf8de0008ed7c11", @@ -2849,9 +2548,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5f8819156cf8de0008ed7c0d", @@ -2861,9 +2560,9 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } + }, }, { "pid": "5fa105c6d16a99000667eb54", @@ -2873,8 +2572,8 @@ productConfigBatch = [ "video": False, "battery": True, "clean": True, - "charge": True + "charge": True, } - } - } + }, + }, ] diff --git a/bumper/plugins/bumper_confserver_portal_rapp.py b/bumper/plugins/bumper_confserver_portal_rapp.py index 1cd8627..44ca250 100644 --- a/bumper/plugins/bumper_confserver_portal_rapp.py +++ b/bumper/plugins/bumper_confserver_portal_rapp.py @@ -6,29 +6,27 @@ 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"), + 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 + 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" + "data": {"data": {"name": "My Home"}, "tag": None}, + "message": "success", } return web.json_response(body) @@ -36,4 +34,5 @@ class api_rapp(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") + plugin = api_rapp() diff --git a/bumper/plugins/bumper_confserver_portal_users.py b/bumper/plugins/bumper_confserver_portal_users.py index 254fdbf..3c20182 100644 --- a/bumper/plugins/bumper_confserver_portal_users.py +++ b/bumper/plugins/bumper_confserver_portal_users.py @@ -10,20 +10,24 @@ from datetime import datetime, timedelta class portal_api_users(plugins.ConfServerApp): - def __init__(self): self.name = "portal_api_users" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "portal_api" - + self.routes = [ - - web.route("*", "/users/user.do", self.handle_usersapi, name="portal_api_users_user"), - + web.route( + "*", + "/users/user.do", + self.handle_usersapi, + name="portal_api_users_user", + ), ] - 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_usersapi(self, request): if not request.method == "GET": # Skip GET for now try: @@ -116,5 +120,5 @@ class portal_api_users(plugins.ConfServerApp): body = {"result": "fail", "todo": "result"} return web.json_response(body) -plugin = portal_api_users() +plugin = portal_api_users() diff --git a/bumper/plugins/bumper_confserver_upload_global.py b/bumper/plugins/bumper_confserver_upload_global.py index 8f6fa11..ed7447e 100644 --- a/bumper/plugins/bumper_confserver_upload_global.py +++ b/bumper/plugins/bumper_confserver_upload_global.py @@ -9,32 +9,38 @@ from bumper import plugins from datetime import datetime, timedelta import os -class upload_global(plugins.ConfServerApp): +class upload_global(plugins.ConfServerApp): def __init__(self): self.name = "upload_global" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "upload_api" - - self.routes = [ - - web.route("*", "/global/{year}/{month}/{day}/{fileid}", self.handle_upload_global_file, name="upload_global_getFile"), + self.routes = [ + web.route( + "*", + "/global/{year}/{month}/{day}/{fileid}", + self.handle_upload_global_file, + name="upload_global_getFile", + ), ] - 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_upload_global_file(self, request): try: fileID = request.match_info.get("id", "") - return web.FileResponse(os.path.join(bumper.bumper_dir,"bumper","web","images","robotvac_image.jpg")) - - except Exception as e: - logging.exception(f"{e}") + return web.FileResponse( + os.path.join( + bumper.bumper_dir, "bumper", "web", "images", "robotvac_image.jpg" + ) + ) + + except Exception as e: + logging.exception(f"{e}") + - - plugin = upload_global() diff --git a/bumper/plugins/bumper_confserver_v1_global_auth.py b/bumper/plugins/bumper_confserver_v1_global_auth.py index bbceb70..ffb94b5 100644 --- a/bumper/plugins/bumper_confserver_v1_global_auth.py +++ b/bumper/plugins/bumper_confserver_v1_global_auth.py @@ -10,19 +10,24 @@ from datetime import datetime, timedelta class v1_global_auth(plugins.ConfServerApp): - def __init__(self): self.name = "v1_global_auth" self.plugin_type = "sub_api" self.sub_api = "api_v1" - + authhandler = bumper.ConfServer.ConfServer_AuthHandler() self.routes = [ - web.route("*", "/global/auth/getAuthCode", authhandler.get_AuthCode, name="v1_global_auth_getAuthCode"), + web.route( + "*", + "/global/auth/getAuthCode", + authhandler.get_AuthCode, + name="v1_global_auth_getAuthCode", + ), ] - self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + self.get_milli_time = ( + bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + ) plugin = v1_global_auth() - diff --git a/bumper/plugins/bumper_confserver_v1_private_ad.py b/bumper/plugins/bumper_confserver_v1_private_ad.py index 530057c..000d85b 100644 --- a/bumper/plugins/bumper_confserver_v1_private_ad.py +++ b/bumper/plugins/bumper_confserver_v1_private_ad.py @@ -10,20 +10,30 @@ from datetime import datetime, timedelta class v1_private_ad(plugins.ConfServerApp): - def __init__(self): self.name = "v1_private_ad" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "api_v1" - - self.routes = [ - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getAdByPositionType", self.handle_getAdByPositionType, name="v1_ad_getAdByPositionType"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getBootScreen", self.handle_getBootScreen, name="v1_ad_getBootScreen"), + self.routes = [ + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getAdByPositionType", + self.handle_getAdByPositionType, + name="v1_ad_getAdByPositionType", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getBootScreen", + self.handle_getBootScreen, + name="v1_ad_getBootScreen", + ), ] - 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_getAdByPositionType(self, request): # EcoVacs Home try: body = { @@ -54,5 +64,5 @@ class v1_private_ad(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") -plugin = v1_private_ad() +plugin = v1_private_ad() diff --git a/bumper/plugins/bumper_confserver_v1_private_campaign.py b/bumper/plugins/bumper_confserver_v1_private_campaign.py index c388e25..da097a3 100644 --- a/bumper/plugins/bumper_confserver_v1_private_campaign.py +++ b/bumper/plugins/bumper_confserver_v1_private_campaign.py @@ -10,20 +10,24 @@ from datetime import datetime, timedelta class v1_private_campaign(plugins.ConfServerApp): - def __init__(self): self.name = "v1_private_campaign" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "api_v1" - + self.routes = [ - - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/campaign/homePageAlert", self.handle_homePageAlert, name="v1_campaign_homePageAlert"), - + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/campaign/homePageAlert", + self.handle_homePageAlert, + name="v1_campaign_homePageAlert", + ), ] - 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_homePageAlert(self, request): try: nextAlert = self.get_milli_time( @@ -47,8 +51,7 @@ class v1_private_campaign(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception(f"{e}") - + logging.exception(f"{e}") + plugin = v1_private_campaign() - diff --git a/bumper/plugins/bumper_confserver_v1_private_common.py b/bumper/plugins/bumper_confserver_v1_private_common.py index ec2010d..9727828 100644 --- a/bumper/plugins/bumper_confserver_v1_private_common.py +++ b/bumper/plugins/bumper_confserver_v1_private_common.py @@ -8,25 +8,65 @@ from bumper.models import * class v1_private_common(plugins.ConfServerApp): - def __init__(self): self.name = "v1_private_common" self.plugin_type = "sub_api" self.sub_api = "api_v1" self.routes = [ - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkAPPVersion", self.handle_checkAPPVersion, name="v1_common_checkAppVersion"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkVersion", self.handle_checkVersion, name="v1_common_checkVersion"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/uploadDeviceInfo", self.handle_uploadDeviceInfo, name="v1_common_uploadDeviceInfo"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getSystemReminder", self.handle_getSystemReminder, name="v1_common_getSystemReminder"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/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"), - + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkAPPVersion", + self.handle_checkAPPVersion, + name="v1_common_checkAppVersion", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkVersion", + self.handle_checkVersion, + name="v1_common_checkVersion", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/uploadDeviceInfo", + self.handle_uploadDeviceInfo, + name="v1_common_uploadDeviceInfo", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getSystemReminder", + self.handle_getSystemReminder, + name="v1_common_getSystemReminder", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/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", + ), ] - 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_checkVersion(self, request): try: @@ -115,11 +155,8 @@ class v1_private_common(plugins.ConfServerApp): async def handle_getConfig(self, request): try: data = [] - for key in request.query["keys"].split(','): - data.append({ - "key": key, - "value": "Y" - }) + for key in request.query["keys"].split(","): + data.append({"key": key, "value": "Y"}) body = { "code": bumper.RETURN_API_SUCCESS, @@ -160,7 +197,7 @@ class v1_private_common(plugins.ConfServerApp): "id": "20180804040641_7d746faf18b8cb22a50d145598fe4c90", "type": "USER", "url": "https://gl-eu-wap.ecovacs.com/content/agreement?id=20180804040641_7d746faf18b8cb22a50d145598fe4c90&language=EN", - "version": "1.03" + "version": "1.03", }, { "acceptTime": None, @@ -168,8 +205,8 @@ class v1_private_common(plugins.ConfServerApp): "id": "20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac", "type": "PRIVACY", "url": "https://gl-eu-wap.ecovacs.com/content/agreement?id=20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac&language=EN", - "version": "1.03" - } + "version": "1.03", + }, ], "msg": "操作成功", "success": True, @@ -186,9 +223,7 @@ class v1_private_common(plugins.ConfServerApp): time = self.get_milli_time(datetime.utcnow().timestamp()) body = { "code": bumper.RETURN_API_SUCCESS, - "data": { - "timestamp": time - }, + "data": {"timestamp": time}, "msg": "操作成功", "success": True, "time": time, @@ -202,261 +237,442 @@ class v1_private_common(plugins.ConfServerApp): 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"}]} +AREA_LIST = { + "currentVersion": 231, + "areaList": [ + {"areaKey": "JP", "chsName": "日本", "enName": "Japan", "pyFirst": "R"}, + {"areaKey": "MY", "chsName": "马来西亚", "enName": "Malaysia", "pyFirst": "M"}, + {"areaKey": "DE", "chsName": "德国", "enName": "Germany", "pyFirst": "D"}, + { + "areaKey": "LI", + "chsName": "列支敦斯登", + "enName": "Liechtenstein", + "pyFirst": "L", + }, + {"areaKey": "AT", "chsName": "奥地利", "enName": "Austria", "pyFirst": "A"}, + {"areaKey": "TW", "chsName": "台湾", "enName": "Taiwan", "pyFirst": "T"}, + {"areaKey": "FR", "chsName": "法国", "enName": "France", "pyFirst": "F"}, + { + "areaKey": "CN", + "chsName": "中国大陆", + "enName": "China Mainland", + "pyFirst": "Z", + }, + {"areaKey": "SG", "chsName": "新加坡", "enName": "Singapore", "pyFirst": "X"}, + { + "areaKey": "RE", + "chsName": "留尼汪岛", + "enName": "Reunion Island", + "pyFirst": "L", + }, + { + "areaKey": "EH", + "chsName": "西撒哈拉", + "enName": "Western Sahara", + "pyFirst": "X", + }, + { + "areaKey": "WF", + "chsName": "瓦利斯群岛和富图纳群岛", + "enName": "Wallis and Futuna Islands", + "pyFirst": "W", + }, + {"areaKey": "KP", "chsName": "朝鲜", "enName": "North Korea", "pyFirst": "C"}, + {"areaKey": "ZW", "chsName": "津巴布韦", "enName": "Zimbabwe", "pyFirst": "J"}, + { + "areaKey": "VI", + "chsName": "美属维尔京群岛", + "enName": "United States Virgin Islands", + "pyFirst": "M", + }, + { + "areaKey": "PF", + "chsName": "法属玻里尼西亚", + "enName": "French Polynesia", + "pyFirst": "F", + }, + {"areaKey": "DJ", "chsName": "吉布提", "enName": "Djibouti", "pyFirst": "J"}, + {"areaKey": "KZ", "chsName": "哈萨克斯坦", "enName": "Kazakhstan", "pyFirst": "H"}, + {"areaKey": "TV", "chsName": "图瓦卢", "enName": "Tuvalu", "pyFirst": "T"}, + {"areaKey": "VU", "chsName": "瓦努阿图", "enName": "Vanuatu", "pyFirst": "W"}, + {"areaKey": "IN", "chsName": "印度", "enName": "India", "pyFirst": "Y"}, + {"areaKey": "CM", "chsName": "喀麦隆", "enName": "Cameroon", "pyFirst": "K"}, + {"areaKey": "LK", "chsName": "斯里兰卡", "enName": "Sri Lanka", "pyFirst": "S"}, + { + "areaKey": "CC", + "chsName": "科科斯群岛", + "enName": "Cocos Islands", + "pyFirst": "K", + }, + { + "areaKey": "KY", + "chsName": "开曼群岛", + "enName": "Cayman Islands", + "pyFirst": "K", + }, + {"areaKey": "QA", "chsName": "卡塔尔", "enName": "Qatar", "pyFirst": "K"}, + {"areaKey": "AZ", "chsName": "阿塞拜疆", "enName": "Azerbaijan", "pyFirst": "A"}, + {"areaKey": "HN", "chsName": "洪都拉斯", "enName": "Honduras", "pyFirst": "H"}, + {"areaKey": "AW", "chsName": "阿鲁巴岛", "enName": "Aruba", "pyFirst": "A"}, + {"areaKey": "KH", "chsName": "柬埔寨", "enName": "Cambodia", "pyFirst": "J"}, + {"areaKey": "CO", "chsName": "哥伦比亚", "enName": "Colombia", "pyFirst": "G"}, + {"areaKey": "IR", "chsName": "伊朗", "enName": "Iran", "pyFirst": "Y"}, + {"areaKey": "ZA", "chsName": "南非", "enName": "South Africa", "pyFirst": "N"}, + {"areaKey": "UY", "chsName": "乌拉圭", "enName": "Uruguay", "pyFirst": "W"}, + {"areaKey": "GU", "chsName": "关岛", "enName": "Guam", "pyFirst": "G"}, + {"areaKey": "GH", "chsName": "加纳", "enName": "Ghana", "pyFirst": "J"}, + {"areaKey": "GN", "chsName": "几内亚", "enName": "Guynea", "pyFirst": "J"}, + { + "areaKey": "MH", + "chsName": "马绍尔群岛", + "enName": "Marshall Islands", + "pyFirst": "M", + }, + {"areaKey": "SE", "chsName": "瑞典", "enName": "Sweden", "pyFirst": "R"}, + { + "areaKey": "SB", + "chsName": "所罗门群岛", + "enName": "Solomon Islands", + "pyFirst": "S", + }, + {"areaKey": "NE", "chsName": "尼日尔", "enName": "Niger", "pyFirst": "N"}, + {"areaKey": "HT", "chsName": "海地", "enName": "Haiti", "pyFirst": "H"}, + {"areaKey": "PL", "chsName": "波兰", "enName": "Poland", "pyFirst": "B"}, + { + "areaKey": "DO", + "chsName": "多米尼加共和国", + "enName": "Dominican Republic", + "pyFirst": "D", + }, + {"areaKey": "PS", "chsName": "巴勒斯坦", "enName": "Palestine", "pyFirst": "B"}, + {"areaKey": "KW", "chsName": "科威特", "enName": "Kuwait", "pyFirst": "K"}, + { + "areaKey": "UZ", + "chsName": "乌兹别克斯坦", + "enName": "Republic of Uzbekistan", + "pyFirst": "W", + }, + {"areaKey": "GD", "chsName": "格林纳达", "enName": "Grenada", "pyFirst": "G"}, + {"areaKey": "KG", "chsName": "吉尔吉斯斯坦", "enName": "Kyrgyzstan", "pyFirst": "J"}, + {"areaKey": "JO", "chsName": "约旦", "enName": "Jordan", "pyFirst": "Y"}, + {"areaKey": "IL", "chsName": "以色列", "enName": "Israel", "pyFirst": "Y"}, + {"areaKey": "UK", "chsName": "英国", "enName": "United Kingdom", "pyFirst": "Y"}, + {"areaKey": "MW", "chsName": "马拉维", "enName": "Malawi", "pyFirst": "M"}, + {"areaKey": "MC", "chsName": "摩纳哥", "enName": "Monaco", "pyFirst": "M"}, + { + "areaKey": "IC", + "chsName": "加那利群岛", + "enName": "Canary Islands", + "pyFirst": "J", + }, + {"areaKey": "JM", "chsName": "牙买加", "enName": "Jamaica", "pyFirst": "Y"}, + { + "areaKey": "MP", + "chsName": "北马里亚纳群岛", + "enName": "The Northern Mariana Islands", + "pyFirst": "B", + }, + {"areaKey": "BH", "chsName": "巴林岛", "enName": "Bahrain", "pyFirst": "B"}, + {"areaKey": "MK", "chsName": "马其顿", "enName": "Macedonia", "pyFirst": "M"}, + {"areaKey": "ET", "chsName": "埃塞俄比亚", "enName": "Ethiopia", "pyFirst": "A"}, + {"areaKey": "CL", "chsName": "智利", "enName": "Chile", "pyFirst": "Z"}, + {"areaKey": "GP", "chsName": "瓜德罗普岛", "enName": "Guadeloupe", "pyFirst": "G"}, + { + "areaKey": "FK", + "chsName": "福克兰群岛", + "enName": "Falkland Islands", + "pyFirst": "F", + }, + {"areaKey": "GL", "chsName": "格陵兰", "enName": "Greenland", "pyFirst": "G"}, + {"areaKey": "BF", "chsName": "布基纳法索", "enName": "Burkina Faso", "pyFirst": "B"}, + {"areaKey": "GI", "chsName": "直布罗陀", "enName": "Gibraltar", "pyFirst": "Z"}, + {"areaKey": "MV", "chsName": "马尔代夫", "enName": "Maldives", "pyFirst": "M"}, + {"areaKey": "CU", "chsName": "古巴", "enName": "Cuba", "pyFirst": "G"}, + {"areaKey": "LS", "chsName": "莱索托", "enName": "Lesotho", "pyFirst": "L"}, + {"areaKey": "MA", "chsName": "摩洛哥", "enName": "Morocco", "pyFirst": "M"}, + {"areaKey": "AL", "chsName": "阿尔巴尼亚", "enName": "Albania", "pyFirst": "A"}, + {"areaKey": "AF", "chsName": "阿富汗", "enName": "Afghanistan", "pyFirst": "A"}, + {"areaKey": "CA", "chsName": "加拿大", "enName": "Canada", "pyFirst": "J"}, + {"areaKey": "BB", "chsName": "巴巴多斯", "enName": "Barbados", "pyFirst": "B"}, + {"areaKey": "LC", "chsName": "圣卢西亚岛", "enName": "Saint Lucia", "pyFirst": "S"}, + { + "areaKey": "PN", + "chsName": "皮特克恩岛", + "enName": "Pitcairn Island", + "pyFirst": "P", + }, + {"areaKey": "LV", "chsName": "拉脱维亚", "enName": "Latvia", "pyFirst": "L"}, + {"areaKey": "NO", "chsName": "挪威", "enName": "Norway", "pyFirst": "N"}, + {"areaKey": "BE", "chsName": "比利时", "enName": "Belgium", "pyFirst": "B"}, + {"areaKey": "VE", "chsName": "委内瑞拉", "enName": "Venezuela", "pyFirst": "W"}, + {"areaKey": "MQ", "chsName": "马提尼克", "enName": "Martinique", "pyFirst": "M"}, + {"areaKey": "GY", "chsName": "圭亚那", "enName": "Guyana", "pyFirst": "G"}, + {"areaKey": "AM", "chsName": "亚美尼亚", "enName": "Armenia", "pyFirst": "Y"}, + {"areaKey": "EC", "chsName": "厄瓜多尔", "enName": "Ecuador", "pyFirst": "E"}, + {"areaKey": "CV", "chsName": "佛得角", "enName": "Cape Verde", "pyFirst": "F"}, + {"areaKey": "NZ", "chsName": "新西兰", "enName": "New Zealand", "pyFirst": "X"}, + {"areaKey": "RO", "chsName": "罗马尼亚", "enName": "Romania", "pyFirst": "L"}, + {"areaKey": "DM", "chsName": "多米尼加", "enName": "Dominica", "pyFirst": "D"}, + {"areaKey": "TZ", "chsName": "坦桑尼亚", "enName": "Tanzania", "pyFirst": "T"}, + {"areaKey": "BD", "chsName": "孟加拉国", "enName": "Bangladesh", "pyFirst": "M"}, + {"areaKey": "TD", "chsName": "乍得", "enName": "Chad", "pyFirst": "Z"}, + {"areaKey": "LT", "chsName": "立陶宛", "enName": "Lithuania", "pyFirst": "L"}, + {"areaKey": "TJ", "chsName": "塔吉克斯坦", "enName": "Tajikistan", "pyFirst": "T"}, + {"areaKey": "TK", "chsName": "托克劳", "enName": "Tokelau", "pyFirst": "T"}, + {"areaKey": "BS", "chsName": "巴哈马群岛", "enName": "Bahamas", "pyFirst": "B"}, + {"areaKey": "MM", "chsName": "缅甸", "enName": "Myanmar", "pyFirst": "M"}, + {"areaKey": "BI", "chsName": "布隆迪", "enName": "Burundi", "pyFirst": "B"}, + {"areaKey": "PY", "chsName": "巴拉圭", "enName": "Paraguay", "pyFirst": "B"}, + {"areaKey": "SK", "chsName": "斯洛伐克", "enName": "Slovakia", "pyFirst": "S"}, + {"areaKey": "FI", "chsName": "芬兰", "enName": "Finland", "pyFirst": "F"}, + {"areaKey": "GA", "chsName": "加蓬", "enName": "Gabon", "pyFirst": "J"}, + {"areaKey": "DZ", "chsName": "阿尔及利亚", "enName": "Algeria", "pyFirst": "A"}, + {"areaKey": "FO", "chsName": "法罗群岛", "enName": "Faroe Islands", "pyFirst": "F"}, + {"areaKey": "ZM", "chsName": "赞比亚", "enName": "Zambia", "pyFirst": "Z"}, + {"areaKey": "NU", "chsName": "纽埃", "enName": "Niue", "pyFirst": "N"}, + {"areaKey": "ER", "chsName": "厄立特里亚国", "enName": "Eritrea", "pyFirst": "E"}, + {"areaKey": "HK", "chsName": "香港", "enName": "Hong Kong", "pyFirst": "X"}, + {"areaKey": "IT", "chsName": "意大利", "enName": "Italy", "pyFirst": "Y"}, + {"areaKey": "MS", "chsName": "蒙特色拉特岛", "enName": "Montserrat", "pyFirst": "M"}, + {"areaKey": "EE", "chsName": "爱沙尼亚", "enName": "Estonia", "pyFirst": "A"}, + {"areaKey": "WS", "chsName": "萨摩亚", "enName": "Samoa", "pyFirst": "S"}, + {"areaKey": "TG", "chsName": "多哥", "enName": "Togo", "pyFirst": "D"}, + {"areaKey": "ML", "chsName": "马里", "enName": "Mali", "pyFirst": "M"}, + { + "areaKey": "GF", + "chsName": "法属圭亚那", + "enName": "French Guyana", + "pyFirst": "F", + }, + {"areaKey": "KM", "chsName": "科摩罗", "enName": "Comoros", "pyFirst": "K"}, + {"areaKey": "ID", "chsName": "印度尼西亚", "enName": "Indonesia", "pyFirst": "Y"}, + {"areaKey": "KE", "chsName": "肯尼亚", "enName": "Kenya", "pyFirst": "K"}, + {"areaKey": "EG", "chsName": "埃及", "enName": "Egypt", "pyFirst": "A"}, + { + "areaKey": "NF", + "chsName": "诺福克岛", + "enName": "Norfolk Island", + "pyFirst": "N", + }, + {"areaKey": "RS", "chsName": "塞尔维亚", "enName": "Serbia", "pyFirst": "S"}, + {"areaKey": "TR", "chsName": "土耳其", "enName": "Turkey", "pyFirst": "T"}, + {"areaKey": "DK", "chsName": "丹麦", "enName": "Denmark", "pyFirst": "D"}, + {"areaKey": "AD", "chsName": "安道尔", "enName": "Andorra", "pyFirst": "A"}, + {"areaKey": "LR", "chsName": "利比里亚", "enName": "Liberia", "pyFirst": "L"}, + { + "areaKey": "AE", + "chsName": "阿拉伯联合酋长国", + "enName": "United Arab Emirates", + "pyFirst": "A", + }, + {"areaKey": "CH", "chsName": "瑞士", "enName": "Switzerland", "pyFirst": "R"}, + {"areaKey": "AU", "chsName": "澳大利亚", "enName": "Australia", "pyFirst": "A"}, + {"areaKey": "TP", "chsName": "东帝汶", "enName": "East Timor", "pyFirst": "D"}, + {"areaKey": "LY", "chsName": "利比亚", "enName": "Libya", "pyFirst": "L"}, + {"areaKey": "RW", "chsName": "卢旺达", "enName": "Rwanda", "pyFirst": "L"}, + {"areaKey": "SA", "chsName": "沙特阿拉伯", "enName": "Saudi Arabia", "pyFirst": "S"}, + {"areaKey": "AR", "chsName": "阿根廷", "enName": "Argentina", "pyFirst": "A"}, + {"areaKey": "GM", "chsName": "冈比亚", "enName": "Gambia", "pyFirst": "G"}, + {"areaKey": "BY", "chsName": "白俄罗斯", "enName": "Belarus", "pyFirst": "B"}, + {"areaKey": "SL", "chsName": "塞拉利昂", "enName": "Sierra Leone", "pyFirst": "S"}, + {"areaKey": "TM", "chsName": "土库曼斯坦", "enName": "Turkmenistan", "pyFirst": "T"}, + { + "areaKey": "AG", + "chsName": "安提瓜和巴布达", + "enName": "Antigua and Barbuda", + "pyFirst": "A", + }, + {"areaKey": "MR", "chsName": "毛里塔尼亚", "enName": "Mauritania", "pyFirst": "M"}, + {"areaKey": "PT", "chsName": "葡萄牙", "enName": "Portugal", "pyFirst": "P"}, + {"areaKey": "BW", "chsName": "博茨瓦纳", "enName": "Botswana", "pyFirst": "B"}, + {"areaKey": "GT", "chsName": "危地马拉", "enName": "Guatemala", "pyFirst": "W"}, + {"areaKey": "BT", "chsName": "不丹", "enName": "Bhutan", "pyFirst": "B"}, + {"areaKey": "AI", "chsName": "安圭拉岛", "enName": "Anguilla", "pyFirst": "A"}, + {"areaKey": "OM", "chsName": "阿曼", "enName": "Oman", "pyFirst": "A"}, + {"areaKey": "KI", "chsName": "基里巴斯", "enName": "Kiribati", "pyFirst": "J"}, + {"areaKey": "UA", "chsName": "乌克兰", "enName": "Ukraine", "pyFirst": "W"}, + {"areaKey": "YE", "chsName": "也门", "enName": "Yemen", "pyFirst": "Y"}, + { + "areaKey": "DR", + "chsName": "刚果民主共和国", + "enName": "Democratic Republic of the Congo", + "pyFirst": "G", + }, + {"areaKey": "MD", "chsName": "摩尔多瓦", "enName": "Moldova", "pyFirst": "M"}, + { + "areaKey": "GW", + "chsName": "几内亚比绍", + "enName": "Guinea-Bissau", + "pyFirst": "J", + }, + { + "areaKey": "CG", + "chsName": "刚果布共和国", + "enName": "Congo Brazzaville", + "pyFirst": "G", + }, + {"areaKey": "SN", "chsName": "塞内加尔", "enName": "Senegal", "pyFirst": "S"}, + { + "areaKey": "BA", + "chsName": "波黑", + "enName": "Bosnia Hercegovina", + "pyFirst": "B", + }, + {"areaKey": "MO", "chsName": "澳门", "enName": "Macao", "pyFirst": "A"}, + { + "areaKey": "KN", + "chsName": "圣基茨和尼维斯", + "enName": "Saint Kitts and Nevis", + "pyFirst": "S", + }, + {"areaKey": "TO", "chsName": "汤加", "enName": "Tonga", "pyFirst": "T"}, + {"areaKey": "NG", "chsName": "尼日利亚", "enName": "Nigeria", "pyFirst": "N"}, + { + "areaKey": "TT", + "chsName": "特立尼达和多巴哥", + "enName": "Trinidad and Tobago", + "pyFirst": "T", + }, + { + "areaKey": "CF", + "chsName": "中非共和国", + "enName": "Central African Republic", + "pyFirst": "Z", + }, + {"areaKey": "PE", "chsName": "秘鲁", "enName": "Peru", "pyFirst": "M"}, + { + "areaKey": "PG", + "chsName": "巴布亚新几内亚", + "enName": "Papua New Guinea", + "pyFirst": "B", + }, + { + "areaKey": "CX", + "chsName": "圣延岛", + "enName": "Christmas Island", + "pyFirst": "S", + }, + { + "areaKey": "AN", + "chsName": "安的列斯", + "enName": "Netherlands Antilles", + "pyFirst": "A", + }, + {"areaKey": "BO", "chsName": "玻利维亚", "enName": "Bolivia", "pyFirst": "B"}, + {"areaKey": "IQ", "chsName": "伊拉克", "enName": "Iraq", "pyFirst": "Y"}, + {"areaKey": "NP", "chsName": "尼泊尔", "enName": "Nepal", "pyFirst": "N"}, + {"areaKey": "BJ", "chsName": "贝宁", "enName": "Benin", "pyFirst": "B"}, + {"areaKey": "VN", "chsName": "越南", "enName": "Vietnam", "pyFirst": "Y"}, + {"areaKey": "NI", "chsName": "尼加拉瓜", "enName": "Nicaragua", "pyFirst": "N"}, + {"areaKey": "PW", "chsName": "帕劳群岛", "enName": "Palau", "pyFirst": "P"}, + {"areaKey": "SO", "chsName": "索马里", "enName": "Somalia", "pyFirst": "S"}, + {"areaKey": "SM", "chsName": "圣马力诺", "enName": "San Marino", "pyFirst": "S"}, + {"areaKey": "NR", "chsName": "瑙鲁", "enName": "Nauru", "pyFirst": "N"}, + { + "areaKey": "BN", + "chsName": "文莱", + "enName": "Brunei Darussalam", + "pyFirst": "W", + }, + {"areaKey": "MZ", "chsName": "莫桑比克", "enName": "Mozambique", "pyFirst": "M"}, + {"areaKey": "GR", "chsName": "希腊", "enName": "Greece", "pyFirst": "X"}, + {"areaKey": "TN", "chsName": "突尼斯", "enName": "Tunisia", "pyFirst": "T"}, + { + "areaKey": "RU", + "chsName": "俄罗斯", + "enName": "Russian Federation", + "pyFirst": "E", + }, + {"areaKey": "MG", "chsName": "马达加斯加岛", "enName": "Madagascar", "pyFirst": "M"}, + {"areaKey": "NA", "chsName": "纳米比亚", "enName": "Namibia", "pyFirst": "N"}, + { + "areaKey": "CQ", + "chsName": "赤道几内亚", + "enName": "Equatorial Guinea", + "pyFirst": "C", + }, + {"areaKey": "SR", "chsName": "苏里南", "enName": "Suriname", "pyFirst": "S"}, + {"areaKey": "MU", "chsName": "毛里求斯", "enName": "Mauritius", "pyFirst": "M"}, + {"areaKey": "LA", "chsName": "老挝", "enName": "Laos", "pyFirst": "L"}, + {"areaKey": "US", "chsName": "美国", "enName": "United States", "pyFirst": "M"}, + { + "areaKey": "ST", + "chsName": "圣多美与普林希比共和国", + "enName": "Sao Tome and Principe", + "pyFirst": "S", + }, + {"areaKey": "BM", "chsName": "百慕大群岛", "enName": "Bermuda", "pyFirst": "B"}, + {"areaKey": "LU", "chsName": "卢森堡", "enName": "Luxembourg", "pyFirst": "L"}, + {"areaKey": "CR", "chsName": "哥斯达黎加", "enName": "Costa Rica", "pyFirst": "G"}, + {"areaKey": "KR", "chsName": "韩国", "enName": "South Korea", "pyFirst": "H"}, + {"areaKey": "CZ", "chsName": "捷克", "enName": "Czech Republic", "pyFirst": "J"}, + {"areaKey": "MX", "chsName": "墨西哥", "enName": "Mexico", "pyFirst": "M"}, + {"areaKey": "SH", "chsName": "圣赫勒拿岛", "enName": "St Helena", "pyFirst": "S"}, + {"areaKey": "AO", "chsName": "安哥拉", "enName": "Angola", "pyFirst": "A"}, + {"areaKey": "MN", "chsName": "蒙古", "enName": "Mongolia", "pyFirst": "M"}, + { + "areaKey": "VC", + "chsName": "圣文森特和格林纳丁斯", + "enName": "Saint Vincent and the Grenadines", + "pyFirst": "S", + }, + {"areaKey": "PH", "chsName": "菲律宾", "enName": "Philippines", "pyFirst": "F"}, + {"areaKey": "SC", "chsName": "塞舌尔", "enName": "Seychelles", "pyFirst": "S"}, + {"areaKey": "CK", "chsName": "库克群岛", "enName": "Cook Islands", "pyFirst": "K"}, + {"areaKey": "PK", "chsName": "巴基斯坦", "enName": "Pakistan", "pyFirst": "B"}, + {"areaKey": "HR", "chsName": "克罗地亚", "enName": "Croatia", "pyFirst": "K"}, + {"areaKey": "TH", "chsName": "泰国", "enName": "Thailand", "pyFirst": "T"}, + {"areaKey": "SI", "chsName": "斯洛文尼亚", "enName": "Slovenia", "pyFirst": "S"}, + { + "areaKey": "VG", + "chsName": "英属维尔京群岛", + "enName": "British Virgin Islands", + "pyFirst": "Y", + }, + { + "areaKey": "SY", + "chsName": "阿拉伯叙利亚共和国", + "enName": "Syrian Arab Republic", + "pyFirst": "A", + }, + {"areaKey": "CY", "chsName": "塞浦路斯", "enName": "Cyprus", "pyFirst": "S"}, + {"areaKey": "BR", "chsName": "巴西", "enName": "Brazil", "pyFirst": "B"}, + {"areaKey": "LB", "chsName": "黎巴嫩", "enName": "Lebanon", "pyFirst": "L"}, + {"areaKey": "IS", "chsName": "冰岛", "enName": "Iceland", "pyFirst": "B"}, + {"areaKey": "PA", "chsName": "巴拿马", "enName": "Panama", "pyFirst": "B"}, + {"areaKey": "FM", "chsName": "密克罗尼西亚", "enName": "Micronesia", "pyFirst": "M"}, + { + "areaKey": "VA", + "chsName": "梵蒂冈", + "enName": "Vatican City State", + "pyFirst": "F", + }, + { + "areaKey": "NC", + "chsName": "新喀里多尼亚", + "enName": "New Caledonia", + "pyFirst": "X", + }, + {"areaKey": "MT", "chsName": "马尔他", "enName": "Malta", "pyFirst": "M"}, + {"areaKey": "BG", "chsName": "保加利亚", "enName": "Bulgaria", "pyFirst": "B"}, + {"areaKey": "ES", "chsName": "西班牙", "enName": "Spain", "pyFirst": "X"}, + {"areaKey": "CI", "chsName": "象牙海岸", "enName": "Ivory Coast", "pyFirst": "X"}, + {"areaKey": "IE", "chsName": "爱尔兰", "enName": "Ireland", "pyFirst": "A"}, + {"areaKey": "BZ", "chsName": "伯利兹城", "enName": "Belize", "pyFirst": "B"}, + {"areaKey": "SZ", "chsName": "斯威士兰", "enName": "Swaziland", "pyFirst": "S"}, + {"areaKey": "SV", "chsName": "萨尔瓦多", "enName": "EI Salvador", "pyFirst": "S"}, + {"areaKey": "GE", "chsName": "格鲁吉亚", "enName": "Georgia", "pyFirst": "G"}, + {"areaKey": "SD", "chsName": "苏丹", "enName": "Sudan", "pyFirst": "S"}, + {"areaKey": "PR", "chsName": "波多黎各", "enName": "Puerto Rico", "pyFirst": "B"}, + {"areaKey": "FJ", "chsName": "斐济", "enName": "Fiji", "pyFirst": "F"}, + {"areaKey": "NL", "chsName": "荷兰", "enName": "Netherlands", "pyFirst": "H"}, + {"areaKey": "UG", "chsName": "乌干达", "enName": "Uganda", "pyFirst": "W"}, + {"areaKey": "HU", "chsName": "匈牙利", "enName": "Hungary", "pyFirst": "X"}, + { + "areaKey": "TC", + "chsName": "特克斯和凯科斯群岛", + "enName": "Turks and Caicos Islands", + "pyFirst": "T", + }, + ], +} diff --git a/bumper/plugins/bumper_confserver_v1_private_message.py b/bumper/plugins/bumper_confserver_v1_private_message.py index 596ad67..796a7f7 100644 --- a/bumper/plugins/bumper_confserver_v1_private_message.py +++ b/bumper/plugins/bumper_confserver_v1_private_message.py @@ -10,21 +10,30 @@ from datetime import datetime, timedelta class v1_private_message(plugins.ConfServerApp): - def __init__(self): self.name = "v1_private_message" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "api_v1" - + self.routes = [ - - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/hasUnreadMsg", self.handle_hasUnreadMessage, name="v1_message_hasUnreadMsg"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/getMsgList", self.handle_getMsgList, name="v1_message_getMsgList"), - + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/hasUnreadMsg", + self.handle_hasUnreadMessage, + name="v1_message_hasUnreadMsg", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/getMsgList", + self.handle_getMsgList, + name="v1_message_getMsgList", + ), ] - 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_hasUnreadMessage(self, request): # EcoVacs Home try: body = { @@ -53,7 +62,7 @@ class v1_private_message(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception(f"{e}") + logging.exception(f"{e}") + plugin = v1_private_message() - diff --git a/bumper/plugins/bumper_confserver_v1_private_shop.py b/bumper/plugins/bumper_confserver_v1_private_shop.py index 4697b8c..855570a 100644 --- a/bumper/plugins/bumper_confserver_v1_private_shop.py +++ b/bumper/plugins/bumper_confserver_v1_private_shop.py @@ -10,20 +10,24 @@ from datetime import datetime, timedelta class v1_private_shop(plugins.ConfServerApp): - def __init__(self): self.name = "v1_private_shop" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "api_v1" - - self.routes = [ - - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/shop/getCnWapShopConfig", self.handle_getCnWapShopConfig, name="v1_shop_getCnWapShopConfig"), + self.routes = [ + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/shop/getCnWapShopConfig", + self.handle_getCnWapShopConfig, + name="v1_shop_getCnWapShopConfig", + ), ] - 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_getCnWapShopConfig(self, request): # EcoVacs Home try: body = { @@ -42,7 +46,7 @@ class v1_private_shop(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception(f"{e}") + logging.exception(f"{e}") + plugin = v1_private_shop() - diff --git a/bumper/plugins/bumper_confserver_v1_private_user.py b/bumper/plugins/bumper_confserver_v1_private_user.py index 8787d5b..a69bb4e 100644 --- a/bumper/plugins/bumper_confserver_v1_private_user.py +++ b/bumper/plugins/bumper_confserver_v1_private_user.py @@ -10,34 +10,89 @@ from datetime import datetime, timedelta class v1_private_user(plugins.ConfServerApp): - def __init__(self): self.name = "v1_private_user" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "api_v1" authhandler = bumper.ConfServer.ConfServer_AuthHandler() self.routes = [ - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/login", authhandler.login, name="v1_user_login"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", authhandler.login, name="v1_user_checkLogin"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getAuthCode", authhandler.get_AuthCode, name="v1_user_getAuthCode"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/logout", authhandler.logout, name="v1_user_logout"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreement", self.handle_checkAgreement,name="v1_user_checkAgreement"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreementBatch", self.handle_checkAgreement,name="v1_user_checkAgreementBatch"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserAccountInfo", authhandler.getUserAccountInfo,name="v1_user_getUserAccountInfo"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserMenuInfo", self.handle_getUserMenuInfo,name="v1_user_getUserMenuInfo"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/changeArea", self.handle_changeArea, name="v1_user_changeArea"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/queryChangeArea", self.handle_changeArea, name="v1_user_queryChangeArea"), - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/acceptAgreementBatch", self.handle_acceptAgreementBatch, name="v1_user_acceptAgreementBatch"), - # Direct register from app: - # /{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister - #Register by email - # /registerByEmail - + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/login", + authhandler.login, + name="v1_user_login", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", + authhandler.login, + name="v1_user_checkLogin", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getAuthCode", + authhandler.get_AuthCode, + name="v1_user_getAuthCode", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/logout", + authhandler.logout, + name="v1_user_logout", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreement", + self.handle_checkAgreement, + name="v1_user_checkAgreement", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreementBatch", + self.handle_checkAgreement, + name="v1_user_checkAgreementBatch", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserAccountInfo", + authhandler.getUserAccountInfo, + name="v1_user_getUserAccountInfo", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserMenuInfo", + self.handle_getUserMenuInfo, + name="v1_user_getUserMenuInfo", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/changeArea", + self.handle_changeArea, + name="v1_user_changeArea", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/queryChangeArea", + self.handle_changeArea, + name="v1_user_queryChangeArea", + ), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/acceptAgreementBatch", + self.handle_acceptAgreementBatch, + name="v1_user_acceptAgreementBatch", + ), + # Direct register from app: + # /{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister + # Register by email + # /registerByEmail ] - 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_checkAgreement(self, request): try: @@ -76,84 +131,82 @@ class v1_private_user(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception(f"{e}") + logging.exception(f"{e}") async def handle_getUserMenuInfo(self, request): try: apptype = request.match_info.get("apptype", "") body = { - "code": "0000", - "data": [ - { + "code": "0000", + "data": [ + { "menuItems": [ { - "clickAction": 1, - "clickUri": "https://ecovacs.zendesk.com/hc/en-us", - "menuIconUrl": "https://gl-us-pub.ecovacs.com/upload/global/2019/12/16/2019121603180741b73907046e742b80e8fe4a90fe2498.png", - "menuId": "20191216031849_4d744630f7ad2f5208a4b8051be61d10", - "menuName": "Help & Feedback", - "paramsJson": "" + "clickAction": 1, + "clickUri": "https://ecovacs.zendesk.com/hc/en-us", + "menuIconUrl": "https://gl-us-pub.ecovacs.com/upload/global/2019/12/16/2019121603180741b73907046e742b80e8fe4a90fe2498.png", + "menuId": "20191216031849_4d744630f7ad2f5208a4b8051be61d10", + "menuName": "Help & Feedback", + "paramsJson": "", } ], - "menuPositionKey": "A_FIRST" - }, - { + "menuPositionKey": "A_FIRST", + }, + { "menuItems": [ { - "clickAction": 3, - "clickUri": "robotShare", - "menuIconUrl": "https://gl-us-pub.ecovacs.com/upload/global/2019/12/16/2019121603284185e632ec6c5da10bd82119d7047a1f9e.png", - "menuId": "20191216032853_5fac4cc9cbd0e166dfa951485d1d8cc4", - "menuName": "Share Robot", - "paramsJson": "" + "clickAction": 3, + "clickUri": "robotShare", + "menuIconUrl": "https://gl-us-pub.ecovacs.com/upload/global/2019/12/16/2019121603284185e632ec6c5da10bd82119d7047a1f9e.png", + "menuId": "20191216032853_5fac4cc9cbd0e166dfa951485d1d8cc4", + "menuName": "Share Robot", + "paramsJson": "", } ], - "menuPositionKey": "B_SECOND" - }, - { + "menuPositionKey": "B_SECOND", + }, + { "menuItems": [ { - "clickAction": 3, - "clickUri": "config", - "menuIconUrl": "https://gl-us-pub.ecovacs.com/upload/global/2019/12/16/201912160325324068da4e4a09b8c3973db162e84784d5.png", - "menuId": "20191216032545_ebea0fbb4cb02d9c2fec5bdf3371bc2d", - "menuName": "Settings", - "paramsJson": "" + "clickAction": 3, + "clickUri": "config", + "menuIconUrl": "https://gl-us-pub.ecovacs.com/upload/global/2019/12/16/201912160325324068da4e4a09b8c3973db162e84784d5.png", + "menuId": "20191216032545_ebea0fbb4cb02d9c2fec5bdf3371bc2d", + "menuName": "Settings", + "paramsJson": "", } ], - "menuPositionKey": "C_THIRD" - }, - { + "menuPositionKey": "C_THIRD", + }, + { "menuItems": [ { - "clickAction": 1, - "clickUri": "https://bumper.ecovacs.com/", - "menuIconUrl": "https://gl-us-pub.ecovacs.com/upload/global/2019/12/16/201912160325324068da4e4a09b8c3973db162e84784d5.png", - "menuId": "20191216032545_ebea0fbb4cb02d9c2fec5bdf3371bc2c", - "menuName": "Bumper Status", - "paramsJson": "" + "clickAction": 1, + "clickUri": "https://bumper.ecovacs.com/", + "menuIconUrl": "https://gl-us-pub.ecovacs.com/upload/global/2019/12/16/201912160325324068da4e4a09b8c3973db162e84784d5.png", + "menuId": "20191216032545_ebea0fbb4cb02d9c2fec5bdf3371bc2c", + "menuName": "Bumper Status", + "paramsJson": "", } ], - "menuPositionKey": "D_FOURTH" - } - ], - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()) - } + "menuPositionKey": "D_FOURTH", + }, + ], + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } return web.json_response(body) except Exception as e: - logging.exception(f"{e}") + logging.exception(f"{e}") async def handle_changeArea(self, request): try: body = { "code": bumper.RETURN_API_SUCCESS, - "data": { - "isNeedReLogin": "N" - }, + "data": {"isNeedReLogin": "N"}, "msg": "操作成功", "success": True, "time": self.get_milli_time(datetime.utcnow().timestamp()), @@ -179,5 +232,5 @@ class v1_private_user(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") -plugin = v1_private_user() +plugin = v1_private_user() diff --git a/bumper/plugins/bumper_confserver_v1_private_userSetting.py b/bumper/plugins/bumper_confserver_v1_private_userSetting.py index 9822545..2e78594 100644 --- a/bumper/plugins/bumper_confserver_v1_private_userSetting.py +++ b/bumper/plugins/bumper_confserver_v1_private_userSetting.py @@ -7,18 +7,24 @@ from datetime import datetime class v1_private_userSetting(plugins.ConfServerApp): - def __init__(self): self.name = "v1_private_userSetting" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "api_v1" self.routes = [ - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/userSetting/getSuggestionSetting", self.handle_getSuggestionSetting,name="v1_userSetting_getSuggestionSetting"), + 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 + self.get_milli_time = ( + bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + ) async def handle_getSuggestionSetting(self, request): try: @@ -31,19 +37,19 @@ class v1_private_userSetting(plugins.ConfServerApp): { "name": "Aktionen/Angebote/Ereignisse", "settingKey": "MARKETING", - "val": "Y" + "val": "Y", }, { "name": "Benutzerbefragung", "settingKey": "QUESTIONNAIRE", - "val": "Y" + "val": "Y", }, { "name": "Produkt-Upgrade/Hilfe für Benutzer", "settingKey": "INTRODUCTION", - "val": "Y" - } - ] + "val": "Y", + }, + ], }, "msg": "操作成功", "time": self.get_milli_time(datetime.utcnow().timestamp()), @@ -52,7 +58,7 @@ class v1_private_userSetting(plugins.ConfServerApp): return web.json_response(body) except Exception as e: - logging.exception(f"{e}") + logging.exception(f"{e}") plugin = v1_private_userSetting() diff --git a/bumper/plugins/bumper_confserver_v2_private_user.py b/bumper/plugins/bumper_confserver_v2_private_user.py index 5a3c7a3..981fb03 100644 --- a/bumper/plugins/bumper_confserver_v2_private_user.py +++ b/bumper/plugins/bumper_confserver_v2_private_user.py @@ -10,20 +10,25 @@ from datetime import datetime, timedelta class v2_private_user(plugins.ConfServerApp): - def __init__(self): self.name = "v2_private_user" - self.plugin_type = "sub_api" + self.plugin_type = "sub_api" self.sub_api = "api_v2" authhandler = bumper.ConfServer.ConfServer_AuthHandler() self.routes = [ - web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", authhandler.login, name="v2_user_checkLogin"), + web.route( + "*", + "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", + authhandler.login, + name="v2_user_checkLogin", + ), ] - self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - + self.get_milli_time = ( + bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + ) + plugin = v2_private_user() - diff --git a/bumper/util.py b/bumper/util.py index a2aabcf..ce1359b 100644 --- a/bumper/util.py +++ b/bumper/util.py @@ -22,7 +22,9 @@ def get_logger(name: str, rotate: RotatingFileHandler = None) -> logging.Logger: if not log_to_stdout: if not rotate: - rotate = RotatingFileHandler(f"logs/{name}.log", maxBytes=5000000, backupCount=5) + rotate = RotatingFileHandler( + f"logs/{name}.log", maxBytes=5000000, backupCount=5 + ) rotate.setFormatter(logformat) logger.addHandler(rotate) else: @@ -31,8 +33,12 @@ def get_logger(name: str, rotate: RotatingFileHandler = None) -> logging.Logger: __loggers[name] = logger if name == "mqttserver": - get_logger("transitions", rotate).setLevel(logging.CRITICAL + 1) # Ignore this logger - get_logger("passlib", rotate).setLevel(logging.CRITICAL + 1) # Ignore this logger + get_logger("transitions", rotate).setLevel( + logging.CRITICAL + 1 + ) # Ignore this logger + get_logger("passlib", rotate).setLevel( + logging.CRITICAL + 1 + ) # Ignore this logger get_logger("hbmqtt.broker", rotate) get_logger("hbmqtt.mqtt.protocol", rotate) get_logger("hbmqtt.client", rotate) diff --git a/bumper/xmppserver.py b/bumper/xmppserver.py index 802320a..fbced1a 100644 --- a/bumper/xmppserver.py +++ b/bumper/xmppserver.py @@ -70,9 +70,7 @@ class XMPPServer_Protocol(asyncio.Protocol): def connection_made(self, transport): if self.aclient: # Existing client... upgrading to TLS - xmppserverlog.debug( - f"Upgraded connection for {self.aclient.address}" - ) + xmppserverlog.debug(f"Upgraded connection for {self.aclient.address}") self.aclient.transport = transport else: aclient = XMPPAsyncClient(transport) @@ -254,9 +252,7 @@ class XMPPAsyncClient: if client.type == self.BOT: if client.uid.lower() in ctl_to.lower(): - xmppserverlog.debug( - f"Sending ctl to bot: {rxmlstring}" - ) + xmppserverlog.debug(f"Sending ctl to bot: {rxmlstring}") client.send(rxmlstring) except Exception as e: @@ -310,17 +306,21 @@ class XMPPAsyncClient: if not "from" in xml.attrib: xml.attrib["from"] = f"{self.bumper_jid}" if "errno" in data: - xmppserverlog.error(f"Error from bot - {data}") + xmppserverlog.error(f"Error from bot - {data}") if ( "errno='103'" in data ): # No permissions, usually if bot was last on Ecovac network, Bumper will try to add fuid user as owner if self.type == self.BOT: - xmppserverlog.info("Bot reported user has no permissions, Bumper will attempt to add user to bot. This is typical if bot was last on Ecovacs Network.") + xmppserverlog.info( + "Bot reported user has no permissions, Bumper will attempt to add user to bot. This is typical if bot was last on Ecovacs Network." + ) xquery = xml.getchildren() ctl = xquery[0].getchildren() if "error" in ctl[0].attrib: ctlerr = ctl[0].attrib["error"] - adminuser = ctlerr.replace("permission denied, please contact ", "") + adminuser = ctlerr.replace( + "permission denied, please contact ", "" + ) adminuser = adminuser.replace(" ", "") elif "admin" in ctl[0].attrib: adminuser = ctl[0].attrib["admin"] @@ -473,9 +473,7 @@ class XMPPAsyncClient: async def _handle_starttls(self, data): try: if self.TLSUpgraded == False: - self.TLSUpgraded = ( - True - ) # Set TLSUpgraded true to prevent further attempts to upgrade connection + self.TLSUpgraded = True # Set TLSUpgraded true to prevent further attempts to upgrade connection xmppserverlog.debug( "Upgrading connection with STARTTLS for {}:{}".format( self.address[0], self.address[1] @@ -662,9 +660,7 @@ class XMPPAsyncClient: ) # Send dummy return - self.send( - f' dummy ' - ) + self.send(f' dummy ') elif xml.get("type") == "unavailable": xmppserverlog.debug( "client presence unavailable (DISCONNECT) - {} ".format( @@ -681,9 +677,7 @@ class XMPPAsyncClient: ) ) # Send dummy return - self.send( - f' dummy ' - ) + self.send(f' dummy ') def _parse_data(self, data): @@ -768,9 +762,7 @@ class XMPPAsyncClient: self._handle_connect(newdata.encode("utf-8")) else: if not (newdata == "" or newdata == " "): - xmppserverlog.error( - f"xml parse error - {newdata} - {e}" - ) + xmppserverlog.error(f"xml parse error - {newdata} - {e}") elif "not well-formed (invalid token)" in e.msg: # If a lone
- client is signalling end of session/disconnect @@ -782,15 +774,11 @@ class XMPPAsyncClient: else: if "" in newdata: - xmppserverlog.error( - f"xml parse error - {newdata} - {e}" - ) + xmppserverlog.error(f"xml parse error - {newdata} - {e}") else: self.send("
") # Close stream self._set_state("DISCONNECT") diff --git a/tests/const.py b/tests/const.py index f2a229f..143d88a 100644 --- a/tests/const.py +++ b/tests/const.py @@ -1,2 +1,2 @@ -HOST= "127.0.0.1" +HOST = "127.0.0.1" MQTT_PORT = 8883 diff --git a/tests/test_confserver.py b/tests/test_confserver.py index 058e40a..9673618 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -42,29 +42,39 @@ async def test_confserver_ssl(): conf_server.confserver_app() asyncio.create_task(conf_server.start_server()) + async def test_confserver_exceptions(): with LogCapture() as l: - conf_server = bumper.ConfServer((HOST, 8007), usessl=True) - conf_server.confserver_app() - conf_server.site = web.TCPSite + conf_server = bumper.ConfServer((HOST, 8007), usessl=True) + conf_server.confserver_app() + conf_server.site = web.TCPSite - #bind permission - conf_server.site.start = mock.Mock(side_effect=OSError(1, "error while attempting to bind on address ('127.0.0.1', 8007): permission denied")) - await conf_server.start_server() + # bind permission + conf_server.site.start = mock.Mock( + side_effect=OSError( + 1, + "error while attempting to bind on address ('127.0.0.1', 8007): permission denied", + ) + ) + await conf_server.start_server() - #asyncio Cancel - conf_server.site = web.TCPSite - conf_server.site.start = mock.Mock(side_effect=asyncio.CancelledError) - await conf_server.start_server() + # asyncio Cancel + conf_server.site = web.TCPSite + conf_server.site.start = mock.Mock(side_effect=asyncio.CancelledError) + await conf_server.start_server() + + # general exception + conf_server.site = web.TCPSite + conf_server.site.start = mock.Mock(side_effect=Exception(1, "general")) + await conf_server.start_server() - #general exception - conf_server.site = web.TCPSite - conf_server.site.start = mock.Mock(side_effect=Exception(1, "general")) - await conf_server.start_server() - l.check_present( - ("confserver", "ERROR", "error while attempting to bind on address ('127.0.0.1', 8007): permission denied") + ( + "confserver", + "ERROR", + "error while attempting to bind on address ('127.0.0.1', 8007): permission denied", + ) ) @@ -89,7 +99,7 @@ def test_get_milli_time(): async def test_base(aiohttp_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - + # Start MQTT mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") bumper.mqtt_server = mqtt_server @@ -100,7 +110,7 @@ async def test_base(aiohttp_client): xmpp_server = bumper.XMPPServer(xmpp_address) bumper.xmpp_server = xmpp_server await xmpp_server.start_async_server() - + # Start Helperbot mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) bumper.mqtt_helperbot = mqtt_helperbot @@ -108,7 +118,7 @@ async def test_base(aiohttp_client): client = await aiohttp_client(create_app) resp = await client.get("/") - assert resp.status == 200 + assert resp.status == 200 mqtt_helperbot.Client.disconnect() @@ -120,7 +130,7 @@ async def test_base(aiohttp_client): async def test_restartService(aiohttp_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - + # Start MQTT mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") bumper.mqtt_server = mqtt_server @@ -131,37 +141,39 @@ async def test_restartService(aiohttp_client): xmpp_server = bumper.XMPPServer(xmpp_address) bumper.xmpp_server = xmpp_server await xmpp_server.start_async_server() - + # Start Helperbot mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() client = await aiohttp_client(create_app) - + resp = await client.get("/restart_Helperbot") - assert resp.status == 200 + assert resp.status == 200 resp = await client.get("/restart_MQTTServer") - assert resp.status == 200 + assert resp.status == 200 resp = await client.get("/restart_XMPPServer") - assert resp.status == 200 + assert resp.status == 200 mqtt_helperbot.Client.disconnect() await mqtt_server.broker.shutdown() xmpp_server.disconnect() + async def test_RemoveBot(aiohttp_client): client = await aiohttp_client(create_app) resp = await client.get("/bot/remove/test_did") - assert resp.status == 200 + assert resp.status == 200 + async def test_RemoveClient(aiohttp_client): client = await aiohttp_client(create_app) resp = await client.get("/client/remove/test_resource") - assert resp.status == 200 + assert resp.status == 200 async def test_login(aiohttp_client): @@ -180,7 +192,7 @@ async def test_login(aiohttp_client): assert "username" in jsonresp["data"] remove_existing_db() - bumper.db = "tests/tmp.db" # Set db location for testing + bumper.db = "tests/tmp.db" # Set db location for testing # Test global_e without user resp = await client.get("/v1/private/us/en/dev_1234/global_e/1/0/0/user/login") @@ -216,11 +228,11 @@ async def test_login(aiohttp_client): # Add a bot to db that doesn't have a did newbot = { - "class": "dev_1234", - "company": "com_123", - #"did": self.did, - "name": "sn_1234", - "resource": "res_1234", + "class": "dev_1234", + "company": "com_123", + # "did": self.did, + "name": "sn_1234", + "resource": "res_1234", } bumper.bot_full_upsert(newbot) @@ -592,11 +604,10 @@ async def test_getProductIotMap(aiohttp_client): jsonresp = json.loads(text) assert jsonresp["code"] == bumper.RETURN_API_SUCCESS - # Test getPimFile resp = await client.get("/api/pim/file/get/123") assert resp.status == 200 - + async def test_getUsersAPI(aiohttp_client): remove_existing_db() @@ -1006,6 +1017,3 @@ async def test_dim_devmanager(aiohttp_client): text = await resp.text() test_resp = json.loads(text) assert test_resp["ret"] == "fail" - - - diff --git a/tests/test_init.py b/tests/test_init.py index d84848f..e8e18a5 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -59,4 +59,3 @@ async def test_start_stop_debug(): ("bumper", "INFO", "Shutting down"), ("bumper", "INFO", "Shutdown complete") ) assert b.shutting_down == True - diff --git a/tests/test_mqttserver.py b/tests/test_mqttserver.py index aebbd8d..87eab72 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -14,6 +14,7 @@ import time from tests.const import HOST, MQTT_PORT + async def test_helperbot_message(): mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") await mqtt_server.broker_coro() @@ -51,13 +52,11 @@ async def test_helperbot_message(): mqtt_helperbot.Client._connected_state._value == True ) # Check helperbot is connected msg_payload = "{}" - msg_topic_name = ( - "iot/p2p/GetWKVer/helperbot/bumper/helperbot/bot_serial/ls1ok3/wC3g/q/iCmuqp/j" - ) + msg_topic_name = "iot/p2p/GetWKVer/helperbot/bumper/helperbot/bot_serial/ls1ok3/wC3g/q/iCmuqp/j" await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) - + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) l.check_present( @@ -77,9 +76,7 @@ async def test_helperbot_message(): mqtt_helperbot.Client._connected_state._value == True ) # Check helperbot is connected msg_payload = '{"ret":"ok","ver":"0.13.5"}' - msg_topic_name = ( - "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/iCmuqp/j" - ) + msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/iCmuqp/j" await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) @@ -103,16 +100,13 @@ async def test_helperbot_message(): mqtt_helperbot.Client._connected_state._value == True ) # Check helperbot is connected msg_payload = "test" - msg_topic_name = ( - "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helperbot/p/iCmuqp/j" - ) + msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helperbot/p/iCmuqp/j" await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) - l.check_present( ( "helperbot", @@ -134,7 +128,7 @@ async def test_helperbot_message(): await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) - + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) l.check_present( @@ -156,7 +150,7 @@ async def test_helperbot_expire_message(): timeout = 0.1 # Test broadcast message - mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT,timeout) + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT, timeout) bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() assert ( @@ -167,14 +161,13 @@ async def test_helperbot_expire_message(): expire_msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" currenttime = time.time() request_id = "ABC" - data = { - "time": currenttime, - "topic": expire_msg_topic_name, - "payload": expire_msg_payload, - } + data = { + "time": currenttime, + "topic": expire_msg_topic_name, + "payload": expire_msg_payload, + } - - mqtt_helperbot.commands[request_id]= data + mqtt_helperbot.commands[request_id] = data assert mqtt_helperbot.commands[request_id] == data @@ -185,13 +178,12 @@ async def test_helperbot_expire_message(): msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) # Send another message to force get_msg - await asyncio.sleep(timeout*2) + await asyncio.sleep(timeout * 2) assert mqtt_helperbot.commands.get(request_id, None) == None await mqtt_helperbot.Client.disconnect() await mqtt_server.broker.shutdown() - async def test_helperbot_sendcommand(): @@ -233,9 +225,7 @@ async def test_helperbot_sendcommand(): # Send response beforehand msg_payload = '{"ret":"ok","ver":"0.13.5"}' - msg_topic_name = ( - "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" - ) + msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) @@ -247,7 +237,7 @@ async def test_helperbot_sendcommand(): "ret": "ok", } - #mqtt_helperbot.Client.disconnect() + # mqtt_helperbot.Client.disconnect() # Test GetLifeSpan (xml command) cmdjson = { @@ -269,9 +259,7 @@ async def test_helperbot_sendcommand(): # Send response beforehand msg_payload = "" - msg_topic_name = ( - "iot/p2p/GetLifeSpan/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testx/q" - ) + msg_topic_name = "iot/p2p/GetLifeSpan/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testx/q" await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) @@ -289,12 +277,7 @@ async def test_helperbot_sendcommand(): "payloadType": "j", "toRes": "wC3g", "payload": { - "header": { - "pri": 1, - "ts": "1569380075887", - "tzm": -240, - "ver": "0.0.50" - } + "header": {"pri": 1, "ts": "1569380075887", "tzm": -240, "ver": "0.0.50"} }, "td": "q", "toId": "bot_serial", @@ -310,7 +293,7 @@ async def test_helperbot_sendcommand(): # Send response beforehand msg_payload = '{"body":{"code":0,"data":{"area":0,"cid":"111","start":"1569378657","time":6,"type":"auto"},"msg":"ok"},"header":{"fwVer":"1.6.4","hwVer":"0.1.1","pri":1,"ts":"1569380074036","tzm":480,"ver":"0.0.1"}}' - + msg_topic_name = ( "iot/p2p/getStats/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testj/j" ) @@ -322,14 +305,33 @@ async def test_helperbot_sendcommand(): assert commandresult == { "id": "testj", - "resp": {'body':{'code':0,'data':{'area':0,'cid':'111','start':'1569378657','time':6,'type':'auto'},'msg':'ok'},'header':{'fwVer':'1.6.4','hwVer':'0.1.1','pri':1,'ts':'1569380074036','tzm':480,'ver':'0.0.1'}}, + "resp": { + "body": { + "code": 0, + "data": { + "area": 0, + "cid": "111", + "start": "1569378657", + "time": 6, + "type": "auto", + }, + "msg": "ok", + }, + "header": { + "fwVer": "1.6.4", + "hwVer": "0.1.1", + "pri": 1, + "ts": "1569380074036", + "tzm": 480, + "ver": "0.0.1", + }, + }, "ret": "ok", } mqtt_helperbot.Client.disconnect() await mqtt_server.broker.shutdown() - async def test_mqttserver(): @@ -338,9 +340,10 @@ async def test_mqttserver(): bumper.db = "tests/tmp.db" # Set db location for testing + mqtt_server = bumper.MQTTServer( + HOST, MQTT_PORT, password_file="tests/passwd", allow_anonymous=True + ) - mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd", allow_anonymous=True) - await mqtt_server.broker_coro() # Test helperbot connect @@ -387,13 +390,19 @@ async def test_mqttserver(): test_client.client_id = "test-file-auth" # await test_client.start_helper_bot() test_client.Client = hbmqtt.client.MQTTClient( - client_id=test_client.client_id, config={"check_hostname": False, "auto_reconnect": False, "reconnect_retries": 1} + client_id=test_client.client_id, + config={ + "check_hostname": False, + "auto_reconnect": False, + "reconnect_retries": 1, + }, ) # good user/pass await test_client.Client.connect( f"mqtts://test-client:abc123!@{HOST}:{MQTT_PORT}/", - cafile=bumper.ca_cert, cleansession=True + cafile=bumper.ca_cert, + cleansession=True, ) assert ( @@ -403,42 +412,57 @@ async def test_mqttserver(): assert ( test_client.Client._connected_state._value == False ) # Check client is disconnected - + # bad password with LogCapture() as l: - + await test_client.Client.connect( f"mqtts://test-client:notvalid!@{HOST}:{MQTT_PORT}/", - cafile=bumper.ca_cert, cleansession=True + cafile=bumper.ca_cert, + cleansession=True, ) l.check_present( - ("mqttserver", "INFO", "File Authentication Failed - Username: test-client - ClientID: test-file-auth"), - order_matters=False - ) - # no username in file + ( + "mqttserver", + "INFO", + "File Authentication Failed - Username: test-client - ClientID: test-file-auth", + ), + order_matters=False, + ) + # no username in file await test_client.Client.connect( f"mqtts://test-client-noexist:notvalid!@{HOST}:{MQTT_PORT}/", - cafile=bumper.ca_cert, cleansession=True + cafile=bumper.ca_cert, + cleansession=True, ) - l.check_present( - ("mqttserver", "INFO", 'File Authentication Failed - No Entry for Username: test-client-noexist - ClientID: test-file-auth'), - order_matters=False + ( + "mqttserver", + "INFO", + "File Authentication Failed - No Entry for Username: test-client-noexist - ClientID: test-file-auth", + ), + order_matters=False, ) - + await mqtt_server.broker.shutdown() - + async def test_nofileauth_mqttserver(): with LogCapture() as l: - mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd-notfound") + mqtt_server = bumper.MQTTServer( + HOST, MQTT_PORT, password_file="tests/passwd-notfound" + ) await mqtt_server.broker_coro() - await mqtt_server.broker.shutdown() + await mqtt_server.broker.shutdown() l.check_present( - ("hbmqtt.broker.plugins.bumper", "WARNING", 'Password file tests/passwd-notfound not found'), - order_matters=False + ( + "hbmqtt.broker.plugins.bumper", + "WARNING", + "Password file tests/passwd-notfound not found", + ), + order_matters=False, ) diff --git a/tests/test_xmppserver.py b/tests/test_xmppserver.py index 776cd0d..cc5521c 100644 --- a/tests/test_xmppserver.py +++ b/tests/test_xmppserver.py @@ -513,7 +513,9 @@ async def test_client_send_iq(*args, **kwargs): bumper.xmppserver.XMPPServer.clients.append(xmppclient2) # Roster IQ - Only seen from Android app so far - test_data = b'' + test_data = ( + b'' + ) xmppclient._parse_data(test_data) assert ( @@ -594,4 +596,3 @@ async def test_client_send_iq(*args, **kwargs): # Reset mock calls mock_send.reset_mock() - From f80737c37b75f6778d366286f06c1cb4a7e2cc12 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 12:01:40 +0100 Subject: [PATCH 07/10] run codespell --- .pre-commit-config.yaml | 2 +- README.md | 2 +- bumper/plugins/bumper_confserver_portal_appsvr.py | 2 +- bumper/xmppserver.py | 2 +- tests/test_xmppserver.py | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d2d0d97..4d8539d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: codespell args: - - --ignore-words-list=hass,deebot + - -L bumper - --skip="./.*,*.csv,*.json" - --quiet-level=2 exclude_types: [csv, json] diff --git a/README.md b/README.md index 9704fcc..f6aa939 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ A Gitter community has been created for Bumper so users can chat and dig into is ***Testing needed*** -Bumper needs users to assist with testing in order to ensure compatability as bumper moves forward! If you've tested Bumper with your bot, please open an issue with details on success or issues. +Bumper needs users to assist with testing in order to ensure compatibility as bumper moves forward! If you've tested Bumper with your bot, please open an issue with details on success or issues. ***Please note**: this software is experimental and not ready for production use. Use at your own risk.* diff --git a/bumper/plugins/bumper_confserver_portal_appsvr.py b/bumper/plugins/bumper_confserver_portal_appsvr.py index 60dbb85..c47b22a 100644 --- a/bumper/plugins/bumper_confserver_portal_appsvr.py +++ b/bumper/plugins/bumper_confserver_portal_appsvr.py @@ -143,7 +143,7 @@ class portal_api_appsvr(plugins.ConfServerApp): # "auth": { # "realm": "ecouser.net", # "resource": "res", - # "token": "toke", + # "token": "token", # "userid": "userid", # "with": "users" # }, diff --git a/bumper/xmppserver.py b/bumper/xmppserver.py index fbced1a..8dbd5cc 100644 --- a/bumper/xmppserver.py +++ b/bumper/xmppserver.py @@ -756,7 +756,7 @@ class XMPPAsyncClient: if ( "no element found" in e.msg ): # Element not closed or not all bytes received - # Happens wth connect stream often + # Happens with connect stream often if "' xmppclient._parse_data(test_data) @@ -418,7 +418,7 @@ async def test_bot_init(*args, **kwargs): # Reset mock calls mock_send.reset_mock() - # Send presense from bot + # Send presence from bot test_data = b"hello world" xmppclient._parse_data(test_data) From ccca39761c006210faac198988ce69ddeda92d31 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 12:03:53 +0100 Subject: [PATCH 08/10] run isort --- bumper/__init__.py | 2 +- bumper/confserver.py | 1 + bumper/db.py | 9 ++++--- bumper/plugins.py | 1 + .../plugins/bumper_confserver_portal_dim.py | 15 +++++------ .../plugins/bumper_confserver_portal_iot.py | 15 +++++------ .../plugins/bumper_confserver_portal_neng.py | 11 ++++---- .../plugins/bumper_confserver_portal_rapp.py | 6 +++-- .../plugins/bumper_confserver_portal_users.py | 11 ++++---- .../bumper_confserver_upload_global.py | 13 +++++----- .../bumper_confserver_v1_global_auth.py | 11 ++++---- .../bumper_confserver_v1_private_ad.py | 11 ++++---- .../bumper_confserver_v1_private_campaign.py | 11 ++++---- .../bumper_confserver_v1_private_message.py | 11 ++++---- .../bumper_confserver_v1_private_shop.py | 11 ++++---- .../bumper_confserver_v1_private_user.py | 11 ++++---- ...umper_confserver_v1_private_userSetting.py | 6 +++-- .../bumper_confserver_v2_private_user.py | 11 ++++---- bumper/util.py | 1 - bumper/xmppserver.py | 8 +++--- setup.cfg | 25 +++++++++++++++++++ tests/test_confserver.py | 22 ++++++++-------- tests/test_db.py | 14 ++++++----- tests/test_init.py | 18 +++++++------ tests/test_mqttserver.py | 19 +++++++------- tests/test_xmppserver.py | 18 +++++++------ 26 files changed, 172 insertions(+), 120 deletions(-) create mode 100644 setup.cfg diff --git a/bumper/__init__.py b/bumper/__init__.py index fae5aad..bcbf2a6 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -9,7 +9,7 @@ from typing import Optional from bumper.confserver import ConfServer from bumper.db import * from bumper.models import * -from bumper.mqttserver import MQTTServer, MQTTHelperBot +from bumper.mqttserver import MQTTHelperBot, MQTTServer from bumper.util import get_logger, log_to_stdout from bumper.xmppserver import XMPPServer diff --git a/bumper/confserver.py b/bumper/confserver.py index e8fb066..b5859b3 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -11,6 +11,7 @@ from aiohttp import web from bumper import plugins from bumper.models import * + from .util import get_logger diff --git a/bumper/db.py b/bumper/db.py index 09e99ef..d22cd1d 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -4,18 +4,19 @@ import logging import os from datetime import datetime, timedelta -from tinydb import TinyDB, Query +from tinydb import Query, TinyDB import bumper -from .util import get_logger from bumper.models import ( - VacBotClient, - VacBotDevice, BumperUser, EcoVacsHomeProducts, OAuth, + VacBotClient, + VacBotDevice, ) +from .util import get_logger + bumperlog = get_logger("bumper") diff --git a/bumper/plugins.py b/bumper/plugins.py index e3934f3..dcc0fe8 100644 --- a/bumper/plugins.py +++ b/bumper/plugins.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import asyncio + from aiohttp import web diff --git a/bumper/plugins/bumper_confserver_portal_dim.py b/bumper/plugins/bumper_confserver_portal_dim.py index 9f4aa29..32da818 100644 --- a/bumper/plugins/bumper_confserver_portal_dim.py +++ b/bumper/plugins/bumper_confserver_portal_dim.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins -from datetime import datetime, timedelta -import string import random +import string +from datetime import datetime, timedelta + +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * class portal_api_dim(plugins.ConfServerApp): diff --git a/bumper/plugins/bumper_confserver_portal_iot.py b/bumper/plugins/bumper_confserver_portal_iot.py index 85c4e61..330d852 100644 --- a/bumper/plugins/bumper_confserver_portal_iot.py +++ b/bumper/plugins/bumper_confserver_portal_iot.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins -from datetime import datetime, timedelta -import string import random +import string +from datetime import datetime, timedelta + +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * class portal_api_iot(plugins.ConfServerApp): diff --git a/bumper/plugins/bumper_confserver_portal_neng.py b/bumper/plugins/bumper_confserver_portal_neng.py index a591006..7ffb7a7 100644 --- a/bumper/plugins/bumper_confserver_portal_neng.py +++ b/bumper/plugins/bumper_confserver_portal_neng.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class portal_api_neng(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/plugins/bumper_confserver_portal_rapp.py b/bumper/plugins/bumper_confserver_portal_rapp.py index 44ca250..0b0ac3b 100644 --- a/bumper/plugins/bumper_confserver_portal_rapp.py +++ b/bumper/plugins/bumper_confserver_portal_rapp.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 -from aiohttp import web import logging -from bumper.models import * + +from aiohttp import web + from bumper import plugins +from bumper.models import * class api_rapp(plugins.ConfServerApp): diff --git a/bumper/plugins/bumper_confserver_portal_users.py b/bumper/plugins/bumper_confserver_portal_users.py index 3c20182..3118abc 100644 --- a/bumper/plugins/bumper_confserver_portal_users.py +++ b/bumper/plugins/bumper_confserver_portal_users.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class portal_api_users(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/plugins/bumper_confserver_upload_global.py b/bumper/plugins/bumper_confserver_upload_global.py index ed7447e..5ab1aae 100644 --- a/bumper/plugins/bumper_confserver_upload_global.py +++ b/bumper/plugins/bumper_confserver_upload_global.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins -from datetime import datetime, timedelta import os +from datetime import datetime, timedelta + +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * class upload_global(plugins.ConfServerApp): diff --git a/bumper/plugins/bumper_confserver_v1_global_auth.py b/bumper/plugins/bumper_confserver_v1_global_auth.py index ffb94b5..3650647 100644 --- a/bumper/plugins/bumper_confserver_v1_global_auth.py +++ b/bumper/plugins/bumper_confserver_v1_global_auth.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class v1_global_auth(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/plugins/bumper_confserver_v1_private_ad.py b/bumper/plugins/bumper_confserver_v1_private_ad.py index 000d85b..36869e3 100644 --- a/bumper/plugins/bumper_confserver_v1_private_ad.py +++ b/bumper/plugins/bumper_confserver_v1_private_ad.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class v1_private_ad(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/plugins/bumper_confserver_v1_private_campaign.py b/bumper/plugins/bumper_confserver_v1_private_campaign.py index da097a3..d0ce35d 100644 --- a/bumper/plugins/bumper_confserver_v1_private_campaign.py +++ b/bumper/plugins/bumper_confserver_v1_private_campaign.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class v1_private_campaign(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/plugins/bumper_confserver_v1_private_message.py b/bumper/plugins/bumper_confserver_v1_private_message.py index 796a7f7..6bb4eac 100644 --- a/bumper/plugins/bumper_confserver_v1_private_message.py +++ b/bumper/plugins/bumper_confserver_v1_private_message.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class v1_private_message(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/plugins/bumper_confserver_v1_private_shop.py b/bumper/plugins/bumper_confserver_v1_private_shop.py index 855570a..00693d0 100644 --- a/bumper/plugins/bumper_confserver_v1_private_shop.py +++ b/bumper/plugins/bumper_confserver_v1_private_shop.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class v1_private_shop(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/plugins/bumper_confserver_v1_private_user.py b/bumper/plugins/bumper_confserver_v1_private_user.py index a69bb4e..f4c5cce 100644 --- a/bumper/plugins/bumper_confserver_v1_private_user.py +++ b/bumper/plugins/bumper_confserver_v1_private_user.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class v1_private_user(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/plugins/bumper_confserver_v1_private_userSetting.py b/bumper/plugins/bumper_confserver_v1_private_userSetting.py index 2e78594..37bb39d 100644 --- a/bumper/plugins/bumper_confserver_v1_private_userSetting.py +++ b/bumper/plugins/bumper_confserver_v1_private_userSetting.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 -from aiohttp import web import logging +from datetime import datetime + +from aiohttp import web + import bumper from bumper import plugins -from datetime import datetime class v1_private_userSetting(plugins.ConfServerApp): diff --git a/bumper/plugins/bumper_confserver_v2_private_user.py b/bumper/plugins/bumper_confserver_v2_private_user.py index 981fb03..f076c7c 100644 --- a/bumper/plugins/bumper_confserver_v2_private_user.py +++ b/bumper/plugins/bumper_confserver_v2_private_user.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import asyncio -from aiohttp import web -from bumper import plugins import logging -import bumper -from bumper.models import * -from bumper import plugins from datetime import datetime, timedelta +from aiohttp import web + +import bumper +from bumper import plugins +from bumper.models import * + class v2_private_user(plugins.ConfServerApp): def __init__(self): diff --git a/bumper/util.py b/bumper/util.py index ce1359b..3ab30ab 100644 --- a/bumper/util.py +++ b/bumper/util.py @@ -2,7 +2,6 @@ import logging import os import sys from logging.handlers import RotatingFileHandler - from typing import MutableMapping logformat = logging.Formatter( diff --git a/bumper/xmppserver.py b/bumper/xmppserver.py index 8dbd5cc..72a386e 100644 --- a/bumper/xmppserver.py +++ b/bumper/xmppserver.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 +import asyncio +import base64 import logging import re +import ssl import uuid import xml.etree.ElementTree as ET -import base64 -import ssl -import bumper -import asyncio +import bumper from bumper import get_logger xmppserverlog = get_logger("xmppserver") diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..67524cb --- /dev/null +++ b/setup.cfg @@ -0,0 +1,25 @@ +[flake8] +# To work with Black +max-line-length = 88 +# E501: line too long +# W503: Line break occurred before a binary operator +# E203: Whitespace before ':' +# D202 No blank lines allowed after function docstring +# D105 Missing docstring in magic method +# D107 Missing docstring in __init__ +ignore = + E501, + W503, + E203, + D202, + D105, + D107 + +# Disable unused imports for __init__.py +per-file-ignores = + */__init__.py: F401 + +[isort] +# https://github.com/timothycrosley/isort +# https://github.com/timothycrosley/isort/wiki/isort-Settings +profile = black \ No newline at end of file diff --git a/tests/test_confserver.py b/tests/test_confserver.py index 9673618..b53cdbb 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -1,18 +1,20 @@ -from unittest import mock -import bumper import asyncio -import pytest -import os +import datetime import json -import tinydb -import pytest_aiohttp -import pytest_asyncio -import datetime, time -from aiohttp import web import logging -from testfixtures import LogCapture +import os +import time +from unittest import mock from unittest.mock import MagicMock +import pytest +import pytest_aiohttp +import pytest_asyncio +import tinydb +from aiohttp import web +from testfixtures import LogCapture + +import bumper from tests.const import HOST, MQTT_PORT diff --git a/tests/test_db.py b/tests/test_db.py index 2f306c5..2d28eb4 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 -import bumper -from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts -from tinydb import TinyDB, Query -from tinydb.storages import MemoryStorage -from datetime import datetime, timedelta -import os import json import logging +import os +from datetime import datetime, timedelta + +from tinydb import Query, TinyDB +from tinydb.storages import MemoryStorage + +import bumper +from bumper.models import BumperUser, EcoVacsHomeProducts, VacBotClient, VacBotDevice def test_db_path(): diff --git a/tests/test_init.py b/tests/test_init.py index e8e18a5..044fe2b 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,14 +1,16 @@ -from unittest import mock -from unittest.mock import patch -import pytest -from tinydb.storages import MemoryStorage -from tinydb import TinyDB, Query -import bumper +import asyncio +import json import os import platform -import json -import asyncio +from unittest import mock +from unittest.mock import patch + +import pytest from testfixtures import LogCapture +from tinydb import Query, TinyDB +from tinydb.storages import MemoryStorage + +import bumper def test_strtobool(): diff --git a/tests/test_mqttserver.py b/tests/test_mqttserver.py index 87eab72..7135773 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -1,17 +1,18 @@ -from unittest import mock -import bumper import asyncio -import pytest -import os import json -import tinydb -import pytest_asyncio -import xml.etree.ElementTree as ET -import hbmqtt import logging -from testfixtures import LogCapture +import os import time +import xml.etree.ElementTree as ET +from unittest import mock +import hbmqtt +import pytest +import pytest_asyncio +import tinydb +from testfixtures import LogCapture + +import bumper from tests.const import HOST, MQTT_PORT diff --git a/tests/test_xmppserver.py b/tests/test_xmppserver.py index 8be9144..c6cf681 100644 --- a/tests/test_xmppserver.py +++ b/tests/test_xmppserver.py @@ -1,15 +1,17 @@ -from unittest import mock -import bumper import asyncio -import pytest -import os import json -import tinydb -import pytest_asyncio -import xml.etree.ElementTree as ET +import os import socket -from testfixtures import LogCapture import ssl +import xml.etree.ElementTree as ET +from unittest import mock + +import pytest +import pytest_asyncio +import tinydb +from testfixtures import LogCapture + +import bumper def return_send_data(data, *args, **kwargs): From ac33a522f9c5e1e4f5c7273d25f63e568d73fd9e Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 13:16:42 +0100 Subject: [PATCH 09/10] add test timeout --- pytest.ini | 3 ++- requirements-test.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index fe2b891..26a6fdf 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,5 +4,6 @@ env = D:BUMPER_CERT=tests/test_certs/bumper.crt D:BUMPER_KEY=tests/test_certs/bumper.key -#log_cli=true +timeout = 60 +log_cli=true #log_level=DEBUG \ No newline at end of file diff --git a/requirements-test.txt b/requirements-test.txt index e5ef785..af06dc5 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -8,6 +8,7 @@ pytest-asyncio pytest-aiohttp testfixtures pytest-env +pytest-timeout #pbr = "*" #pytest-aiohttp = "*" From 8642990b6efac631c7658d6266faea1e13a0e1fd Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 30 Jan 2022 14:52:08 +0100 Subject: [PATCH 10/10] use asyncio_mode = auto and fixtures for mqtt and conf server --- pytest.ini | 5 +- requirements-test.txt | 17 +-- tests/__init__.py | 5 + tests/conftest.py | 27 +++++ tests/const.py | 2 - tests/test_confserver.py | 241 +++++++++++++++------------------------ tests/test_mqttserver.py | 210 ++++++++++++++++------------------ 7 files changed, 230 insertions(+), 277 deletions(-) create mode 100644 tests/conftest.py delete mode 100644 tests/const.py diff --git a/pytest.ini b/pytest.ini index 26a6fdf..16b0152 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,6 +4,7 @@ env = D:BUMPER_CERT=tests/test_certs/bumper.crt D:BUMPER_KEY=tests/test_certs/bumper.key -timeout = 60 -log_cli=true +asyncio_mode = auto +timeout = 10 +#log_cli=true #log_level=DEBUG \ No newline at end of file diff --git a/requirements-test.txt b/requirements-test.txt index af06dc5..5376f69 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -4,18 +4,11 @@ pylint==2.12.2 pytest==6.2.5 pytest-cov==3.0.0 types-cachetools==4.2.9 -pytest-asyncio -pytest-aiohttp -testfixtures -pytest-env -pytest-timeout +pytest-asyncio==0.17.2 +pytest-aiohttp==1.0.3 +testfixtures==6.18.3 +pytest-env==0.6.2 +pytest-timeout==2.1.0 #pbr = "*" -#pytest-aiohttp = "*" -#pytest-cov = "*" -#testfixtures = "*" -#codecov = "*" #autoflake = "*" -#pytest-env = "*" -#pytest = "*" -#pytest-asyncio = "*" \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..9959d47 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,5 @@ + +HOST = "127.0.0.1" +MQTT_PORT = 8883 + + diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..21a09f1 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,27 @@ +import pytest + +import bumper +from tests import HOST, MQTT_PORT + + +@pytest.fixture +async def mqtt_server(): + mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") + await mqtt_server.broker_coro() + bumper.mqtt_server = mqtt_server + + yield + + await mqtt_server.broker.shutdown() + + +@pytest.fixture +async def conf_server_client(aiohttp_client): + confserver = bumper.ConfServer("127.0.0.1:11111", False) + confserver.confserver_app() + + client = await aiohttp_client(confserver.app) + + yield client + + await client.close() \ No newline at end of file diff --git a/tests/const.py b/tests/const.py deleted file mode 100644 index 143d88a..0000000 --- a/tests/const.py +++ /dev/null @@ -1,2 +0,0 @@ -HOST = "127.0.0.1" -MQTT_PORT = 8883 diff --git a/tests/test_confserver.py b/tests/test_confserver.py index b53cdbb..7dfe287 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -1,33 +1,21 @@ import asyncio import datetime import json -import logging import os -import time from unittest import mock -from unittest.mock import MagicMock import pytest -import pytest_aiohttp -import pytest_asyncio -import tinydb from aiohttp import web from testfixtures import LogCapture import bumper -from tests.const import HOST, MQTT_PORT +from tests import HOST, MQTT_PORT def create_confserver(): return bumper.ConfServer("127.0.0.1:11111", False) -def create_app(loop): - confserver = bumper.ConfServer("127.0.0.1:11111", False) - confserver.confserver_app() - return confserver.app - - def async_return(result): f = asyncio.Future() f.set_result(result) @@ -98,15 +86,11 @@ def test_get_milli_time(): ) -async def test_base(aiohttp_client): +@pytest.mark.usefixtures("mqtt_server") +async def test_base(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - # Start MQTT - mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") - bumper.mqtt_server = mqtt_server - await mqtt_server.broker_coro() - # Start XMPP xmpp_address = (HOST, 5223) xmpp_server = bumper.XMPPServer(xmpp_address) @@ -118,26 +102,19 @@ async def test_base(aiohttp_client): bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() - client = await aiohttp_client(create_app) - resp = await client.get("/") + resp = await conf_server_client.get("/") assert resp.status == 200 mqtt_helperbot.Client.disconnect() - await mqtt_server.broker.shutdown() - bumper.xmpp_server.disconnect() -async def test_restartService(aiohttp_client): +@pytest.mark.usefixtures("mqtt_server") +async def test_restartService(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - # Start MQTT - mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") - bumper.mqtt_server = mqtt_server - await mqtt_server.broker_coro() - # Start XMPP xmpp_address = (HOST, 5223) xmpp_server = bumper.XMPPServer(xmpp_address) @@ -149,42 +126,36 @@ async def test_restartService(aiohttp_client): bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() - client = await aiohttp_client(create_app) - - resp = await client.get("/restart_Helperbot") + resp = await conf_server_client.get("/restart_Helperbot") assert resp.status == 200 - resp = await client.get("/restart_MQTTServer") + resp = await conf_server_client.get("/restart_MQTTServer") assert resp.status == 200 - resp = await client.get("/restart_XMPPServer") + resp = await conf_server_client.get("/restart_XMPPServer") assert resp.status == 200 mqtt_helperbot.Client.disconnect() - await mqtt_server.broker.shutdown() xmpp_server.disconnect() -async def test_RemoveBot(aiohttp_client): - client = await aiohttp_client(create_app) - resp = await client.get("/bot/remove/test_did") +async def test_RemoveBot(conf_server_client): + resp = await conf_server_client.get("/bot/remove/test_did") assert resp.status == 200 -async def test_RemoveClient(aiohttp_client): - client = await aiohttp_client(create_app) - resp = await client.get("/client/remove/test_resource") +async def test_RemoveClient(conf_server_client): + resp = await conf_server_client.get("/client/remove/test_resource") assert resp.status == 200 -async def test_login(aiohttp_client): +async def test_login(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) # Test without user - resp = await client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/login") + resp = await conf_server_client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/login") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -197,7 +168,7 @@ async def test_login(aiohttp_client): bumper.db = "tests/tmp.db" # Set db location for testing # Test global_e without user - resp = await client.get("/v1/private/us/en/dev_1234/global_e/1/0/0/user/login") + resp = await conf_server_client.get("/v1/private/us/en/dev_1234/global_e/1/0/0/user/login") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -208,7 +179,7 @@ async def test_login(aiohttp_client): # Add a user to db and test with existing users bumper.user_add("testuser") - resp = await client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/login") + resp = await conf_server_client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/login") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -219,7 +190,7 @@ async def test_login(aiohttp_client): # Add a bot to db that will be added to user bumper.bot_add("sn_123", "did_123", "dev_123", "res_123", "com_123") - resp = await client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/login") + resp = await conf_server_client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/login") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -238,7 +209,7 @@ async def test_login(aiohttp_client): } bumper.bot_full_upsert(newbot) - resp = await client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/login") + resp = await conf_server_client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/login") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -248,16 +219,15 @@ async def test_login(aiohttp_client): assert "username" in jsonresp["data"] -async def test_logout(aiohttp_client): +async def test_logout(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) # Add a token to user and test bumper.user_add("testuser") bumper.user_add_device("testuser", "dev_1234") bumper.user_add_token("testuser", "token_1234") - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/ios/1/0/0/user/logout?accessToken={}".format( "token_1234" ) @@ -269,13 +239,12 @@ async def test_logout(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_checkLogin(aiohttp_client): +async def test_checkLogin(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) # Test without token - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/ios/1/0/0/user/checkLogin?accessToken={}".format( None ) @@ -291,7 +260,7 @@ async def test_checkLogin(aiohttp_client): # Add a user to db and test with existing users bumper.user_add("testuser") - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/ios/1/0/0/user/checkLogin?accessToken={}".format( None ) @@ -307,7 +276,7 @@ async def test_checkLogin(aiohttp_client): # Test again using global_e app bumper.user_add("testuser") - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/user/checkLogin?accessToken={}".format( None ) @@ -328,7 +297,7 @@ async def test_checkLogin(aiohttp_client): bumper.user_add("testuser") bumper.user_add_device("testuser", "dev_1234") bumper.user_add_token("testuser", "token_1234") - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/ios/1/0/0/user/checkLogin?accessToken={}".format( "token_1234" ) @@ -344,7 +313,7 @@ async def test_checkLogin(aiohttp_client): # Test again using global_e app bumper.user_add("testuser") - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/user/checkLogin?accessToken={}".format( "token_1234" ) @@ -359,13 +328,12 @@ async def test_checkLogin(aiohttp_client): assert "username" in jsonresp["data"] -async def test_getAuthCode(aiohttp_client): +async def test_getAuthCode(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) # Test without user or token - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/ios/1/0/0/user/getAuthCode?uid={}&accessToken={}".format( None, None ) @@ -376,7 +344,7 @@ async def test_getAuthCode(aiohttp_client): assert jsonresp["code"] == bumper.ERR_TOKEN_INVALID # Test as global_e - resp = await client.get( + resp = await conf_server_client.get( "/v1/global/auth/getAuthCode?uid={}&deviceId={}".format(None, "dev_1234") ) assert resp.status == 200 @@ -388,7 +356,7 @@ async def test_getAuthCode(aiohttp_client): bumper.user_add("testuser") bumper.user_add_device("testuser", "dev_1234") bumper.user_add_token("testuser", "token_1234") - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/ios/1/0/0/user/getAuthCode?uid={}&accessToken={}".format( "testuser", "token_1234" ) @@ -401,7 +369,7 @@ async def test_getAuthCode(aiohttp_client): assert "ecovacsUid" in jsonresp["data"] # The above should have added an authcode to token, try again to test with existing authcode - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/ios/1/0/0/user/getAuthCode?uid={}&accessToken={}".format( "testuser", "token_1234" ) @@ -414,19 +382,18 @@ async def test_getAuthCode(aiohttp_client): assert "ecovacsUid" in jsonresp["data"] -async def test_checkAgreement(aiohttp_client): +async def test_checkAgreement(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/checkAgreement") + resp = await conf_server_client.get("/v1/private/us/en/dev_1234/ios/1/0/0/user/checkAgreement") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) assert jsonresp["code"] == bumper.RETURN_API_SUCCESS # Test as global_e - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/user/checkAgreement" ) assert resp.status == 200 @@ -435,12 +402,11 @@ async def test_checkAgreement(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_homePageAlert(aiohttp_client): +async def test_homePageAlert(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/ios/1/0/0/campaign/homePageAlert" ) assert resp.status == 200 @@ -449,24 +415,22 @@ async def test_homePageAlert(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_checkVersion(aiohttp_client): +async def test_checkVersion(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get("/v1/private/us/en/dev_1234/ios/1/0/0/common/checkVersion") + resp = await conf_server_client.get("/v1/private/us/en/dev_1234/ios/1/0/0/common/checkVersion") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_checkAppVersion(aiohttp_client): +async def test_checkAppVersion(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/common/checkAPPVersion" ) assert resp.status == 200 @@ -475,12 +439,10 @@ async def test_checkAppVersion(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_uploadDeviceInfo(aiohttp_client): +async def test_uploadDeviceInfo(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/common/uploadDeviceInfo" ) assert resp.status == 200 @@ -489,12 +451,11 @@ async def test_uploadDeviceInfo(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_getAdByPositionType(aiohttp_client): +async def test_getAdByPositionType(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/ad/getAdByPositionType" ) assert resp.status == 200 @@ -503,12 +464,11 @@ async def test_getAdByPositionType(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_getBootScreen(aiohttp_client): +async def test_getBootScreen(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/ad/getBootScreen" ) assert resp.status == 200 @@ -517,12 +477,11 @@ async def test_getBootScreen(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_hasUnreadMsg(aiohttp_client): +async def test_hasUnreadMsg(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/message/hasUnreadMsg" ) assert resp.status == 200 @@ -531,12 +490,11 @@ async def test_hasUnreadMsg(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_getMsgList(aiohttp_client): +async def test_getMsgList(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/message/getMsgList" ) assert resp.status == 200 @@ -545,12 +503,11 @@ async def test_getMsgList(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_getSystemReminder(aiohttp_client): +async def test_getSystemReminder(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/common/getSystemReminder" ) assert resp.status == 200 @@ -559,12 +516,11 @@ async def test_getSystemReminder(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_getCnWapShopConfig(aiohttp_client): +async def test_getCnWapShopConfig(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/shop/getCnWapShopConfig" ) assert resp.status == 200 @@ -573,10 +529,9 @@ async def test_getCnWapShopConfig(aiohttp_client): assert jsonresp["code"] == bumper.RETURN_API_SUCCESS -async def test_neng_hasUnreadMessage(aiohttp_client): +async def test_neng_hasUnreadMessage(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) postbody = { "auth": { @@ -588,42 +543,40 @@ async def test_neng_hasUnreadMessage(aiohttp_client): }, "count": 20, } - resp = await client.post("/api/neng/message/hasUnreadMsg", json=postbody) + resp = await conf_server_client.post("/api/neng/message/hasUnreadMsg", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) assert jsonresp["code"] == 0 -async def test_getProductIotMap(aiohttp_client): +async def test_getProductIotMap(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.post("/api/pim/product/getProductIotMap") + resp = await conf_server_client.post("/api/pim/product/getProductIotMap") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) assert jsonresp["code"] == bumper.RETURN_API_SUCCESS # Test getPimFile - resp = await client.get("/api/pim/file/get/123") + resp = await conf_server_client.get("/api/pim/file/get/123") assert resp.status == 200 -async def test_getUsersAPI(aiohttp_client): +async def test_getUsersAPI(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) - resp = await client.get("/api/users/user.do") + resp = await conf_server_client.get("/api/users/user.do") assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) assert jsonresp["result"] == "fail" -async def test_getUserAccountInfo(aiohttp_client): +async def test_getUserAccountInfo(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing bumper.user_add("testuser") @@ -633,9 +586,7 @@ async def test_getUserAccountInfo(aiohttp_client): bumper.user_add_bot("testuser", "did_1234") bumper.bot_add("sn_1234", "did_1234", "class_1234", "res_1234", "com_1234") - client = await aiohttp_client(create_app) - - resp = await client.get( + resp = await conf_server_client.get( "/v1/private/us/en/dev_1234/global_e/1/0/0/user/getUserAccountInfo" ) assert resp.status == 200 @@ -646,14 +597,13 @@ async def test_getUserAccountInfo(aiohttp_client): assert jsonresp["data"]["userName"] == "fusername_testuser" -async def test_postUsersAPI(aiohttp_client): +async def test_postUsersAPI(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) # Test FindBest postbody = {"todo": "FindBest", "service": "EcoMsgNew"} - resp = await client.post("/api/users/user.do", json=postbody) + resp = await conf_server_client.post("/api/users/user.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -661,7 +611,7 @@ async def test_postUsersAPI(aiohttp_client): # Test EcoUpdate postbody = {"todo": "FindBest", "service": "EcoUpdate"} - resp = await client.post("/api/users/user.do", json=postbody) + resp = await conf_server_client.post("/api/users/user.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -684,7 +634,7 @@ async def test_postUsersAPI(aiohttp_client): "token": "auth_1234", "userId": "testuser", } - resp = await client.post("/api/users/user.do", json=postbody) + resp = await conf_server_client.post("/api/users/user.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -700,7 +650,7 @@ async def test_postUsersAPI(aiohttp_client): "todo": "loginByItToken", "token": "auth_1234", } - resp = await client.post("/api/users/user.do", json=postbody) + resp = await conf_server_client.post("/api/users/user.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -716,7 +666,7 @@ async def test_postUsersAPI(aiohttp_client): "todo": "loginByItToken", "token": "auth_1234", } - resp = await client.post("/api/users/user.do", data=postbody) + resp = await conf_server_client.post("/api/users/user.do", data=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -734,7 +684,7 @@ async def test_postUsersAPI(aiohttp_client): "todo": "GetDeviceList", "userid": "testuser", } - resp = await client.post("/api/users/user.do", json=postbody) + resp = await conf_server_client.post("/api/users/user.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -753,7 +703,7 @@ async def test_postUsersAPI(aiohttp_client): "nick": "botnick", "did": "did_1234", } - resp = await client.post("/api/users/user.do", json=postbody) + resp = await conf_server_client.post("/api/users/user.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -772,7 +722,7 @@ async def test_postUsersAPI(aiohttp_client): "nick": "botnick", "did": "did_1234", } - resp = await client.post("/api/users/user.do", json=postbody) + resp = await conf_server_client.post("/api/users/user.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -790,17 +740,16 @@ async def test_postUsersAPI(aiohttp_client): "todo": "DeleteOneDevice", "did": "did_1234", } - resp = await client.post("/api/users/user.do", json=postbody) + resp = await conf_server_client.post("/api/users/user.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) assert jsonresp["result"] == "ok" -async def test_appsvr_api(aiohttp_client): +async def test_appsvr_api(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) # Test GetGlobalDeviceList postbody = { @@ -820,7 +769,7 @@ async def test_appsvr_api(aiohttp_client): "todo": "GetGlobalDeviceList", "userid": "testuser", } - resp = await client.post("/api/appsvr/app.do", json=postbody) + resp = await conf_server_client.post("/api/appsvr/app.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) @@ -829,20 +778,19 @@ async def test_appsvr_api(aiohttp_client): bumper.bot_add("sn_1234", "did_1234", "ls1ok3", "res_1234", "eco-ng") # Test again with bot added - resp = await client.post("/api/appsvr/app.do", json=postbody) + resp = await conf_server_client.post("/api/appsvr/app.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) assert jsonresp["ret"] == "ok" -async def test_lg_logs(aiohttp_client): +async def test_lg_logs(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing bumper.bot_add("sn_1234", "did_1234", "ls1ok3", "res_1234", "eco-ng") bumper.bot_set_mqtt("did_1234", True) confserver = create_confserver() - client = await aiohttp_client(create_app) bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot(HOST, MQTT_PORT) # Test return get status @@ -868,21 +816,20 @@ async def test_lg_logs(aiohttp_client): "resource": "res_1234", "td": "GetCleanLogs", } - resp = await client.post("/api/lg/log.do", json=postbody) + resp = await conf_server_client.post("/api/lg/log.do", json=postbody) assert resp.status == 200 text = await resp.text() jsonresp = json.loads(text) assert jsonresp["ret"] == "ok" -async def test_postLookup(aiohttp_client): +async def test_postLookup(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing - client = await aiohttp_client(create_app) # Test FindBest postbody = {"todo": "FindBest", "service": "EcoMsgNew"} - resp = await client.post("/lookup.do", json=postbody) + resp = await conf_server_client.post("/lookup.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) @@ -890,23 +837,22 @@ async def test_postLookup(aiohttp_client): # Test EcoUpdate postbody = {"todo": "FindBest", "service": "EcoUpdate"} - resp = await client.post("/lookup.do", json=postbody) + resp = await conf_server_client.post("/lookup.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) assert test_resp["result"] == "ok" -async def test_devmgr(aiohttp_client): +async def test_devmgr(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing confserver = create_confserver() - client = await aiohttp_client(create_app) bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot(HOST, MQTT_PORT) # Test PollSCResult postbody = {"td": "PollSCResult"} - resp = await client.post("/api/iot/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/iot/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) @@ -914,7 +860,7 @@ async def test_devmgr(aiohttp_client): # Test HasUnreadMsg postbody = {"td": "HasUnreadMsg"} - resp = await client.post("/api/iot/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/iot/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) @@ -935,7 +881,7 @@ async def test_devmgr(aiohttp_client): bumper.mqtt_helperbot.send_command = mock.MagicMock( return_value=async_return(command_getstatus_resp) ) - resp = await client.post("/api/iot/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/iot/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) @@ -946,23 +892,22 @@ async def test_devmgr(aiohttp_client): bumper.mqtt_helperbot.send_command = mock.MagicMock( return_value=async_return(command_timeout_resp) ) - resp = await client.post("/api/iot/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/iot/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) assert test_resp["ret"] == "fail" -async def test_dim_devmanager(aiohttp_client): +async def test_dim_devmanager(conf_server_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing confserver = create_confserver() - client = await aiohttp_client(create_app) bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot(HOST, MQTT_PORT) # Test PollSCResult postbody = {"td": "PollSCResult"} - resp = await client.post("/api/dim/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/dim/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) @@ -970,7 +915,7 @@ async def test_dim_devmanager(aiohttp_client): # Test HasUnreadMsg postbody = {"td": "HasUnreadMsg"} - resp = await client.post("/api/dim/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/dim/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) @@ -991,7 +936,7 @@ async def test_dim_devmanager(aiohttp_client): bumper.mqtt_helperbot.send_command = mock.MagicMock( return_value=async_return(command_getstatus_resp) ) - resp = await client.post("/api/dim/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/dim/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) @@ -1002,7 +947,7 @@ async def test_dim_devmanager(aiohttp_client): bumper.mqtt_helperbot.send_command = mock.MagicMock( return_value=async_return(command_timeout_resp) ) - resp = await client.post("/api/dim/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/dim/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) @@ -1014,7 +959,7 @@ async def test_dim_devmanager(aiohttp_client): bumper.mqtt_helperbot.send_command = mock.MagicMock( return_value=async_return(command_getstatus_resp) ) - resp = await client.post("/api/dim/devmanager.do", json=postbody) + resp = await conf_server_client.post("/api/dim/devmanager.do", json=postbody) assert resp.status == 200 text = await resp.text() test_resp = json.loads(text) diff --git a/tests/test_mqttserver.py b/tests/test_mqttserver.py index 7135773..5157d3c 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -1,25 +1,17 @@ import asyncio -import json -import logging import os import time -import xml.etree.ElementTree as ET -from unittest import mock import hbmqtt import pytest -import pytest_asyncio -import tinydb from testfixtures import LogCapture import bumper -from tests.const import HOST, MQTT_PORT +from tests import HOST, MQTT_PORT +@pytest.mark.usefixtures("mqtt_server") async def test_helperbot_message(): - mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") - await mqtt_server.broker_coro() - with LogCapture() as l: # Test broadcast message @@ -142,13 +134,9 @@ async def test_helperbot_message(): l.clear() mqtt_helperbot.Client.disconnect() - await mqtt_server.broker.shutdown() - +@pytest.mark.usefixtures("mqtt_server") async def test_helperbot_expire_message(): - mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") - await mqtt_server.broker_coro() - timeout = 0.1 # Test broadcast message mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT, timeout) @@ -184,13 +172,10 @@ async def test_helperbot_expire_message(): assert mqtt_helperbot.commands.get(request_id, None) == None await mqtt_helperbot.Client.disconnect() - await mqtt_server.broker.shutdown() +@pytest.mark.usefixtures("mqtt_server") async def test_helperbot_sendcommand(): - mqtt_server = bumper.MQTTServer(HOST, MQTT_PORT, password_file="tests/passwd") - await mqtt_server.broker_coro() - timeout = 0.1 mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT, timeout) bumper.mqtt_helperbot = mqtt_helperbot @@ -332,8 +317,6 @@ async def test_helperbot_sendcommand(): mqtt_helperbot.Client.disconnect() - await mqtt_server.broker.shutdown() - async def test_mqttserver(): if os.path.exists("tests/tmp.db"): @@ -347,107 +330,108 @@ async def test_mqttserver(): await mqtt_server.broker_coro() - # Test helperbot connect - mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) - await mqtt_helperbot.start_helper_bot() - assert ( - mqtt_helperbot.Client._connected_state._value == True - ) # Check helperbot is connected - await mqtt_helperbot.Client.disconnect() + try: + # Test helperbot connect + mqtt_helperbot = bumper.MQTTHelperBot(HOST, MQTT_PORT) + await mqtt_helperbot.start_helper_bot() + assert ( + mqtt_helperbot.Client._connected_state._value == True + ) # Check helperbot is connected + await mqtt_helperbot.Client.disconnect() - # Test client connect - bumper.user_add("user_123") # Add user to db - bumper.client_add("user_123", "ecouser.net", "resource_123") # Add client to db - test_client = bumper.MQTTHelperBot(HOST, MQTT_PORT) - test_client.client_id = "user_123@ecouser.net/resource_123" - # await test_client.start_helper_bot() - test_client.Client = hbmqtt.client.MQTTClient( - client_id=test_client.client_id, config={"check_hostname": False} - ) - - await test_client.Client.connect( - f"mqtts://{HOST}:{MQTT_PORT}/", - cafile=bumper.ca_cert, - ) - assert ( - test_client.Client._connected_state._value == True - ) # Check client is connected - await test_client.Client.disconnect() - assert ( - test_client.Client._connected_state._value == False - ) # Check client is disconnected - - # Test fake_bot connect - fake_bot = bumper.MQTTHelperBot(HOST, MQTT_PORT) - fake_bot.client_id = "bot_serial@ls1ok3/wC3g" - await fake_bot.start_helper_bot() - assert ( - fake_bot.Client._connected_state._value == True - ) # Check fake_bot is connected - await fake_bot.Client.disconnect() - - # Test file auth client connect - test_client = bumper.MQTTHelperBot(HOST, MQTT_PORT) - test_client.client_id = "test-file-auth" - # await test_client.start_helper_bot() - test_client.Client = hbmqtt.client.MQTTClient( - client_id=test_client.client_id, - config={ - "check_hostname": False, - "auto_reconnect": False, - "reconnect_retries": 1, - }, - ) - - # good user/pass - await test_client.Client.connect( - f"mqtts://test-client:abc123!@{HOST}:{MQTT_PORT}/", - cafile=bumper.ca_cert, - cleansession=True, - ) - - assert ( - test_client.Client._connected_state._value == True - ) # Check client is connected - await test_client.Client.disconnect() - assert ( - test_client.Client._connected_state._value == False - ) # Check client is disconnected - - # bad password - with LogCapture() as l: + # Test client connect + bumper.user_add("user_123") # Add user to db + bumper.client_add("user_123", "ecouser.net", "resource_123") # Add client to db + test_client = bumper.MQTTHelperBot(HOST, MQTT_PORT) + test_client.client_id = "user_123@ecouser.net/resource_123" + # await test_client.start_helper_bot() + test_client.Client = hbmqtt.client.MQTTClient( + client_id=test_client.client_id, config={"check_hostname": False} + ) await test_client.Client.connect( - f"mqtts://test-client:notvalid!@{HOST}:{MQTT_PORT}/", + f"mqtts://{HOST}:{MQTT_PORT}/", + cafile=bumper.ca_cert, + ) + assert ( + test_client.Client._connected_state._value == True + ) # Check client is connected + await test_client.Client.disconnect() + assert ( + test_client.Client._connected_state._value == False + ) # Check client is disconnected + + # Test fake_bot connect + fake_bot = bumper.MQTTHelperBot(HOST, MQTT_PORT) + fake_bot.client_id = "bot_serial@ls1ok3/wC3g" + await fake_bot.start_helper_bot() + assert ( + fake_bot.Client._connected_state._value == True + ) # Check fake_bot is connected + await fake_bot.Client.disconnect() + + # Test file auth client connect + test_client = bumper.MQTTHelperBot(HOST, MQTT_PORT) + test_client.client_id = "test-file-auth" + # await test_client.start_helper_bot() + test_client.Client = hbmqtt.client.MQTTClient( + client_id=test_client.client_id, + config={ + "check_hostname": False, + "auto_reconnect": False, + "reconnect_retries": 1, + }, + ) + + # good user/pass + await test_client.Client.connect( + f"mqtts://test-client:abc123!@{HOST}:{MQTT_PORT}/", cafile=bumper.ca_cert, cleansession=True, ) - l.check_present( - ( - "mqttserver", - "INFO", - "File Authentication Failed - Username: test-client - ClientID: test-file-auth", - ), - order_matters=False, - ) - # no username in file - await test_client.Client.connect( - f"mqtts://test-client-noexist:notvalid!@{HOST}:{MQTT_PORT}/", - cafile=bumper.ca_cert, - cleansession=True, - ) + assert ( + test_client.Client._connected_state._value == True + ) # Check client is connected + await test_client.Client.disconnect() + assert ( + test_client.Client._connected_state._value == False + ) # Check client is disconnected - l.check_present( - ( - "mqttserver", - "INFO", - "File Authentication Failed - No Entry for Username: test-client-noexist - ClientID: test-file-auth", - ), - order_matters=False, - ) + # bad password + with LogCapture() as l: - await mqtt_server.broker.shutdown() + await test_client.Client.connect( + f"mqtts://test-client:notvalid!@{HOST}:{MQTT_PORT}/", + cafile=bumper.ca_cert, + cleansession=True, + ) + + l.check_present( + ( + "mqttserver", + "INFO", + "File Authentication Failed - Username: test-client - ClientID: test-file-auth", + ), + order_matters=False, + ) + # no username in file + await test_client.Client.connect( + f"mqtts://test-client-noexist:notvalid!@{HOST}:{MQTT_PORT}/", + cafile=bumper.ca_cert, + cleansession=True, + ) + + l.check_present( + ( + "mqttserver", + "INFO", + "File Authentication Failed - No Entry for Username: test-client-noexist - ClientID: test-file-auth", + ), + order_matters=False, + ) + finally: + await mqtt_server.broker.shutdown() async def test_nofileauth_mqttserver():