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/.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 diff --git a/.gitignore b/.gitignore index 31682cc..19b97b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,17 @@ # 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/* +tests/logs +tests/tmp.db !tests/test* !tests/test_certs -!tests/pytest.ini +!pytest.ini !tests/passwd !tests/passwd_bad diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4d8539d --- /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: + - -L bumper + - --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/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/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/bumper/__init__.py b/bumper/__init__.py index 1ec0c42..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 @@ -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: @@ -183,7 +189,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") @@ -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 02f595a..b5859b3 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -11,8 +11,10 @@ from aiohttp import web 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 +23,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 +49,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,16 +132,14 @@ class ConfServer: ) else: - site = web.TCPSite( - runner, host=address, port=port - ) + site = web.TCPSite(runner, host=address, port=port) await site.start() 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 +169,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 +177,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: @@ -178,12 +190,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,14 +212,14 @@ 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: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") @web.middleware async def log_all_requests(self, request, handler): @@ -231,9 +245,9 @@ 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: postbody = await request.post() @@ -252,18 +266,18 @@ class ConfServer: to_log["response"]["body"] = f"{json.loads(response.body)}" confserverlog.debug(json.dumps(to_log)) - + 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 + return e else: return await handler(request) @@ -274,24 +288,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 +326,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() @@ -319,7 +335,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,30 +348,28 @@ class ConfServer: return web.json_response({"status": "successfully removed bot"}) except Exception as e: - confserverlog.exception("{}".format(e)) - pass + confserverlog.exception(f"{e}") + 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("{}".format(e)) - pass + confserverlog.exception(f"{e}") + 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( - "client with devid {} attempting login".format(user_devid) - ) + confserverlog.info(f"client with devid {user_devid} attempting login") if bumper.use_auth: if ( not user_devid == "" @@ -417,7 +431,7 @@ class ConfServer: ) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def handle_lookup(self, request): try: @@ -464,7 +478,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 @@ -476,16 +490,12 @@ 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) except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") async def disconnect(self): try: @@ -493,20 +503,22 @@ class ConfServer: await self.app.shutdown() except Exception as e: - confserverlog.exception("{}".format(e)) + confserverlog.exception(f"{e}") class ConfServer_GeneralFunctions: def __init__(self): 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 @@ -514,89 +526,89 @@ 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): - try: - user_devid = request.match_info.get("devid", "") - countrycode = request.match_info.get("country", "us") - apptype = request.match_info.get("apptype", "") - confserverlog.info( - "client with devid {} attempting login".format(user_devid) - ) - 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("{}".format(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 +672,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 +717,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: @@ -719,7 +731,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 +750,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() @@ -752,9 +768,11 @@ 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 + 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 +800,7 @@ class ConfServer: return body except Exception as e: - confserverlog.exception("{}".format(e)) - + confserverlog.exception(f"{e}") def getUserAccountInfo(self, request): try: @@ -851,7 +868,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: @@ -859,7 +876,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 +894,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}") diff --git a/bumper/db.py b/bumper/db.py index 8b930f9..d22cd1d 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -4,11 +4,18 @@ import logging import os from datetime import datetime, timedelta -from tinydb import TinyDB, Query +from tinydb import Query, TinyDB import bumper +from bumper.models import ( + BumperUser, + EcoVacsHomeProducts, + OAuth, + VacBotClient, + VacBotDevice, +) + from .util import get_logger -from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts, OAuth bumperlog = get_logger("bumper") @@ -44,7 +51,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 +129,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 +143,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 +221,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 +235,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 +250,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 +267,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 == uid.replace("fuid_", "")) + | (Query().userid == f"fuid_{uid}") ) # Userid with or without fuid_ ) if tmpauth: @@ -276,10 +283,11 @@ 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 +300,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 == uid.replace("fuid_", "")) + | (Query().userid == f"fuid_{uid}") ) # Userid with or without fuid_ ) if tmpauth: @@ -326,11 +334,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( - "Adding new bot with SN: {} DID: {}".format(newbot.name, newbot.did) - ) + bumperlog.info(f"Adding new bot with SN: {newbot.name} DID: {newbot.did}") bot_full_upsert(newbot.asdict()) @@ -357,7 +363,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 @@ -379,7 +389,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 +418,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..2a79321 100644 --- a/bumper/models.py +++ b/bumper/models.py @@ -6,9 +6,9 @@ from datetime import datetime, timedelta import bumper -class VacBotDevice(object): +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 @@ -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,9 @@ 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 @@ -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 2dc9599..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() @@ -42,26 +41,35 @@ 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: - 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), @@ -70,22 +78,20 @@ 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: - payload = await asyncio.wait_for(command_dto.wait_for_response(), timeout=self.wait_resp_timeout_seconds) - 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: 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 +124,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, @@ -139,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) @@ -152,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") @@ -177,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}, } @@ -185,7 +193,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 +207,7 @@ class MQTTServer: pass except Exception as e: - mqttserverlog.exception("{}".format(e)) + mqttserverlog.exception(f"{e}") # asyncio.create_task(bumper.shutdown()) pass @@ -217,7 +225,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 @@ -231,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( @@ -241,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("/") @@ -251,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( @@ -281,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") @@ -328,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..dcc0fe8 100644 --- a/bumper/plugins.py +++ b/bumper/plugins.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 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 a92ca66..c47b22a 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": "token", + # "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("{}".format(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,20 +191,15 @@ 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) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_appsvr_oauth_callback(self, request): try: @@ -199,13 +209,13 @@ class portal_api_appsvr(plugins.ConfServerApp): "code": 0, "data": oauth.toResponse(), "ret": "ok", - "todo": "result" + "todo": "result", } 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..32da818 100644 --- a/bumper/plugins/bumper_confserver_portal_dim.py +++ b/bumper/plugins/bumper_confserver_portal_dim.py @@ -1,30 +1,35 @@ #!/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): - 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: @@ -42,8 +47,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 @@ -66,11 +71,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("{}".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..ead6976 100644 --- a/bumper/plugins/bumper_confserver_portal_ecms.py +++ b/bumper/plugins/bumper_confserver_portal_ecms.py @@ -8,31 +8,32 @@ 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) 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..330d852 100644 --- a/bumper/plugins/bumper_confserver_portal_iot.py +++ b/bumper/plugins/bumper_confserver_portal_iot.py @@ -1,30 +1,36 @@ #!/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): - 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()) @@ -41,8 +47,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 @@ -70,12 +76,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("{}".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..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)) @@ -64,18 +63,18 @@ 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": 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", } @@ -88,7 +87,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 +98,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..7ffb7a7 100644 --- a/bumper/plugins/bumper_confserver_portal_neng.py +++ b/bumper/plugins/bumper_confserver_portal_neng.py @@ -1,30 +1,45 @@ #!/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): 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: @@ -33,11 +48,11 @@ 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: - body = {"code":0,"data":{"hasNext":False,"msgs":[]}} + body = {"code": 0, "data": {"hasNext": False, "msgs": []}} # share msg response # { @@ -65,33 +80,33 @@ 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: - 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("{}".format(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 2c7bee1..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: @@ -35,16 +66,20 @@ 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: fileID = request.match_info.get("id", "") - return web.FileResponse(os.path.join(bumper.bumper_dir, "bumper", "web", "images", "robotvac_image.jpg")) + return web.FileResponse( + os.path.join( + bumper.bumper_dir, "bumper", "web", "images", "robotvac_image.jpg" + ) + ) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_getConfignetAll(self, request): try: @@ -52,7 +87,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,19 +95,19 @@ 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: 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) except Exception as e: - logging.exception("{}".format(e)) + logging.exception(f"{e}") async def handle_product_config_batch(self, request): try: @@ -86,2626 +121,2290 @@ 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: - logging.exception("{}".format(e)) + logging.exception(f"{e}") 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 ca5ef99..0b0ac3b 100644 --- a/bumper/plugins/bumper_confserver_portal_rapp.py +++ b/bumper/plugins/bumper_confserver_portal_rapp.py @@ -1,39 +1,40 @@ #!/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): - 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) 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..3118abc 100644 --- a/bumper/plugins/bumper_confserver_portal_users.py +++ b/bumper/plugins/bumper_confserver_portal_users.py @@ -1,29 +1,34 @@ #!/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): 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: @@ -110,11 +115,11 @@ 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"} 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 f823ec5..5ab1aae 100644 --- a/bumper/plugins/bumper_confserver_upload_global.py +++ b/bumper/plugins/bumper_confserver_upload_global.py @@ -1,40 +1,47 @@ #!/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): - 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("{}".format(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..3650647 100644 --- a/bumper/plugins/bumper_confserver_v1_global_auth.py +++ b/bumper/plugins/bumper_confserver_v1_global_auth.py @@ -1,28 +1,34 @@ #!/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): 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 2e7df5b..36869e3 100644 --- a/bumper/plugins/bumper_confserver_v1_private_ad.py +++ b/bumper/plugins/bumper_confserver_v1_private_ad.py @@ -1,29 +1,40 @@ #!/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): 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 = { @@ -37,7 +48,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 +63,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..d0ce35d 100644 --- a/bumper/plugins/bumper_confserver_v1_private_campaign.py +++ b/bumper/plugins/bumper_confserver_v1_private_campaign.py @@ -1,29 +1,34 @@ #!/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): 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 +52,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..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: @@ -48,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_checkAPPVersion(self, request): # EcoVacs Home try: @@ -73,7 +113,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 +128,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,16 +150,13 @@ 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: 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, @@ -132,7 +169,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 +184,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: @@ -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, @@ -179,16 +216,14 @@ 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: 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, @@ -197,266 +232,447 @@ 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() -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 7e1d6d1..6bb4eac 100644 --- a/bumper/plugins/bumper_confserver_v1_private_message.py +++ b/bumper/plugins/bumper_confserver_v1_private_message.py @@ -1,30 +1,40 @@ #!/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): 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 = { @@ -38,7 +48,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 +63,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..00693d0 100644 --- a/bumper/plugins/bumper_confserver_v1_private_shop.py +++ b/bumper/plugins/bumper_confserver_v1_private_shop.py @@ -1,29 +1,34 @@ #!/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): 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 +47,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..f4c5cce 100644 --- a/bumper/plugins/bumper_confserver_v1_private_user.py +++ b/bumper/plugins/bumper_confserver_v1_private_user.py @@ -1,43 +1,99 @@ #!/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): 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 +132,67 @@ 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: 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()) - } - - return web.json_response(body) - - except Exception as e: - logging.exception("{}".format(e)) - - async def handle_changeArea(self, request): - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "isNeedReLogin": "N" - }, + "menuPositionKey": "D_FOURTH", + }, + ], "msg": "操作成功", "success": True, "time": self.get_milli_time(datetime.utcnow().timestamp()), @@ -162,7 +201,22 @@ 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: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": {"isNeedReLogin": "N"}, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception(f"{e}") async def handle_acceptAgreementBatch(self, request): try: @@ -177,7 +231,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..37bb39d 100644 --- a/bumper/plugins/bumper_confserver_v1_private_userSetting.py +++ b/bumper/plugins/bumper_confserver_v1_private_userSetting.py @@ -1,24 +1,32 @@ #!/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): - 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 +39,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 +60,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/plugins/bumper_confserver_v2_private_user.py b/bumper/plugins/bumper_confserver_v2_private_user.py index 5a3c7a3..f076c7c 100644 --- a/bumper/plugins/bumper_confserver_v2_private_user.py +++ b/bumper/plugins/bumper_confserver_v2_private_user.py @@ -1,29 +1,35 @@ #!/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): 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..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( @@ -22,7 +21,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 +32,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 eb7aed0..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") @@ -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): @@ -70,16 +70,14 @@ 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) - ) + xmppserverlog.debug(f"Upgraded connection for {self.aclient.address}") self.aclient.transport = transport else: aclient = XMPPAsyncClient(transport) 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 +117,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 +131,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 +147,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 +156,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 +180,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 +242,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=") @@ -254,13 +252,11 @@ class XMPPAsyncClient: if client.type == self.BOT: if client.uid.lower() in ctl_to.lower(): - xmppserverlog.debug( - "Sending ctl to bot: {}".format(rxmlstring) - ) + xmppserverlog.debug(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 +272,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 +289,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,19 +304,23 @@ 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}") + 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"] @@ -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,17 +465,15 @@ 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: 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] @@ -500,7 +498,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 +521,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 +540,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 +557,7 @@ class XMPPAsyncClient: ) # Fail except Exception as e: - xmppserverlog.exception("{}".format(e)) + xmppserverlog.exception(f"{e}") def _handle_bind(self, xml): try: @@ -575,7 +573,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 +587,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 +600,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 +615,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 +634,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: @@ -662,9 +660,7 @@ class XMPPAsyncClient: ) # Send dummy return - self.send( - ' dummy '.format(self.bumper_jid) - ) + 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( - ' dummy '.format(self.bumper_jid) - ) + self.send(f' dummy ') def _parse_data(self, data): @@ -762,41 +756,35 @@ 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 " - 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 else: if "" in newdata: - xmppserverlog.error( - "xml parse error - {} - {}".format(newdata, e) - ) + xmppserverlog.error(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/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/tests/pytest.ini b/pytest.ini similarity index 69% rename from tests/pytest.ini rename to pytest.ini index 9ca2dc7..16b0152 100644 --- a/tests/pytest.ini +++ b/pytest.ini @@ -3,3 +3,8 @@ 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 + +asyncio_mode = auto +timeout = 10 +#log_cli=true +#log_level=DEBUG \ 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..5376f69 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,14 @@ +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==0.17.2 +pytest-aiohttp==1.0.3 +testfixtures==6.18.3 +pytest-env==0.6.2 +pytest-timeout==2.1.0 + +#pbr = "*" +#autoflake = "*" 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 "$@" 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/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..9959d47 --- /dev/null +++ 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/test_confserver.py b/tests/test_confserver.py index 2149673..7dfe287 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -1,29 +1,21 @@ -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 +import os +from unittest import mock + +import pytest from aiohttp import web -import logging from testfixtures import LogCapture -from unittest.mock import MagicMock + +import bumper +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) @@ -36,38 +28,48 @@ 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.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", + ) ) 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()) @@ -84,93 +86,76 @@ 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_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, 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() - client = await aiohttp_client(create_app) - resp = await client.get("/") - assert resp.status == 200 + 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_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, 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() - client = await aiohttp_client(create_app) - - resp = await client.get("/restart_Helperbot") - assert resp.status == 200 + resp = await conf_server_client.get("/restart_Helperbot") + assert resp.status == 200 - resp = await client.get("/restart_MQTTServer") - assert resp.status == 200 + resp = await conf_server_client.get("/restart_MQTTServer") + assert resp.status == 200 - resp = await client.get("/restart_XMPPServer") - assert resp.status == 200 + 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") - 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 +async def test_RemoveBot(conf_server_client): + resp = await conf_server_client.get("/bot/remove/test_did") + assert resp.status == 200 -async def test_login(aiohttp_client): +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(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) @@ -180,10 +165,10 @@ 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") + 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) @@ -194,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) @@ -205,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) @@ -216,15 +201,15 @@ 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) - 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) @@ -234,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" ) @@ -255,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 ) @@ -277,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 ) @@ -293,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 ) @@ -314,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" ) @@ -330,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" ) @@ -345,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 ) @@ -362,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 @@ -374,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" ) @@ -387,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" ) @@ -400,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 @@ -421,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 @@ -435,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 @@ -461,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 @@ -475,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 @@ -489,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 @@ -503,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 @@ -517,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 @@ -531,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 @@ -545,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 @@ -559,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": { @@ -574,43 +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") @@ -620,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 @@ -633,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) @@ -648,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) @@ -671,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) @@ -687,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) @@ -703,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) @@ -721,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) @@ -740,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) @@ -759,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) @@ -777,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 = { @@ -807,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) @@ -816,21 +778,20 @@ 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("127.0.0.1") + bumper.mqtt_helperbot = bumper.mqttserver.MQTTHelperBot(HOST, MQTT_PORT) # Test return get status command_getstatus_resp = { @@ -855,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) @@ -877,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("127.0.0.1") + 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) @@ -901,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) @@ -922,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) @@ -933,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("127.0.0.1") + 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) @@ -957,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) @@ -978,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) @@ -989,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) @@ -1001,11 +959,8 @@ 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) assert test_resp["ret"] == "fail" - - - diff --git a/tests/test_db.py b/tests/test_db.py index a23b368..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(): @@ -79,7 +81,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 +95,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..044fe2b 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,14 +1,16 @@ -import mock -from 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(): @@ -59,4 +61,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 70205bc..5157d3c 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -1,27 +1,21 @@ -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 time +import hbmqtt +import pytest +from testfixtures import LogCapture +import bumper +from tests import HOST, MQTT_PORT + + +@pytest.mark.usefixtures("mqtt_server") async def test_helperbot_message(): - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, 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,19 +39,17 @@ 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 ) # 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( @@ -71,15 +63,13 @@ 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 ) # 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 ) @@ -97,22 +87,19 @@ 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 ) # 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", @@ -124,7 +111,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 @@ -134,7 +121,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( @@ -147,82 +134,50 @@ 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_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") - await mqtt_server.broker_coro() + 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 - with LogCapture("helperbot") as l: + 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, + } - # 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 + mqtt_helperbot.commands[request_id] = data - 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 mqtt_helperbot.commands[request_id] == data - assert { - "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) + 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.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) - - - 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 - - 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 mqtt_server.broker.shutdown() - + await asyncio.sleep(timeout * 2) + + assert mqtt_helperbot.commands.get(request_id, None) == None + + await mqtt_helperbot.Client.disconnect() +@pytest.mark.usefixtures("mqtt_server") async def test_helperbot_sendcommand(): - mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address, 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 +200,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,14 +209,9 @@ 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 = ( - "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 ) @@ -276,7 +223,7 @@ async def test_helperbot_sendcommand(): "ret": "ok", } - #mqtt_helperbot.Client.disconnect() + # mqtt_helperbot.Client.disconnect() # Test GetLifeSpan (xml command) cmdjson = { @@ -296,14 +243,9 @@ 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 = ( - "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 ) @@ -321,12 +263,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", @@ -340,12 +277,9 @@ 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"}}' - + msg_topic_name = ( "iot/p2p/getStats/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testj/j" ) @@ -357,15 +291,32 @@ 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(): if os.path.exists("tests/tmp.db"): @@ -373,109 +324,130 @@ 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( + HOST, MQTT_PORT, password_file="tests/passwd", allow_anonymous=True + ) - mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd", allow_anonymous=True) - await mqtt_server.broker_coro() - # Test helperbot connect - mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) - 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(mqtt_address) - 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( - "mqtts://{}:{}/".format(test_client.address[0], test_client.address[1]), - 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(mqtt_address) - 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(mqtt_address) - 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!@{test_client.address[0]}:{test_client.address[1]}/", - 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: - - await test_client.Client.connect( - f"mqtts://test-client:notvalid!@{test_client.address[0]}:{test_client.address[1]}/", - cafile=bumper.ca_cert, cleansession=True + # 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} ) - l.check_present( - ("mqttserver", "INFO", "File Authentication Failed - Username: test-client - ClientID: test-file-auth"), - order_matters=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: + + await test_client.Client.connect( + f"mqtts://test-client:notvalid!@{HOST}:{MQTT_PORT}/", + cafile=bumper.ca_cert, + cleansession=True, ) - # no username in file - await test_client.Client.connect( - f"mqtts://test-client-noexist:notvalid!@{test_client.address[0]}:{test_client.address[1]}/", - 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() - l.check_present( - ("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_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() + 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 ef48025..c6cf681 100644 --- a/tests/test_xmppserver.py +++ b/tests/test_xmppserver.py @@ -1,15 +1,17 @@ -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): @@ -66,21 +68,19 @@ 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 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' ) @@ -88,13 +88,11 @@ 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 ( - 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 @@ -109,26 +107,26 @@ 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 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() # 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,21 +139,19 @@ 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 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' ) @@ -165,7 +161,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,34 +170,30 @@ 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 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 mock_send.reset_mock() # Client sends auth - test_data = 'AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl'.encode( - "utf-8" - ) + test_data = b'AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl' 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 @@ -277,21 +269,19 @@ 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 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] == '' ) @@ -299,13 +289,11 @@ 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 ( - 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 @@ -314,26 +302,24 @@ 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 assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # client ready # Reset mock calls mock_send.reset_mock() - # Send presense from client - test_data = ''.encode("utf-8") + # Send presence from client + test_data = b'' 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 @@ -347,21 +333,19 @@ 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 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' ) @@ -369,13 +353,11 @@ 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 ( - 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 @@ -394,21 +376,19 @@ 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 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] == '' ) @@ -416,13 +396,11 @@ 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 ( - 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 @@ -431,27 +409,23 @@ 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 assert ( - mock_send.mock_calls[0].args[0] == '' + mock_send.mock_calls[0][1][0] == '' ) # Bot ready # Reset mock calls mock_send.reset_mock() - # Send presense from bot - test_data = "hello world".encode( - "utf-8" - ) + # Send presence from bot + test_data = b"hello world" 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 @@ -467,13 +441,11 @@ 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 ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # ping response @@ -501,24 +473,20 @@ 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 ( - mock_send2.mock_calls[0].args[0] + mock_send2.mock_calls[0][1][0] == '' ) # ping response # Ping response from bot to user - test_data = "".encode( - "utf-8" - ) + test_data = b"" xmppclient2._parse_data(test_data) assert ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # ping response @@ -547,13 +515,13 @@ 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 ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # feature not implemented response @@ -561,13 +529,11 @@ 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 ( - mock_send2.mock_calls[0].args[0] + mock_send2.mock_calls[0][1][0] == '' ) # command was sent to bot @@ -575,13 +541,11 @@ 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 ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to client @@ -589,13 +553,11 @@ 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 ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to ecouser.net @@ -603,13 +565,11 @@ 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 ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to ecouser.net @@ -617,13 +577,11 @@ 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 ( - mock_send.mock_calls[0].args[0] + mock_send.mock_calls[0][1][0] == '' ) # result sent to ecouser.net @@ -631,15 +589,12 @@ 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] + mock_send.mock_calls[0][1][0] == '' ) # result sent to ecouser.net # Reset mock calls mock_send.reset_mock() - 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