From 9d6c8d0b73fdd9a49a13f4644df07f9833b4f1b4 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:52:06 +0100 Subject: [PATCH] Fix listen alerts by upgrading firebase-messaging to support new fcm api v1 --- poetry.lock | 134 +++++++++-------- pyproject.toml | 3 +- ring_doorbell/cli.py | 4 +- ring_doorbell/const.py | 24 ++- ring_doorbell/doorbot.py | 6 +- ring_doorbell/listen/eventlistener.py | 157 ++++++++++++++------ ring_doorbell/other.py | 6 +- ring_doorbell/ring.py | 8 +- tests/conftest.py | 5 +- tests/fixtures/ring_listen_credentials.json | 37 +++-- tests/test_cli.py | 6 +- tests/test_listen.py | 66 +++----- 12 files changed, 275 insertions(+), 181 deletions(-) diff --git a/poetry.lock b/poetry.lock index 32107d71..cb26c36d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -655,13 +655,13 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "firebase-messaging" -version = "0.3.0" +version = "0.4.0" description = "FCM/GCM push notification client" optional = true -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "firebase_messaging-0.3.0-py3-none-any.whl", hash = "sha256:a13fcd52e69316d0c9a8238a2df94d7a8c195883e83e8e89ed27a8815d70f5e0"}, - {file = "firebase_messaging-0.3.0.tar.gz", hash = "sha256:1f4ecd81d78da25ddb50842f04b7f4a13ee401341c0d3ea0c1bd8492ba067f3b"}, + {file = "firebase_messaging-0.4.0-py3-none-any.whl", hash = "sha256:8ac37996aa3c2c5c25eadd5f604946dfcf45a202ae402196207370cc8b9f9dc8"}, + {file = "firebase_messaging-0.4.0.tar.gz", hash = "sha256:592daccfbc6093b181c9df8462b0850c770f85c7dddd98639e8eb57523a27958"}, ] [package.dependencies] @@ -671,7 +671,7 @@ http-ece = ">=1.1.0,<2.0.0" protobuf = ">=4.24.3,<5.0.0" [package.extras] -docs = ["sphinx (==7.1.2)", "sphinx-autodoc-typehints (>=1.24.0,<2.0.0)", "sphinx-rtd-theme (>=1.3.0,<2.0.0)"] +docs = ["myst-parser", "sphinx (==7.1.2)", "sphinx-autodoc-typehints (>=1.24.0,<2.0.0)", "sphinx-rtd-theme (>=1.3.0,<2.0.0)"] [[package]] name = "freezegun" @@ -802,13 +802,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.7" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] [[package]] @@ -1109,38 +1109,38 @@ files = [ [[package]] name = "mypy" -version = "1.11.1" +version = "1.11.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, - {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, - {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, - {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, - {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, - {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, - {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, - {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, - {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, - {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, - {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, - {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, - {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, - {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, - {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, - {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, - {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, - {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, - {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, ] [package.dependencies] @@ -1347,17 +1347,17 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.23.8" +version = "0.24.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, - {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, ] [package.dependencies] -pytest = ">=7.0.0,<9" +pytest = ">=8.2,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] @@ -1554,29 +1554,29 @@ fixture = ["fixtures"] [[package]] name = "ruff" -version = "0.6.1" +version = "0.6.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.1-py3-none-linux_armv6l.whl", hash = "sha256:b4bb7de6a24169dc023f992718a9417380301b0c2da0fe85919f47264fb8add9"}, - {file = "ruff-0.6.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:45efaae53b360c81043e311cdec8a7696420b3d3e8935202c2846e7a97d4edae"}, - {file = "ruff-0.6.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bc60c7d71b732c8fa73cf995efc0c836a2fd8b9810e115be8babb24ae87e0850"}, - {file = "ruff-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c7477c3b9da822e2db0b4e0b59e61b8a23e87886e727b327e7dcaf06213c5cf"}, - {file = "ruff-0.6.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a0af7ab3f86e3dc9f157a928e08e26c4b40707d0612b01cd577cc84b8905cc9"}, - {file = "ruff-0.6.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392688dbb50fecf1bf7126731c90c11a9df1c3a4cdc3f481b53e851da5634fa5"}, - {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5278d3e095ccc8c30430bcc9bc550f778790acc211865520f3041910a28d0024"}, - {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe6d5f65d6f276ee7a0fc50a0cecaccb362d30ef98a110f99cac1c7872df2f18"}, - {file = "ruff-0.6.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e0dd11e2ae553ee5c92a81731d88a9883af8db7408db47fc81887c1f8b672e"}, - {file = "ruff-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d812615525a34ecfc07fd93f906ef5b93656be01dfae9a819e31caa6cfe758a1"}, - {file = "ruff-0.6.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faaa4060f4064c3b7aaaa27328080c932fa142786f8142aff095b42b6a2eb631"}, - {file = "ruff-0.6.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99d7ae0df47c62729d58765c593ea54c2546d5de213f2af2a19442d50a10cec9"}, - {file = "ruff-0.6.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9eb18dfd7b613eec000e3738b3f0e4398bf0153cb80bfa3e351b3c1c2f6d7b15"}, - {file = "ruff-0.6.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c62bc04c6723a81e25e71715aa59489f15034d69bf641df88cb38bdc32fd1dbb"}, - {file = "ruff-0.6.1-py3-none-win32.whl", hash = "sha256:9fb4c4e8b83f19c9477a8745e56d2eeef07a7ff50b68a6998f7d9e2e3887bdc4"}, - {file = "ruff-0.6.1-py3-none-win_amd64.whl", hash = "sha256:c2ebfc8f51ef4aca05dad4552bbcf6fe8d1f75b2f6af546cc47cc1c1ca916b5b"}, - {file = "ruff-0.6.1-py3-none-win_arm64.whl", hash = "sha256:3bc81074971b0ffad1bd0c52284b22411f02a11a012082a76ac6da153536e014"}, - {file = "ruff-0.6.1.tar.gz", hash = "sha256:af3ffd8c6563acb8848d33cd19a69b9bfe943667f0419ca083f8ebe4224a3436"}, + {file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"}, + {file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"}, + {file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"}, + {file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"}, + {file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"}, + {file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"}, + {file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"}, ] [[package]] @@ -1971,18 +1971,22 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.20.0" +version = "3.20.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = true python-versions = ">=3.8" files = [ - {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"}, - {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"}, + {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, + {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [extras] docs = ["myst-parser", "sphinx", "sphinx-rtd-theme"] @@ -1991,4 +1995,4 @@ listen = ["firebase-messaging"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "c31fef4a6c5e01413c429d13f8526dcb9b6289fa301cd1d538e0395f98a25e53" +content-hash = "9450ea4a06e2a82694cc6870f11bf3cdec977fc662d684d3f1e826f3890216a1" diff --git a/pyproject.toml b/pyproject.toml index 48514353..39d53762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,9 @@ anyio = "*" # see https://github.com/python-trio/asyncclick/issues/18 sphinx = {version = "<7.2.6", optional = true} sphinx-rtd-theme = {version = "^1.3.0", optional = true} myst-parser = { version = "*", optional = true } -firebase-messaging = {version = "^0.3.0", optional = true} +firebase-messaging = {version = "^0.4.0", optional = true} typing-extensions = ">=4.12.2,<5.0" +async-timeout = ">=3.0.0" [tool.poetry.group.dev.dependencies] mock = "*" diff --git a/ring_doorbell/cli.py b/ring_doorbell/cli.py index 04c7de2b..59365c45 100644 --- a/ring_doorbell/cli.py +++ b/ring_doorbell/cli.py @@ -756,12 +756,12 @@ def credentials_updated_callback(credentials) -> None: credentials = json.load(f) event_listener = RingEventListener(ring, credentials, credentials_updated_callback) - await event_listener.async_start() + await event_listener.start() event_listener.add_notification_callback(_event_handler(ring).on_event) await ainput("Listening, press enter to cancel\n") - event_listener.stop() + await event_listener.stop() if __name__ == "__main__": diff --git a/ring_doorbell/const.py b/ring_doorbell/const.py index 9feaba1a..f0b085b6 100644 --- a/ring_doorbell/const.py +++ b/ring_doorbell/const.py @@ -38,6 +38,7 @@ class RingCapability(Enum): OPEN = auto() KNOCK = auto() PRE_ROLL = auto() + DING = auto() @staticmethod def from_name(name: str) -> RingCapability: @@ -72,7 +73,10 @@ def from_name(name: str) -> RingCapability: DEFAULT_LISTEN_EVENT_EXPIRES_IN = 180 # for Ring android app. 703521446232 for ring-site -RING_SENDER_ID = 876313859327 +FCM_RING_SENDER_ID = "876313859327" +FCM_API_KEY = "AIzaSyCv-hdFBmmdBBJadNy-TFwB-xN_H5m3Bk8" +FCM_PROJECT_ID = "ring-17770" +FCM_APP_ID = "1:876313859327:android:e10ec6ddb3c81f39" CLI_TOKEN_FILE = "ring_token.cache" # noqa: S105 GCM_TOKEN_FILE = "ring_gcm_token.cache" # noqa: S105 @@ -112,6 +116,11 @@ def from_name(name: str) -> RingCapability: KIND_DING = "ding" KIND_MOTION = "motion" KIND_INTERCOM_UNLOCK = "intercom_unlock" +KIND_ALARM_MODE_NONE = "alarm_mode_none" +KIND_ALARM_MODE_SOME = "alarm_mode_some" +KIND_ALARM_SIREN = "alarm_siren" +KIND_ALARM_SILENCED = "alarm_silenced" + # chime test sound kinds CHIME_TEST_SOUND_KINDS = (KIND_DING, KIND_MOTION) @@ -183,6 +192,19 @@ def from_name(name: str) -> RingCapability: PUSH_ACTION_MOTION = "com.ring.push.HANDLE_NEW_motion" PUSH_ACTION_INTERCOM_UNLOCK = "com.ring.push.INTERCOM_UNLOCK_FROM_APP" +PUSH_NOTIFICATION_KINDS = { + PUSH_ACTION_DING: KIND_DING, # legacy + "com.ring.pn.live-event.ding": KIND_DING, + PUSH_ACTION_MOTION: KIND_MOTION, # legacy + "com.ring.pn.live-event.motion": KIND_MOTION, + "com.ring.pn.live-event.intercom": KIND_DING, + PUSH_ACTION_INTERCOM_UNLOCK: KIND_INTERCOM_UNLOCK, + "com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_NONE_NOTICE": KIND_ALARM_MODE_NONE, + "com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_SOME_NOTICE": KIND_ALARM_MODE_SOME, + "com.ring.push.HANDLE_NEW_USER_SOUND_SIREN": KIND_ALARM_SIREN, + "com.ring.push.HANDLE_NEW_NON_ALARM_SIREN_SILENCED": KIND_ALARM_SILENCED, +} + POST_DATA_JSON = { "api_version": API_VERSION, "device_model": "ring-doorbell", diff --git a/ring_doorbell/doorbot.py b/ring_doorbell/doorbot.py index 3f62953c..35be0130 100644 --- a/ring_doorbell/doorbot.py +++ b/ring_doorbell/doorbot.py @@ -125,7 +125,11 @@ def has_capability(self, capability: RingCapability | str) -> bool: # noqa: PLR return True if capability == RingCapability.HISTORY: return True - if capability in [RingCapability.MOTION_DETECTION, RingCapability.VIDEO]: + if capability in [ + RingCapability.MOTION_DETECTION, + RingCapability.VIDEO, + RingCapability.DING, + ]: return self.kind in ( DOORBELL_KINDS + DOORBELL_2_KINDS diff --git a/ring_doorbell/listen/eventlistener.py b/ring_doorbell/listen/eventlistener.py index 79e23426..4da160dc 100644 --- a/ring_doorbell/listen/eventlistener.py +++ b/ring_doorbell/listen/eventlistener.py @@ -8,19 +8,24 @@ import time from typing import TYPE_CHECKING, Any, Callable, ClassVar -from firebase_messaging import FcmPushClient +from async_timeout import timeout as asyncio_timeout +from firebase_messaging import FcmPushClient, FcmRegisterConfig from ring_doorbell.const import ( API_URI, API_VERSION, DEFAULT_LISTEN_EVENT_EXPIRES_IN, + FCM_API_KEY, + FCM_APP_ID, + FCM_PROJECT_ID, + FCM_RING_SENDER_ID, KIND_DING, KIND_INTERCOM_UNLOCK, KIND_MOTION, PUSH_ACTION_DING, PUSH_ACTION_INTERCOM_UNLOCK, PUSH_ACTION_MOTION, - RING_SENDER_ID, + PUSH_NOTIFICATION_KINDS, SUBSCRIPTION_ENDPOINT, ) from ring_doorbell.event import RingEvent @@ -41,6 +46,8 @@ class RingEventListener: """Class to connect to firebase cloud messaging.""" + SESSION_REFRESH_INTERVAL = 60 * 60 * 12 + def __init__( self, ring: Ring, @@ -72,6 +79,14 @@ def __init__( self._subscription_counter = 1 self._intercom_unlock_counter: dict[int, int] = {} + self.session_refresh_task: asyncio.Task | None = None + self.fcm_token: str | None = None + + def _credentials_updated_cb(self, creds: dict[str, Any]) -> None: + self._credentials = creds + if self._credentials_updated_callback: + self._credentials_updated_callback(creds) + async def async_add_subscription_to_ring(self, token: str) -> None: """Add subscription to ring.""" if not self._ring.session: @@ -82,6 +97,7 @@ async def async_add_subscription_to_ring(self, token: str) -> None: "metadata": { "api_version": API_VERSION, "device_model": self._device_model, + "pn_dict_version": "2.0.0", "pn_service": "fcm", }, "os": "android", @@ -108,8 +124,6 @@ async def async_add_subscription_to_ring(self, token: str) -> None: # Update devices for the intercom unlock events if not self._ring.devices_data: await self._ring.async_update_devices() - if not self._ring.dings_data: - await self._ring.async_update_dings() def add_notification_callback(self, callback: OnNotificationCallable) -> int: """Add a callback to be notified on event.""" @@ -132,63 +146,74 @@ def remove_notification_callback(self, subscription_id: int) -> None: del self._callbacks[subscription_id] - if len(self._callbacks) == 0 and self._receiver: - self._receiver.stop() - self._receiver = None - - def stop(self) -> None: + async def stop(self) -> None: """Stop the listener.""" + self.started = False + if self._receiver: - self.started = False - self._receiver.stop() - self._receiver = None + await self._receiver.stop() + + refresh_task = self.session_refresh_task + self.session_refresh_task = None + if refresh_task and not refresh_task.done(): + refresh_task.cancel() self._callbacks = {} - async def async_start( + async def start( self, - callback: OnNotificationCallable | None = None, *, - listen_loop: asyncio.AbstractEventLoop | None = None, - callback_loop: asyncio.AbstractEventLoop | None = None, - timeout: int = 30, + timeout: int = 10, ) -> bool: """Start the listener.""" - if not callback: - callback = self._ring._add_event_to_dings_data # noqa: SLF001 - if not self._receiver: + fcm_config = FcmRegisterConfig( + FCM_PROJECT_ID, FCM_APP_ID, FCM_API_KEY, FCM_RING_SENDER_ID + ) self._receiver = FcmPushClient( - credentials=self._credentials, - credentials_updated_callback=self._credentials_updated_callback, + self._on_notification, + fcm_config, + self._credentials, + self._credentials_updated_cb, config=self._config, - http_client_session=self._ring.auth.http_client_session, + http_client_session=self._ring.auth._session, # noqa: SLF001 + ) + self.fcm_token = await self._receiver.checkin_or_register() + if not self.fcm_token: + _logger.error( + "Ring listener unable to check in to fcm, " "event listener not started" ) - fcm_token: str | None = await self._receiver.checkin( - RING_SENDER_ID, self._app_id - ) - if not fcm_token: - _logger.error("Unable to check in to fcm, event listener not started") return False - await self.async_add_subscription_to_ring(fcm_token) + if not self.subscribed: + await self.async_add_subscription_to_ring(self.fcm_token) if self.subscribed: - self.add_notification_callback(callback) + self.add_notification_callback(self._ring._add_event_to_dings_data) # noqa: SLF001 - self._receiver.start( - self._on_notification, - listen_event_loop=listen_loop, - callback_event_loop=callback_loop, + async with asyncio_timeout(timeout): + await self._receiver.start() + self.started = True + self.session_refresh_task = asyncio.create_task( + self._periodic_session_refresh() ) + return self.started - start = time.time() - now = start - while not self._receiver.is_started() and now - start < timeout: - await asyncio.sleep(0.1) - now = time.time() - self.started = self._receiver.is_started() + async def _periodic_session_refresh(self) -> None: + while self.started: + now = time.monotonic() + if TYPE_CHECKING: + assert self._ring.session_refresh_time + assert self.fcm_token + since_refresh = now - self._ring.session_refresh_time - return self.subscribed and self.started + if since_refresh > self.SESSION_REFRESH_INTERVAL: + _logger.debug("Refreshing ring session") + await self._ring.async_create_session() + await self.async_add_subscription_to_ring(self.fcm_token) + break + + sleep_for = 1 + time.monotonic() - self._ring.session_refresh_time + await asyncio.sleep(sleep_for) def _get_ding_event(self, gcm_data: dict[str, Any]) -> RingEvent: ding = gcm_data["ding"] @@ -243,8 +268,48 @@ def _on_notification( persistent_id: str, # noqa: ARG002 obj: Any | None = None, # noqa: ARG002 ) -> None: - gcm_data = json.loads(notification["data"]["gcmData"]) + msg_data = notification["data"] + if "gcmData" in msg_data: + gcm_data = json.loads(notification["data"]["gcmData"]) + ring_event = self._get_legacy_ring_event(gcm_data) + else: + ring_event = self._get_ring_event(msg_data) + if ring_event: + for callback in self._callbacks.values(): + callback(ring_event) + + def _get_ring_event(self, msg_data: dict) -> RingEvent | None: + if (android_config_str := msg_data.get("android_config")) is None or ( + data_str := msg_data.get("data") + ) is None: + _logger.debug( + "Unexpected alert type in fcm message data. Full message is:\n%s", + json.dumps(msg_data), + ) + return None + + android_config = json.loads(android_config_str) + data = json.loads(data_str) + event_category = android_config["category"] + event_kind = PUSH_NOTIFICATION_KINDS.get(event_category, "Unknown") + device = data["device"] + event = data["event"] + event_id = event["ding"]["id"] + created_at = event["ding"]["created_at"] + create_seconds = parse_datetime(created_at).timestamp() + return RingEvent( + event_id, + device["id"], + device_name=device.get("name"), + device_kind=device.get("kind"), + kind=event_kind, + now=create_seconds, + expires_in=DEFAULT_LISTEN_EVENT_EXPIRES_IN, + state=event["ding"]["subtype"], + ) + + def _get_legacy_ring_event(self, gcm_data: dict) -> RingEvent | None: re: RingEvent | None = None if "ding" in gcm_data: re = self._get_ding_event(gcm_data) @@ -253,12 +318,10 @@ def _on_notification( elif "community_alert" not in gcm_data: _logger.debug( "Unexpected alert type in gcmData. Full message is:\n%s", - json.dumps(notification), + json.dumps(gcm_data), ) - return - if re: - for callback in self._callbacks.values(): - callback(re) + return None + return re DEPRECATED_API_QUERIES: ClassVar = { "start", diff --git a/ring_doorbell/other.py b/ring_doorbell/other.py index c4a86fc4..3d63c8e5 100644 --- a/ring_doorbell/other.py +++ b/ring_doorbell/other.py @@ -69,7 +69,11 @@ def has_capability(self, capability: RingCapability | str) -> bool: if isinstance(capability, RingCapability) else RingCapability.from_name(capability) ) - if capability in [RingCapability.OPEN, RingCapability.HISTORY]: + if capability in [ + RingCapability.OPEN, + RingCapability.HISTORY, + RingCapability.DING, + ]: return self.kind in INTERCOM_KINDS return False diff --git a/ring_doorbell/ring.py b/ring_doorbell/ring.py index 0b2a7c62..d69cb5be 100644 --- a/ring_doorbell/ring.py +++ b/ring_doorbell/ring.py @@ -4,8 +4,8 @@ from __future__ import annotations import logging +import time from itertools import chain -from time import time from typing import TYPE_CHECKING, Any, ClassVar from ring_doorbell import RingEvent @@ -50,6 +50,7 @@ def __init__(self, auth: Auth) -> None: self.push_dings_data: list[RingEvent] = [] self.groups_data: dict[str, dict[str, Any]] = {} self.init_loop = None + self.session_refresh_time: float | None = None async def async_update_data(self) -> None: """Update all data.""" @@ -67,7 +68,7 @@ async def _async_update_data(self) -> None: def _add_event_to_dings_data(self, ring_event: RingEvent) -> None: # Purge expired push_dings - now = time() + now = time.monotonic() self.push_dings_data = [ re for re in self.push_dings_data if now < re.now + re.expires_in ] @@ -91,6 +92,7 @@ async def async_create_session(self) -> None: json=session_post_data, ) self.session = resp.json() + self.session_refresh_time = time.monotonic() async def async_update_devices(self) -> None: """Update device data.""" @@ -244,7 +246,7 @@ def groups(self) -> Mapping[str, RingLightGroup]: def active_alerts(self) -> Sequence[RingEvent]: """Get active alerts.""" - now = time() + now = time.time() # Purge expired push_dings self.push_dings_data = [ re for re in self.push_dings_data if now < re.now + re.expires_in diff --git a/tests/conftest.py b/tests/conftest.py index 89bb39bc..59684320 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -87,8 +87,11 @@ def _listen_mock(mocker, request) -> None: if not can_listen or "nolistenmock" in request.keywords: return - mocker.patch("firebase_messaging.FcmPushClient.checkin", return_value="foobar") + mocker.patch( + "firebase_messaging.FcmPushClient.checkin_or_register", return_value="foobar" + ) mocker.patch("firebase_messaging.FcmPushClient.start") + mocker.patch("firebase_messaging.FcmPushClient.stop") mocker.patch("firebase_messaging.FcmPushClient.is_started", return_value=True) diff --git a/tests/fixtures/ring_listen_credentials.json b/tests/fixtures/ring_listen_credentials.json index cdd14d43..2fa5778c 100644 --- a/tests/fixtures/ring_listen_credentials.json +++ b/tests/fixtures/ring_listen_credentials.json @@ -1,17 +1,36 @@ { "gcm": { - "token": "foobar", - "appId": "foobar", - "androidId": "5678901234567890123", - "securityToken": "0123456789012345678" + "token": "XYZ01234zyx:APA91b0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-01234abcdefghijklmnopqrstuvwxyz", + "app_id": "abcdef01-ef12-1234-12ef-abcdef012345", + "android_id": "5678901234567890123", + "security_token": "0123456789012345678" }, "keys": { - "public": "foobar", - "private": "foobar", - "secret": "foobar" + "public": "BPEHm32RWI4db8FCk0IM6G9f9vz_uJeRiuU64Y5dkyZjkBXmyGgzwzZMylPaLNvg50EuoQmNlU7sSMUf0mYctn0=", + "private": "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg-jTz69Lsq4oa49_5PMMkcqYFwkEGc5L-zGDwUwfY5eahRANCAATxB5t9kViOHW_BQpNCDOhvX_b8_7iXkYrlOuGOXZMmY5AV5shoM8M2TMpT2izb4OdBLqEJjZVO7EjFH9JmHLZ9", + "secret": "_zGHqwp9rRP5cgzilMLvCA" }, "fcm": { - "token": "foobar", - "pushSet": "foobar" + "registration": { + "name": "projects/project-1234/registrations/abcdefghijklmnopqrstuvwxyz-0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-01234abcdefghijklmnopqrstuvwxyz_XYZ01234zyx:APA91b0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "token": "abcdefghijklmnopqrstuvwxyz-0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-01234abcdefghijklmnopqrstuvwxyz_XYZ01234zyx:APA91b0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "web": { + "endpoint": "https://fcm.googleapis.com/fcm/send/XYZ01234zyx:APA91b0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-01234abcdefghijklmnopqrstuvwxyz", + "p256dh": "123456967219824KDJDOWFNAFW=", + "auth": "DSFNDSAKGFAGFA==" + } + }, + "installation": { + "token": "OPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijkl", + "expires_in": 604800, + "refresh_token": "1_OPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijklOPQRSTUVWXYZ-01234abcdefghijkl", + "fid": "1234AMXDRTTYODLsd-nb14", + "created_at": 36245.436300371 + } + }, + "config": { + "bundle_id": "project.push.com", + "project_id": "project-1234", + "vapid_key": "BDOU99-h67HcA6JeFXHbSNMu7e2yNNu3RzoMj8TM4W88jITfq7ZmPvIM1Iv-4_l2LxQcYwhqby2xGpWwzjfAnG4" } } diff --git a/tests/test_cli.py b/tests/test_cli.py index e827fef1..9fbf9da2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -226,7 +226,9 @@ async def test_listen_store_credentials(mocker, auth): ring = Ring(auth) assert not Path(GCM_TOKEN_FILE).is_file() - await runner.invoke(listen, ["--store-credentials"], obj=ring) + await runner.invoke( + listen, ["--store-credentials"], obj=ring, catch_exceptions=True + ) assert Path(GCM_TOKEN_FILE).is_file() assert firebase_messaging.fcmregister.FcmRegister.gcm_check_in.call_count == 0 assert firebase_messaging.fcmregister.FcmRegister.register.call_count == 1 @@ -247,7 +249,7 @@ async def test_listen_event_handler(mocker, auth): ring = Ring(auth) listener = RingEventListener(ring) - await listener.async_start() + await listener.start() listener.add_notification_callback(_event_handler(ring).on_event) msg = json.loads(load_fixture("ring_listen_fcmdata.json")) diff --git a/tests/test_listen.py b/tests/test_listen.py index 498966fb..9630549c 100644 --- a/tests/test_listen.py +++ b/tests/test_listen.py @@ -1,16 +1,14 @@ """The tests for the Ring platform.""" -import asyncio import datetime import json import pytest -from ring_doorbell import Auth, Ring -from ring_doorbell.const import USER_AGENT +from ring_doorbell import Ring from ring_doorbell.exceptions import RingError from ring_doorbell.listen import can_listen -from tests.conftest import load_fixture, load_fixture_as_dict +from tests.conftest import load_fixture # test_module.py pytestmark = pytest.mark.skipif( @@ -24,12 +22,11 @@ async def test_listen(auth, mocker): import firebase_messaging - disconnectmock = mocker.patch("firebase_messaging.FcmPushClient.stop") ring = Ring(auth) listener = RingEventListener(ring) - await listener.async_start() - assert firebase_messaging.FcmPushClient.checkin.call_count == 1 + await listener.start() + assert firebase_messaging.FcmPushClient.checkin_or_register.call_count == 1 assert firebase_messaging.FcmPushClient.start.call_count == 1 assert listener.subscribed is True assert listener.started is True @@ -46,7 +43,6 @@ async def test_listen(auth, mocker): cbid = listener.add_notification_callback(lambda: 2) del listener._callbacks[1] listener.remove_notification_callback(cbid) - disconnectmock.assert_called() async def test_active_dings(auth, mocker): @@ -54,13 +50,13 @@ async def test_active_dings(auth, mocker): ring = Ring(auth) listener = RingEventListener(ring) - await listener.async_start() - assert firebase_messaging.FcmPushClient.checkin.call_count == 1 + await listener.start() + assert firebase_messaging.FcmPushClient.checkin_or_register.call_count == 1 assert firebase_messaging.FcmPushClient.start.call_count == 1 assert listener.subscribed is True assert listener.started is True num_active = len(ring.active_alerts()) - assert num_active == 3 + assert num_active == 0 alertstoadd = 2 for i in range(alertstoadd): msg = json.loads(load_fixture("ring_listen_fcmdata.json")) @@ -86,7 +82,7 @@ async def test_active_dings(auth, mocker): dings = ring.active_alerts() assert len(dings) == num_active + alertstoadd - listener.stop() + await listener.stop() async def test_intercom_unlock(auth, mocker): @@ -94,13 +90,13 @@ async def test_intercom_unlock(auth, mocker): ring = Ring(auth) listener = RingEventListener(ring) - await listener.async_start() - assert firebase_messaging.FcmPushClient.checkin.call_count == 1 + await listener.start() + assert firebase_messaging.FcmPushClient.checkin_or_register.call_count == 1 assert firebase_messaging.FcmPushClient.start.call_count == 1 assert listener.subscribed is True assert listener.started is True num_active = len(ring.active_alerts()) - assert num_active == 3 + assert num_active == 0 alertstoadd = 2 for i in range(alertstoadd): msg = json.loads(load_fixture("ring_listen_fcmdata.json")) @@ -111,13 +107,13 @@ async def test_intercom_unlock(auth, mocker): dings = ring.active_alerts() assert len(dings) == num_active + alertstoadd - listener.stop() + await listener.stop() @pytest.mark.nolistenmock async def test_listen_subscribe_fail(auth, mocker, caplog, putpatch_status_fixture): checkinmock = mocker.patch( - "firebase_messaging.FcmPushClient.checkin", return_value="foobar" + "firebase_messaging.FcmPushClient.checkin_or_register", return_value="foobar" ) connectmock = mocker.patch("firebase_messaging.FcmPushClient.start") mocker.patch("firebase_messaging.FcmPushClient.is_started", return_value=True) @@ -126,7 +122,7 @@ async def test_listen_subscribe_fail(auth, mocker, caplog, putpatch_status_fixtu ring = Ring(auth) listener = RingEventListener(ring) - await listener.async_start() + await listener.start() # Check in gets and error so register is called assert checkinmock.call_count == 1 assert listener.subscribed is False @@ -164,7 +160,7 @@ async def test_listen_gcm_fail(auth, mocker): ring = Ring(auth) listener = RingEventListener(ring, credentials) - await listener.async_start() + await listener.start() # Check in gets and error so register is called assert checkinmock.call_count == 1 assert registermock.call_count == 1 @@ -176,21 +172,21 @@ async def test_listen_gcm_fail(auth, mocker): @pytest.mark.nolistenmock async def test_listen_fcm_fail(auth, mocker, caplog): checkinmock = mocker.patch( - "firebase_messaging.FcmPushClient.checkin", return_value=None + "firebase_messaging.FcmPushClient.checkin_or_register", return_value=None ) connectmock = mocker.patch("firebase_messaging.FcmPushClient.start") mocker.patch("firebase_messaging.FcmPushClient.is_started", return_value=True) ring = Ring(auth) listener = RingEventListener(ring) - await listener.async_start() + await listener.start() # Check in gets and error so register is called assert checkinmock.call_count == 1 assert listener.subscribed is False assert listener.started is False assert connectmock.call_count == 0 - exp = "Unable to check in to fcm, event listener not started" + exp = "Ring listener unable to check in to fcm, event listener not started" assert ( len( [ @@ -201,29 +197,3 @@ async def test_listen_fcm_fail(auth, mocker, caplog): ) == 1 ) - - -def test_no_event_loop(): - auth = Auth(USER_AGENT, token=load_fixture_as_dict("ring_oauth.json")) - ring = Ring(auth) - listener = RingEventListener(ring) - - with pytest.deprecated_call(): - listener.start() - with pytest.deprecated_call(): - ring.update_dings() - assert listener.started is True - - -async def test_run_in_executor(auth): - import firebase_messaging - - ring = Ring(auth) - listener = RingEventListener(ring) - loop = asyncio.get_running_loop() - with pytest.deprecated_call(): - await loop.run_in_executor(None, listener.start) - - assert listener.started - assert firebase_messaging.FcmPushClient.checkin.call_count == 1 - assert firebase_messaging.FcmPushClient.start.call_count == 1