Merge pull request #2 from edenhaus/next-v2

Add pre-commit and fix tests
This commit is contained in:
Robert Resch 2022-01-30 14:56:14 +01:00 committed by GitHub
commit c18208aa30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 4994 additions and 5328 deletions

6
.coveragerc Normal file
View file

@ -0,0 +1,6 @@
[run]
branch = True
source = bumper
omit =
tests/*

58
.github/workflows/ci.yml vendored Normal file
View file

@ -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

9
.gitignore vendored
View file

@ -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

89
.pre-commit-config.yaml Normal file
View file

@ -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

View file

@ -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

59
.yamllint Normal file
View file

@ -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

33
Pipfile
View file

@ -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

723
Pipfile.lock generated
View file

@ -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"
}
}
}

View file

@ -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.*

View file

@ -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"

21
bandit.yaml Normal file
View file

@ -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

View file

@ -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(

View file

@ -1,4 +1,4 @@
import bumper
if __name__ == "__main__":
bumper.main()
bumper.main()

View file

@ -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))
confserverlog.exception(f"{e}")

View file

@ -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())

File diff suppressed because it is too large Load diff

View file

@ -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)

View file

@ -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

View file

@ -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": "<!-- 邮件代码 --><div style=\"position: relative;\"><div style=\"font-size: 14px;font-family: Helvetica;padding: 65px 0;line-height: 150%;\"><p style=\"margin-bottom: 15px;\">Hey there,</p><p style=\"margin-bottom: 15px;\">\n\t\t\tCheck out my new awesome robot vacuum: <a href=\"#\" style=\"color: #1c95ea;text-decoration: none;\">DEEBOT 900 Series</a>!<br/>\n\t\t\tDownload the <strong>ECOVACS HOME</strong> App and sign up with your email: <a href=\"#\" style=\"color: #1c95ea;text-decoration: none;\">brian@bmartin.net</a>, so you and I can control this kick-ass robot together.</p><p style=\"margin-bottom: 15px;\">\n\t\t\tIOS: <span style=\"font-size:15px\"><a href=\"https://itunes.apple.com/us/app/ecovacs-home/id1329458504?l=zh&ls=1&mt=8\">https://itunes.apple.com/us/app/ecovacs-home/id1329458504?l=zh&amp;ls=1&amp;mt=8</a></span><br/>\n\t\t\tAndroid: <span style=\"font-size:15px\"><a href=\"https://play.google.com/store/apps/details?id=com.eco.global.app\">https://play.google.com/store/apps/details?id=com.eco.global.app</a></span></p><p style=\"margin-bottom: 15px;\">\n\t\t\tThis invitation is valid within 7 days.<br/>\n\t\t\tIf I&#39;m sending to the wrong person, please ignore this email.</p><p style=\"margin-bottom: 15px;\">Thank you.</p></div></div><!-- 邮件代码 -->",
# "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": "<!-- 邮件代码 --><div style=\"position: relative;\"><div style=\"font-size: 14px;font-family: Helvetica;padding: 65px 0;line-height: 150%;\"><p style=\"margin-bottom: 15px;\">Hey there,</p><p style=\"margin-bottom: 15px;\">\n\t\t\tCheck out my new awesome robot vacuum: <a href=\"#\" style=\"color: #1c95ea;text-decoration: none;\">DEEBOT 900 Series</a>!<br/>\n\t\t\tDownload the <strong>ECOVACS HOME</strong> App and sign up with your email: <a href=\"#\" style=\"color: #1c95ea;text-decoration: none;\">brian@bmartin.net</a>, so you and I can control this kick-ass robot together.</p><p style=\"margin-bottom: 15px;\">\n\t\t\tIOS: <span style=\"font-size:15px\"><a href=\"https://itunes.apple.com/us/app/ecovacs-home/id1329458504?l=zh&ls=1&mt=8\">https://itunes.apple.com/us/app/ecovacs-home/id1329458504?l=zh&amp;ls=1&amp;mt=8</a></span><br/>\n\t\t\tAndroid: <span style=\"font-size:15px\"><a href=\"https://play.google.com/store/apps/details?id=com.eco.global.app\">https://play.google.com/store/apps/details?id=com.eco.global.app</a></span></p><p style=\"margin-bottom: 15px;\">\n\t\t\tThis invitation is valid within 7 days.<br/>\n\t\t\tIf I&#39;m sending to the wrong person, please ignore this email.</p><p style=\"margin-bottom: 15px;\">Thank you.</p></div></div><!-- 邮件代码 -->",
# "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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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()

File diff suppressed because it is too large Load diff

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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",
},
],
}

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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 = '<iq type="set" id="{}" from="{}" to="{}"><query xmlns="com:ctl"><ctl td="AddUser" id="0000" jid="{}" /></query></iq>'.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 = '<iq type="set" id="{}" from="{}" to="{}"><query xmlns="com:ctl"><ctl td="SetAC" id="1111" jid="{}"><acs><ac name="userman" allow="1"/><ac name="setting" allow="1"/><ac name="clean" allow="1"/></acs></ctl></query></iq>'.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(
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
@ -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 = '<iq type="result" id="{}" />'.format(xml.get("id"))
@ -636,7 +634,7 @@ class XMPPAsyncClient:
# Most likely a bot, possibly hello world in text
# Send dummy return
self.send('<presence to="{}"> dummy </presence>'.format(self.bumper_jid))
self.send(f'<presence to="{self.bumper_jid}"> dummy </presence>')
# If it is a BOT, send extras
if self.type == self.BOT:
@ -662,9 +660,7 @@ class XMPPAsyncClient:
)
# Send dummy return
self.send(
'<presence to="{}"> dummy </presence>'.format(self.bumper_jid)
)
self.send(f'<presence to="{self.bumper_jid}"> dummy </presence>')
elif xml.get("type") == "unavailable":
xmppserverlog.debug(
"client presence unavailable (DISCONNECT) - {} ".format(
@ -681,9 +677,7 @@ class XMPPAsyncClient:
)
)
# Send dummy return
self.send(
'<presence to="{}"> dummy </presence>'.format(self.bumper_jid)
)
self.send(f'<presence to="{self.bumper_jid}"> dummy </presence>')
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 "<stream:stream " in newdata:
if self.state == self.CONNECT or self.state == self.INIT:
self._handle_connect(newdata.encode("utf-8"))
else:
if not (newdata == "" or newdata == " "):
xmppserverlog.error(
"xml parse error - {} - {}".format(newdata, e)
)
xmppserverlog.error(f"xml parse error - {newdata} - {e}")
elif "not well-formed (invalid token)" in e.msg:
# If a lone </stream:stream> - client is signalling end of session/disconnect
if not "</stream:stream>" in newdata:
xmppserverlog.error("xml parse error - {} - {}".format(newdata, e))
xmppserverlog.error(f"xml parse error - {newdata} - {e}")
else:
self.send("</stream:stream>") # Close stream
else:
if "<stream:stream" in newdata: # Handle start stream and connect
if self.state == self.CONNECT or self.state == self.INIT:
xmppserverlog.debug(
"Handling connect data - {}".format(newdata)
)
xmppserverlog.debug(f"Handling connect data - {newdata}")
self._handle_connect(newdata.encode("utf-8"))
else:
if not "</stream:stream>" in newdata:
xmppserverlog.error(
"xml parse error - {} - {}".format(newdata, e)
)
xmppserverlog.error(f"xml parse error - {newdata} - {e}")
else:
self.send("</stream:stream>") # Close stream
self._set_state("DISCONNECT")
except Exception as e:
xmppserverlog.exception("{}".format(e))
xmppserverlog.exception(f"{e}")
def _handle_iq(self, xml, data):

19
mypy.ini Normal file
View file

@ -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

84
pylintrc Normal file
View file

@ -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

View file

@ -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

2
requirements-dev.txt Normal file
View file

@ -0,0 +1,2 @@
-r requirements.txt
-r requirements-test.txt

14
requirements-test.txt Normal file
View file

@ -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 = "*"

21
scripts/run-in-env.sh Executable file
View file

@ -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 "$@"

25
setup.cfg Normal file
View file

@ -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

5
tests/__init__.py Normal file
View file

@ -0,0 +1,5 @@
HOST = "127.0.0.1"
MQTT_PORT = 8883

27
tests/conftest.py Normal file
View file

@ -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()

View file

@ -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"

View file

@ -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()

View file

@ -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

View file

@ -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 = "<ctl ts='1547822804960' td='DustCaseST' st='0'/>"
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 = "<ctl ts='1547822804960' td='DustCaseST' st='0'/>"
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 = "<ctl ret='ok' type='Brush' left='4142' total='18000'/>"
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,
)

View file

@ -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 = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>".encode(
"utf-8"
)
test_data = b"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>"
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]
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
)
# Server tells client available features
assert (
mock_send.mock_calls[1].args[0]
mock_send.mock_calls[1][1][0]
== '<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
)
@ -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 = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl</auth>'.encode(
"utf-8"
)
test_data = b'<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl</auth>'
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
) # 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 = "</stream:stream>".encode("utf-8")
test_data = b"</stream:stream>"
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] == "</stream:stream>"
assert mock_send.mock_calls[0][1][0] == "</stream:stream>"
# Reset mock calls
mock_send.reset_mock()
# Send abnormal stream from "client"
test_data = "<badstr />".encode("utf-8")
test_data = b"<badstr />"
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 = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>".encode(
"utf-8"
)
test_data = b"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>"
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]
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
)
# Server tells client available features
assert (
mock_send.mock_calls[1].args[0]
mock_send.mock_calls[1][1][0]
== '<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
)
@ -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 = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>".encode("utf-8")
test_data = b"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
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 = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>".encode(
"utf-8"
)
test_data = b"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>"
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]
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
)
# Server tells client available features (without STARTTLS)
assert (
mock_send.mock_calls[1].args[0]
mock_send.mock_calls[1][1][0]
== '<stream:features><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
)
# Reset mock calls
mock_send.reset_mock()
# Client sends auth
test_data = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl</auth>'.encode(
"utf-8"
)
test_data = b'<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl</auth>'
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
) # 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 = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>".encode(
"utf-8"
)
test_data = b"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>"
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]
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
)
# Server tells client binds
assert (
mock_send.mock_calls[1].args[0]
mock_send.mock_calls[1][1][0]
== '<stream:features><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>'
)
@ -299,13 +289,11 @@ async def test_client_init(*args, **kwargs):
mock_send.reset_mock()
# Send bind from "client"
test_data = '<iq type="set" id="5E9872D5-547E-49AF-AE51-9EFAA282F952"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>IOSF53D07BA</resource></bind></iq>'.encode(
"utf-8"
)
test_data = b'<iq type="set" id="5E9872D5-547E-49AF-AE51-9EFAA282F952"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>IOSF53D07BA</resource></bind></iq>'
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq type="result" id="5E9872D5-547E-49AF-AE51-9EFAA282F952"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>fuid_tmpuser@ecouser.net/IOSF53D07BA</jid></bind></iq>'
) # 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 = '<iq type="set" id="FA1041E7-AA27-43DD-BAA3-64DE2DE56AA3"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>'.encode(
"utf-8"
)
test_data = b'<iq type="set" id="FA1041E7-AA27-43DD-BAA3-64DE2DE56AA3"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>'
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]
== '<iq type="result" id="FA1041E7-AA27-43DD-BAA3-64DE2DE56AA3" />'
) # client ready
# Reset mock calls
mock_send.reset_mock()
# Send presense from client
test_data = '<presence type="available"/>'.encode("utf-8")
# Send presence from client
test_data = b'<presence type="available"/>'
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<presence to="fuid_tmpuser@ecouser.net/IOSF53D07BA"> dummy </presence>'
) # 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 = "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='159.ecorobot.net' version='1.0'>".encode(
"utf-8"
)
test_data = b"<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='159.ecorobot.net' version='1.0'>"
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]
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
)
# Server tells client available features
assert (
mock_send.mock_calls[1].args[0]
mock_send.mock_calls[1][1][0]
== '<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
)
@ -369,13 +353,11 @@ async def test_bot_connect(*args, **kwargs):
mock_send.reset_mock()
# Send auth from "bot"
test_data = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AEUwMDAwMDAwMDAwMDAwMDAxMjM0AGVuY3J5cHRlZF9wYXNz</auth>".encode(
"utf-8"
)
test_data = b"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AEUwMDAwMDAwMDAwMDAwMDAxMjM0AGVuY3J5cHRlZF9wYXNz</auth>"
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
) # 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 = "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='159.ecorobot.net' version='1.0'>".encode(
"utf-8"
)
test_data = b"<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='159.ecorobot.net' version='1.0'>"
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]
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
)
# Server tells client binds
assert (
mock_send.mock_calls[1].args[0]
mock_send.mock_calls[1][1][0]
== '<stream:features><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>'
)
@ -416,13 +396,11 @@ async def test_bot_init(*args, **kwargs):
mock_send.reset_mock()
# Send bind from "bot"
test_data = "<iq type='set' id='2521'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>atom</resource></bind></iq>".encode(
"utf-8"
)
test_data = b"<iq type='set' id='2521'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>atom</resource></bind></iq>"
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq type="result" id="2521"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>E0000000000000001234@159.ecorobot.net/atom</jid></bind></iq>'
) # 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 = "<iq type='set' id='2522'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>".encode(
"utf-8"
)
test_data = b"<iq type='set' id='2522'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>"
xmppclient._parse_data(test_data)
assert xmppclient.state == xmppclient.READY # Bot moved to READY state
assert (
mock_send.mock_calls[0].args[0] == '<iq type="result" id="2522" />'
mock_send.mock_calls[0][1][0] == '<iq type="result" id="2522" />'
) # Bot ready
# Reset mock calls
mock_send.reset_mock()
# Send presense from bot
test_data = "<presence><status>hello world</status></presence><iq type='result' from='E0000000000000001234@159.ecorobot.net/atom' to='ecouser.net' id='s2c1'/>".encode(
"utf-8"
)
# Send presence from bot
test_data = b"<presence><status>hello world</status></presence><iq type='result' from='E0000000000000001234@159.ecorobot.net/atom' to='ecouser.net' id='s2c1'/>"
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<presence to="E0000000000000001234@159.ecorobot.net/atom"> dummy </presence>'
) # 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 = '<iq xmlns:ns0="urn:xmpp:ping" from="E000BVTNX18700260382@159.ecorobot.net/atom" id="2542" to="159.ecorobot.net" type="get"><ping /></iq>'.encode(
"utf-8"
)
test_data = b'<iq xmlns:ns0="urn:xmpp:ping" from="E000BVTNX18700260382@159.ecorobot.net/atom" id="2542" to="159.ecorobot.net" type="get"><ping /></iq>'
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq type="result" id="2542" from="159.ecorobot.net" />'
) # 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 = '<iq id="104934615" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="get"><ping xmlns="urn:xmpp:ping" /></iq>'.encode(
"utf-8"
)
test_data = b'<iq id="104934615" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="get"><ping xmlns="urn:xmpp:ping" /></iq>'
xmppclient._parse_data(test_data)
assert (
mock_send2.mock_calls[0].args[0]
mock_send2.mock_calls[0][1][0]
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="104934615" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="get"><ping xmlns="urn:xmpp:ping" /></iq>'
) # ping response
# Ping response from bot to user
test_data = "<iq type='result' to='E0000000000000001234@159.ecorobot.net/atom' id='104934615'/>".encode(
"utf-8"
)
test_data = b"<iq type='result' to='E0000000000000001234@159.ecorobot.net/atom' id='104934615'/>"
xmppclient2._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq from="fuid_tmpuser@ecouser.net/IOSF53D07BA" id="104934615" to="E0000000000000001234@159.ecorobot.net/atom" type="result" />'
) # 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 = '<iq id="EE0XQ-2" type="get"><query xmlns="jabber:iq:roster" ></query></iq>'.encode(
"utf-8"
test_data = (
b'<iq id="EE0XQ-2" type="get"><query xmlns="jabber:iq:roster" ></query></iq>'
)
xmppclient._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq type="error" id="EE0XQ-2"><error type="cancel" code="501"><feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>'
) # feature not implemented response
@ -561,13 +529,11 @@ async def test_client_send_iq(*args, **kwargs):
mock_send.reset_mock()
# Bot Command
test_data = '<iq id="7" to="E0000000000000001234@159.ecorobot.net/atom" type="set"><query xmlns="com:ctl"><ctl id="72107787" td="GetCleanState" /></query></iq>'.encode(
"utf-8"
)
test_data = b'<iq id="7" to="E0000000000000001234@159.ecorobot.net/atom" type="set"><query xmlns="com:ctl"><ctl id="72107787" td="GetCleanState" /></query></iq>'
xmppclient._parse_data(test_data)
assert (
mock_send2.mock_calls[0].args[0]
mock_send2.mock_calls[0][1][0]
== '<iq from="fuid_tmpuser@ecouser.net/IOSF53D07BA" id="7" to="E0000000000000001234@159.ecorobot.net/atom" type="set"><query xmlns="com:ctl"><ctl id="72107787" td="GetCleanState" /></query></iq>'
) # 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 = '<iq xmlns:ns0="com:ctl" id="2679" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="set"><query><ctl td="ChargeState"><charge h="0" r="a" type="Going" /></ctl></query></iq>'.encode(
"utf-8"
)
test_data = b'<iq xmlns:ns0="com:ctl" id="2679" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="set"><query><ctl td="ChargeState"><charge h="0" r="a" type="Going" /></ctl></query></iq>'
xmppclient2._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="2679" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="set"><query xmlns="com:ctl"><ctl td="ChargeState"><charge h="0" r="a" type="Going" /></ctl></query></iq>'
) # result sent to client
@ -589,13 +553,11 @@ async def test_client_send_iq(*args, **kwargs):
mock_send.reset_mock()
# Bot result
test_data = "<iq type='result' from='E0000000000000001234@159.ecorobot.net/atom' to='ecouser.net' id='s2c1'/>".encode(
"utf-8"
)
test_data = b"<iq type='result' from='E0000000000000001234@159.ecorobot.net/atom' to='ecouser.net' id='s2c1'/>"
xmppclient2._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="s2c1" to="ecouser.net" type="result" />'
) # 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 = "<iq to='fuid_tmpuser@ecouser.net/IOSF53D07BA' type='set' id='2700'><query xmlns='com:ctl'><ctl td='BatteryInfo'><battery power='100'/></ctl></query></iq>".encode(
"utf-8"
)
test_data = b"<iq to='fuid_tmpuser@ecouser.net/IOSF53D07BA' type='set' id='2700'><query xmlns='com:ctl'><ctl td='BatteryInfo'><battery power='100'/></ctl></query></iq>"
xmppclient2._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="2700" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="set"><query xmlns="com:ctl"><ctl td="BatteryInfo"><battery power="100" /></ctl></query></iq>'
) # 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 = "<iq to='fuid_tmpuser@ecouser.net/IOSF53D07BA' type='set' id='631'><query xmlns='com:ctl'><ctl td='error' errs='102'/></query></iq>".encode(
"utf-8"
)
test_data = b"<iq to='fuid_tmpuser@ecouser.net/IOSF53D07BA' type='set' id='631'><query xmlns='com:ctl'><ctl td='error' errs='102'/></query></iq>"
xmppclient2._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="631" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="set"><query xmlns="com:ctl"><ctl errs="102" td="error" /></query></iq>'
) # 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 = "<iq to='rl.ecorobot.net' type='set' id='1234'><query xmlns='com:sf'><sf td='pub' t='log' ts='1559893796000' tp='p' k='DeviceAlert' v='DorpError' f='E0000000000000001234@159.ecorobot.net' g='fuid_tmpuser@ecouser.net'/></query></iq>".encode(
"utf-8"
)
test_data = b"<iq to='rl.ecorobot.net' type='set' id='1234'><query xmlns='com:sf'><sf td='pub' t='log' ts='1559893796000' tp='p' k='DeviceAlert' v='DorpError' f='E0000000000000001234@159.ecorobot.net' g='fuid_tmpuser@ecouser.net'/></query></iq>"
xmppclient2._parse_data(test_data)
assert (
mock_send.mock_calls[0].args[0]
mock_send.mock_calls[0][1][0]
== '<iq xmlns="com:sf" from="E0000000000000001234@159.ecorobot.net/atom" id="1234" to="rl.ecorobot.net" type="set"><query xmlns="com:ctl"><sf f="E0000000000000001234@159.ecorobot.net" g="fuid_tmpuser@ecouser.net" k="DeviceAlert" t="log" td="pub" tp="p" ts="1559893796000" v="DorpError" /></query></iq>'
) # result sent to ecouser.net
# Reset mock calls
mock_send.reset_mock()

View file

@ -1,4 +1,4 @@
from mock import patch
from unittest.mock import patch
import bumper