ksaa 2025.5.23.3__py3-none-any.whl → 2025.6.23.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kotonebot/backend/bot.py +3 -3
- kotonebot/backend/context/context.py +52 -36
- kotonebot/backend/debug/server.py +3 -5
- kotonebot/backend/dispatch.py +1 -17
- kotonebot/backend/flow_controller.py +195 -0
- kotonebot/backend/loop.py +277 -0
- kotonebot/client/__init__.py +4 -1
- kotonebot/client/device.py +23 -21
- kotonebot/client/host/__init__.py +2 -1
- kotonebot/client/host/custom.py +26 -3
- kotonebot/client/host/leidian_host.py +22 -11
- kotonebot/client/host/mumu12_host.py +25 -3
- kotonebot/client/host/protocol.py +40 -18
- kotonebot/client/implements/__init__.py +6 -0
- kotonebot/client/implements/adb.py +67 -7
- kotonebot/client/implements/adb_raw.py +12 -5
- kotonebot/client/implements/remote_windows.py +20 -18
- kotonebot/client/implements/uiautomator2.py +8 -0
- kotonebot/client/implements/windows.py +56 -28
- kotonebot/client/protocol.py +13 -0
- kotonebot/client/registration.py +89 -0
- kotonebot/config/base_config.py +5 -1
- kotonebot/debug_entry.py +7 -2
- kotonebot/kaa/clear_logs.py +0 -3
- kotonebot/kaa/common.py +21 -0
- kotonebot/kaa/game_ui/common.py +4 -7
- kotonebot/kaa/game_ui/dialog.py +24 -4
- kotonebot/kaa/game_ui/idols_overview.py +28 -12
- kotonebot/kaa/game_ui/primary_button.py +55 -0
- kotonebot/kaa/game_ui/scrollable.py +0 -5
- kotonebot/kaa/main/cli.py +9 -4
- kotonebot/kaa/main/dmm_host.py +33 -7
- kotonebot/kaa/main/gr.py +156 -26
- kotonebot/kaa/main/kaa.py +113 -41
- kotonebot/kaa/metadata.py +27 -0
- kotonebot/kaa/resources/__pycache__/__init__.cpython-310.pyc +0 -0
- kotonebot/kaa/resources/game.db +0 -0
- kotonebot/kaa/resources/game_ver.txt +0 -0
- kotonebot/kaa/resources/idol_cards/i_card-skin-kllj-3-012_0.png +0 -0
- kotonebot/kaa/resources/idol_cards/i_card-skin-kllj-3-012_1.png +0 -0
- kotonebot/kaa/resources/idol_cards/i_card-skin-ssmk-3-012_0.png +0 -0
- kotonebot/kaa/resources/idol_cards/i_card-skin-ssmk-3-012_1.png +0 -0
- kotonebot/kaa/sprites/24e99232-9434-457f-a9a0-69dd7ecf675f.png +0 -0
- kotonebot/kaa/sprites/2ededcf5-1d80-4e2a-9c83-2a31998331ce.png +0 -0
- kotonebot/kaa/sprites/3b473fe6-e147-477f-b088-9b8fb042a4f6.png +0 -0
- kotonebot/kaa/sprites/55f7db71-0a18-4b3d-b847-57959b8d2e32.png +0 -0
- kotonebot/kaa/sprites/6cd80be8-c9b3-4ba5-bf17-3ffc9b000743.png +0 -0
- kotonebot/kaa/sprites/74ec3510-583d-4a76-ac69-38480fbf1387.png +0 -0
- kotonebot/kaa/sprites/a0bd6a5f-784d-4f0a-9d66-10f4b80c8d3e.png +0 -0
- kotonebot/kaa/sprites/d3424d31-0502-4623-996e-f0194e5085ce.png +0 -0
- kotonebot/kaa/sprites/e6b45405-cd9f-4c6e-a9f1-6ec953747c65.png +0 -0
- kotonebot/kaa/sprites/f5c16d2f-ebc5-4617-9b96-971696af7c52.png +0 -0
- kotonebot/kaa/tasks/R.py +157 -135
- kotonebot/kaa/tasks/end_game.py +14 -0
- kotonebot/kaa/tasks/produce/common.py +1 -0
- kotonebot/kaa/tasks/produce/in_purodyuusu.py +0 -4
- kotonebot/kaa/tasks/produce/non_lesson_actions.py +1 -4
- kotonebot/kaa/tasks/produce/produce.py +119 -70
- kotonebot/kaa/tasks/start_game.py +56 -7
- kotonebot/kaa/util/paths.py +6 -1
- kotonebot/tools/mirror.py +23 -13
- kotonebot/util.py +32 -0
- {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/METADATA +38 -1
- {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/RECORD +200 -183
- ksaa-2025.6.23.0.dist-info/licenses/LICENSE +674 -0
- kotonebot/client/factory.py +0 -92
- kotonebot/kaa/sprites/b260d390-2b1a-4974-92db-d68ff7b5b8e6.png +0 -0
- /kotonebot/kaa/sprites/{f6d36222-847e-4300-8f90-c98c7a8f36e3.png → 0235ae3d-1741-4a81-8053-f60bc395ee85.png} +0 -0
- /kotonebot/kaa/sprites/{48a02c33-b7af-4ab5-bda1-3a2ec62a7076.png → 038c69e6-7cb0-4c8d-99da-b537e48a86b7.png} +0 -0
- /kotonebot/kaa/sprites/{b99f0684-8200-4080-9b54-808c4ec12c99.png → 080b3cf5-ce35-4382-9bfa-d6a12efb09b3.png} +0 -0
- /kotonebot/kaa/sprites/{7c65fc91-602b-4d1b-82f9-c9b561a55d9b.png → 09fb5e41-6caa-4246-a415-8317b12d8d81.png} +0 -0
- /kotonebot/kaa/sprites/{5e74dc61-0ad5-4e57-b331-e3d2ed67b6d2.png → 0a6c6b1e-7a80-428e-a483-1858bfb8ac59.png} +0 -0
- /kotonebot/kaa/sprites/{aad29ac0-b9ad-4c38-93f5-e8036ad7b96b.png → 0ad2f603-a3ad-4a92-a398-c411145fd8e9.png} +0 -0
- /kotonebot/kaa/sprites/{e98037f8-3b4a-4de8-adc1-ca0f736b6c98.png → 0d4b669e-5e43-4458-b65a-5565797335ae.png} +0 -0
- /kotonebot/kaa/sprites/{382bc600-72f6-433f-809e-9fe2fdb31b32.png → 0fbcb3f6-9ca0-43c7-975b-e7c15c26c897.png} +0 -0
- /kotonebot/kaa/sprites/{6d39d7a2-0b1f-4f49-9ab8-062bca9b5470.png → 15465879-eb84-4ca6-ae0f-d3c5ac71e723.png} +0 -0
- /kotonebot/kaa/sprites/{474ce25b-8a3d-435e-8d7a-ab6566ae61a0.png → 1639278d-67e0-4452-a2b5-ec4cf4011f42.png} +0 -0
- /kotonebot/kaa/sprites/{7d4f53fb-02bc-4139-b32c-3345a5f92a79.png → 17414e9e-b046-49e0-9596-cad1f4673a9e.png} +0 -0
- /kotonebot/kaa/sprites/{9a72b63e-8bd1-47dc-8298-cdf02220201e.png → 17a8b0a5-5b71-4612-8a68-16c51f03c71b.png} +0 -0
- /kotonebot/kaa/sprites/{a1610e9f-93f0-4ef2-b32a-b2effd1673e8.png → 1aaceb21-dc5c-4a58-96d3-d2c66b2fb357.png} +0 -0
- /kotonebot/kaa/sprites/{237636f2-aec0-40be-b7bb-e08607a89307.png → 1aae0ab3-f619-47e0-8bf6-3d3791f7d445.png} +0 -0
- /kotonebot/kaa/sprites/{872c37e7-e596-4449-86f4-5ba65836a7a7.png → 1b6be6dd-2711-441a-9fa6-3281a6ad10c7.png} +0 -0
- /kotonebot/kaa/sprites/{a829f038-5421-4cc4-a494-105a973351e2.png → 1bb00a74-2fdc-4973-84ae-dc1edee15f81.png} +0 -0
- /kotonebot/kaa/sprites/{e5922ce7-9aa7-4806-acdd-9c35408a0f37.png → 1ccdcacf-4f7d-442c-8e2f-09aa7fe14e56.png} +0 -0
- /kotonebot/kaa/sprites/{1df6ff4b-17f8-4ee6-8df0-0b53e26b7f1e.png → 1d5f7454-672d-4e87-a368-ec4bb32f9b1f.png} +0 -0
- /kotonebot/kaa/sprites/{51f38e84-56c8-4c80-9a67-35196ef54b89.png → 1f2981d8-5eb6-4332-aa9b-17f4db2d4163.png} +0 -0
- /kotonebot/kaa/sprites/{f4b226eb-ed9e-4e5e-9ba0-1b777e401aa3.png → 25910d8e-d57f-409f-b0be-ee68c5af4920.png} +0 -0
- /kotonebot/kaa/sprites/{e9e399f7-4e2f-4392-bb8f-850c4a272dfa.png → 264e056f-3bcc-4b16-94d4-f767c78ab6f5.png} +0 -0
- /kotonebot/kaa/sprites/{cc0ac4cf-01fd-48f9-8707-2d6a2aa0ba8c.png → 26580772-ff20-4dcf-8496-f939e0f2890d.png} +0 -0
- /kotonebot/kaa/sprites/{7dd31493-9f3b-4382-a43a-f8eb1c745ad8.png → 29cb7b0f-5684-4516-8fae-4439f7f28e08.png} +0 -0
- /kotonebot/kaa/sprites/{415c45a8-9b71-4e0b-8cd2-f80a0a4aafb7.png → 2d145ade-b926-4276-b872-baec4ef78308.png} +0 -0
- /kotonebot/kaa/sprites/{d2732870-dd6b-4b7b-b21e-4ff2a4958b32.png → 2dd34393-ee6f-4b19-8a9c-76556d1bd7d7.png} +0 -0
- /kotonebot/kaa/sprites/{0d1918db-e000-4452-8490-ace623127e76.png → 2e00ac54-d23a-4d51-82f2-829c012552a6.png} +0 -0
- /kotonebot/kaa/sprites/{267fcd93-6a5c-4954-a06b-e97f327e9141.png → 2f74b04f-1992-4584-8dc6-e789b081ddc3.png} +0 -0
- /kotonebot/kaa/sprites/{08c2352f-0150-453a-aa0e-948dd0deef49.png → 302fde7d-de85-44c7-ba57-fe4b5f6db5fa.png} +0 -0
- /kotonebot/kaa/sprites/{e508161a-e595-4daf-a364-625aaf67e481.png → 3cc9685d-6347-4114-9d39-7683c2dc9df6.png} +0 -0
- /kotonebot/kaa/sprites/{d0c085fa-3089-4adf-979c-d4b274b32c3b.png → 42d8b5dc-ef27-4411-b8b5-d981e562990b.png} +0 -0
- /kotonebot/kaa/sprites/{2ef87b7f-d69e-4da2-878f-1cc35e48b15c.png → 448ea37d-a10d-4102-ac9f-a58403ee8914.png} +0 -0
- /kotonebot/kaa/sprites/{6dcc0bb3-984f-4c92-b0a3-74281d684182.png → 4a310bdd-0a7d-4669-9d57-6ae69bc8f022.png} +0 -0
- /kotonebot/kaa/sprites/{52e2b832-7837-47ca-a089-1f1593a99b08.png → 4b20b364-5f5f-4b34-960e-b0cfd08b1662.png} +0 -0
- /kotonebot/kaa/sprites/{34ec068a-d6fd-47ac-bd2f-0c31f93c19f3.png → 4e8f3e59-edfa-4523-939d-bc46988fc909.png} +0 -0
- /kotonebot/kaa/sprites/{157cfa99-d40c-48c2-8cf4-70835535522f.png → 4eaa5db2-d624-4df4-b2e4-01f72755f1a4.png} +0 -0
- /kotonebot/kaa/sprites/{56a70bc5-bd16-493b-b5b6-770f42f13de5.png → 5152e3fa-079c-4592-8df7-656101694df8.png} +0 -0
- /kotonebot/kaa/sprites/{b5009ffd-7e53-41ae-b78d-918ba0e7eda4.png → 52f7c8b1-af88-43c5-a999-de4da3caa03a.png} +0 -0
- /kotonebot/kaa/sprites/{465f1030-e538-4b22-a6f7-0087287b2e12.png → 534d1310-3503-46d3-8ecd-426d9bcf183e.png} +0 -0
- /kotonebot/kaa/sprites/{465d4e1a-c5f9-41b7-aa31-ce26d5c69c45.png → 5868ebfb-5e61-4ac6-b270-acb1e89b738f.png} +0 -0
- /kotonebot/kaa/sprites/{3e10014e-2063-424c-aeda-df985d351c12.png → 58a5f51d-d1a3-480b-8dde-ecead34d9f02.png} +0 -0
- /kotonebot/kaa/sprites/{4ffd7c97-86dd-4171-92e7-8e8e83e8f394.png → 5d3d61e9-bf13-4668-b0c7-fed730ea2ed6.png} +0 -0
- /kotonebot/kaa/sprites/{f4a1201f-9305-4ec5-8962-2bef0144ab46.png → 5dd0cc39-4102-4f0f-8ad3-342bbf825977.png} +0 -0
- /kotonebot/kaa/sprites/{dc17dea0-7749-4ecb-bbc0-1a52fb992dcf.png → 5ee8e9a8-b133-4ab7-a52c-e7e4e1d1ce7b.png} +0 -0
- /kotonebot/kaa/sprites/{172318e0-5e7b-4b61-9a17-6e2e66b27e69.png → 60be53a6-98e9-4fc6-9285-85506dc0e335.png} +0 -0
- /kotonebot/kaa/sprites/{8da0b45e-3d90-40f0-9781-c1ff4cd0e060.png → 6114d53e-dd69-4cee-82a7-7e43b338ff8d.png} +0 -0
- /kotonebot/kaa/sprites/{26433c9e-0ea2-4da4-9207-8f3fbad04235.png → 6156116e-56dd-4b41-a6de-ccec55dab7c8.png} +0 -0
- /kotonebot/kaa/sprites/{bdb09790-f702-4af2-ab67-0b3f05fd93ca.png → 65d2a5e5-48aa-4390-bb87-bfd8e044e4f6.png} +0 -0
- /kotonebot/kaa/sprites/{5a4a763f-b69b-4405-994d-470a78476f67.png → 66172bc0-3869-4a48-a26d-0dc3fa404ab8.png} +0 -0
- /kotonebot/kaa/sprites/{e0a5c772-49d3-468a-bcf5-0e3f24365253.png → 66678caa-7b25-4ff7-8c3a-017f67279349.png} +0 -0
- /kotonebot/kaa/sprites/{7f0783fe-89d9-4405-b7a3-6334ba543826.png → 68838f92-5916-424e-a37e-47e0b3834eb3.png} +0 -0
- /kotonebot/kaa/sprites/{c4fed66b-e195-4288-8b1f-ef4b0ef35174.png → 694d3a07-9697-4bda-984d-967779d530f7.png} +0 -0
- /kotonebot/kaa/sprites/{37e3a757-bcbc-4a37-bbf9-a38162a8627e.png → 69b022a8-e42f-4985-bd16-b4a530ad8a20.png} +0 -0
- /kotonebot/kaa/sprites/{5eddc669-dff6-462c-a495-81ddf951429e.png → 6aa34982-e7ab-4001-808f-8c5cf370d087.png} +0 -0
- /kotonebot/kaa/sprites/{428f770b-e149-417e-bb8a-38c1cba024e1.png → 7516c9a1-87ec-4d2f-ac5e-40d1aec27104.png} +0 -0
- /kotonebot/kaa/sprites/{63bfee9f-546e-4c3c-a82f-c2707bc62b90.png → 7aa2435c-da90-4d52-b831-88dbec57dde9.png} +0 -0
- /kotonebot/kaa/sprites/{d3b97b8f-f13a-413d-a097-5c456b8aea0d.png → 7b7dad18-a838-4267-b3c7-6ab43b8361f0.png} +0 -0
- /kotonebot/kaa/sprites/{ef539419-d100-43d5-83a1-14e65b85d816.png → 7cabdf91-2f15-4991-b165-eb9cdd557af6.png} +0 -0
- /kotonebot/kaa/sprites/{12356f5d-4188-4aa5-abb3-d219ca0167ca.png → 7f6cb60e-6ab3-4db4-86e1-8558250062a4.png} +0 -0
- /kotonebot/kaa/sprites/{1d6d5160-9525-42bf-a8d4-43c347d213eb.png → 83acbca8-3567-4262-a1e7-50f158348d45.png} +0 -0
- /kotonebot/kaa/sprites/{4b121018-f5c8-4c31-a5d9-48947982b191.png → 83cf48ba-d1af-4674-a52e-6bb80a626db7.png} +0 -0
- /kotonebot/kaa/sprites/{76c07ba7-afea-405c-806a-7691ce73f83a.png → 83f17b06-2792-4035-bc90-1800279ae131.png} +0 -0
- /kotonebot/kaa/sprites/{c287899b-b9a3-4ee2-a8fd-ebda89099b4b.png → 84bb4986-0295-4a45-9486-86e31fcdf1f1.png} +0 -0
- /kotonebot/kaa/sprites/{7b46a3e5-18d3-45a0-9500-1d7c854a12bb.png → 8b9c115b-d4ce-492e-88cb-08bd01c43570.png} +0 -0
- /kotonebot/kaa/sprites/{263708c5-0177-4c6a-8df3-611ec314fa56.png → 8e48af2b-c083-4480-a27c-c59c31a7ede1.png} +0 -0
- /kotonebot/kaa/sprites/{ced345fa-b375-457d-8b72-802424512e6c.png → 8f1e5644-1079-44b1-9d44-97bae3817bc5.png} +0 -0
- /kotonebot/kaa/sprites/{c94488b2-0ff0-4467-bbb0-405d3a2c55a3.png → 90def878-cd6e-46e5-9dc4-9c92dd904b80.png} +0 -0
- /kotonebot/kaa/sprites/{a69febc4-2d01-4628-be92-29b421604c4d.png → 920a3974-4875-4d2b-bbac-caa175c03ce0.png} +0 -0
- /kotonebot/kaa/sprites/{ddb2e9ee-e16a-4745-8598-b3f9c2f011be.png → 94433cf2-dd04-4b74-ae14-e5e3d945f579.png} +0 -0
- /kotonebot/kaa/sprites/{4ddc656f-ef7e-4580-ad12-9e382ea76e4f.png → 969eb98c-f761-4007-b8bd-2b3fb8d1f08c.png} +0 -0
- /kotonebot/kaa/sprites/{7d968a7f-f4a8-40a0-b9bf-e73ec1f2d7b5.png → 97adee05-1586-4589-93e6-c6c3a9cdae9d.png} +0 -0
- /kotonebot/kaa/sprites/{d364470a-d077-405f-a242-0d4fad32bc40.png → 9aef1aa8-41e1-42a7-83ad-b4e212ee3976.png} +0 -0
- /kotonebot/kaa/sprites/{9ce49f31-6b3a-4bc7-a9c8-9330d6f650f7.png → 9c7f1bcc-0bc8-454b-95fb-56a803030771.png} +0 -0
- /kotonebot/kaa/sprites/{99c36126-0173-49f3-a6b8-819bf9bd9306.png → 9cd7e1f1-13c4-471e-abee-223ffd9126cb.png} +0 -0
- /kotonebot/kaa/sprites/{370afc82-eea9-4063-afb7-5d5fec075510.png → 9d5dd50c-3341-41a4-a3dd-ccc00edcf201.png} +0 -0
- /kotonebot/kaa/sprites/{a5d05106-5a60-414c-972d-a886664abde4.png → 9f99b0e9-8cc0-4877-8f8a-d26257ef5386.png} +0 -0
- /kotonebot/kaa/sprites/{3720275d-cbf7-4513-a4a4-09f712b2286a.png → a1853223-d6b9-4660-af4e-91e59bdddc3c.png} +0 -0
- /kotonebot/kaa/sprites/{588f3405-4110-48dd-99ec-f89c004cf02e.png → a1bba683-a740-4eaf-8c37-3b1042b963b0.png} +0 -0
- /kotonebot/kaa/sprites/{60e43a33-18ee-45f1-bd9a-57bb6539611b.png → a1cf80ee-64bf-4d54-a7db-ffadf3c97026.png} +0 -0
- /kotonebot/kaa/sprites/{14cc953d-0ff6-493f-9478-691b5cbd0d07.png → a3b15425-0bfd-4668-a3a9-2ab64da8505f.png} +0 -0
- /kotonebot/kaa/sprites/{f400e1fe-a04d-442b-a6de-a448813443fd.png → a4075991-98da-4d06-bcde-28054cd124d2.png} +0 -0
- /kotonebot/kaa/sprites/{cecd2638-070f-4710-9142-2dc492660241.png → a7da51c2-8444-4770-a16b-b6543e40197f.png} +0 -0
- /kotonebot/kaa/sprites/{9d7a7bbb-b48b-458c-9964-963a57e10a7d.png → a8b3fdce-9e4a-495c-839e-0142f67fee69.png} +0 -0
- /kotonebot/kaa/sprites/{e55da40e-5fb5-48e0-91b1-2db0b3b32750.png → aa3b717f-67f8-445f-a6bf-b89f7200d95b.png} +0 -0
- /kotonebot/kaa/sprites/{8cc764de-f162-40a4-ba6f-40d60eac5633.png → abcc709c-1f4d-4832-a063-eafbad94ac31.png} +0 -0
- /kotonebot/kaa/sprites/{e6e66cdf-69c8-4862-a770-1f81c62f96ac.png → ad172750-d1a6-4a25-8ce8-9483f4b5affb.png} +0 -0
- /kotonebot/kaa/sprites/{eb229797-bffc-4a48-96de-85480af1608a.png → aefdec38-e3b6-4762-aed8-33107686ddac.png} +0 -0
- /kotonebot/kaa/sprites/{23217053-c549-4ac7-b8c0-3324728e6acd.png → b01c25d2-21bf-4db9-b5b2-ec35817f1b04.png} +0 -0
- /kotonebot/kaa/sprites/{9d908390-6f0f-46e6-a8fc-a7dfe23eda18.png → b1e7f24e-f246-406e-9073-58e2f3e4e819.png} +0 -0
- /kotonebot/kaa/sprites/{863550ee-e9b8-4aa2-8ebb-e906b09dde3b.png → b5d4265c-82e0-470d-a48e-8aa4edbdef5a.png} +0 -0
- /kotonebot/kaa/sprites/{608de0a3-ce93-4344-8f9b-3cb3fab5ad12.png → b65724b4-ec8f-48a9-9ab2-e7ce5b8f35b2.png} +0 -0
- /kotonebot/kaa/sprites/{22ab93e6-0192-48e1-a85f-73ee8db00395.png → b8aada91-eedd-4c53-99f3-f903d547235e.png} +0 -0
- /kotonebot/kaa/sprites/{09a01092-0d32-4894-a47e-9983b39753df.png → ba759205-ce99-4417-8cab-d32fe55dff4c.png} +0 -0
- /kotonebot/kaa/sprites/{934e5383-b63c-472e-afdb-d184c92bdfe3.png → bb0ae68d-44d5-47c6-91dd-a529bc34225b.png} +0 -0
- /kotonebot/kaa/sprites/{55d105dc-c488-47d8-9b95-aba5707cfc2a.png → bed9575d-ccad-4f4e-825e-47b2a9cb0925.png} +0 -0
- /kotonebot/kaa/sprites/{3a0e917b-f419-4398-8638-77f696d47cd4.png → bfec58df-2628-454f-92aa-769cad186448.png} +0 -0
- /kotonebot/kaa/sprites/{1e9e795c-5dc1-4851-8d49-86181b9f4efe.png → c25c7ce5-ad8d-4b2c-83eb-ae6d4871a6c3.png} +0 -0
- /kotonebot/kaa/sprites/{c30d08f7-5c07-487a-86c3-122c082bb07b.png → c3ec61aa-f62e-46fd-b359-48864ea7a150.png} +0 -0
- /kotonebot/kaa/sprites/{685a7a90-8196-4a6a-a67a-4e7078965982.png → c484ca57-6b19-46b5-a107-1805a7488d48.png} +0 -0
- /kotonebot/kaa/sprites/{9e74a9e6-6ea5-4551-9578-8e561195bd3d.png → c571e1c0-ff85-4b2e-97ed-97d33b8d92e3.png} +0 -0
- /kotonebot/kaa/sprites/{a2ea2e0f-4f92-4769-9bc4-1ee10c43712c.png → c5e16f75-9b5a-4853-a144-275f3d20c186.png} +0 -0
- /kotonebot/kaa/sprites/{37f421f8-f615-43d1-a10f-f9a915a940d1.png → c5ea3dfd-6761-466e-b44c-86592ef05323.png} +0 -0
- /kotonebot/kaa/sprites/{276a66cc-95b1-4f86-b700-afa9c9f18149.png → ca2adfb8-1e88-4a31-b3cc-1f70568ce4a3.png} +0 -0
- /kotonebot/kaa/sprites/{bbfcc1ea-b441-4e81-85d2-52ce3bf4eb62.png → cb24b3df-1ec3-4ffd-95f1-ca4b0dd5b7da.png} +0 -0
- /kotonebot/kaa/sprites/{9f6aef36-f2e5-4208-9748-b6bbdb5546fd.png → cdbbb5ba-5117-488a-8172-48cfc92c14c6.png} +0 -0
- /kotonebot/kaa/sprites/{3851e552-fd41-4a15-a6b6-9636c773f67a.png → cf66ae12-48bc-4512-a988-0e5de337c76d.png} +0 -0
- /kotonebot/kaa/sprites/{74db3d1c-d6be-4d1f-92be-9bb3ffad7f51.png → d1699baa-467b-46d1-9276-aae78fe8f589.png} +0 -0
- /kotonebot/kaa/sprites/{8383a31c-a075-4b5f-b839-cbeb63a7aef3.png → d3e6e2e9-c5b5-463f-98f4-de72c9320d9d.png} +0 -0
- /kotonebot/kaa/sprites/{51b836ff-5f2c-4612-85f1-9db74761f300.png → d4c47cc7-5b3e-4c3e-8e80-78cb94e7eed9.png} +0 -0
- /kotonebot/kaa/sprites/{2e0f97d4-ce0b-4930-addd-cbdaedffc918.png → d5d854fc-1b87-43d4-b32d-66eccb66fa41.png} +0 -0
- /kotonebot/kaa/sprites/{593f4c04-4ca4-4bb0-8fea-ad3af32316f7.png → d60a395b-d907-416a-b091-36bfee4948b3.png} +0 -0
- /kotonebot/kaa/sprites/{19fae7eb-addd-4ea2-99ac-0f9262a4d9d6.png → dad4ff8c-07f4-4b07-abd3-5d02bbcc4562.png} +0 -0
- /kotonebot/kaa/sprites/{8778f70d-6bb8-4378-aed7-d77c798ab032.png → dbd537c7-d1d3-4d64-9ccd-43dd4519bedd.png} +0 -0
- /kotonebot/kaa/sprites/{7250e776-3195-406c-ac6f-ec7dbed49cbe.png → dde03640-6c3a-4b13-a783-f672816bfec4.png} +0 -0
- /kotonebot/kaa/sprites/{4b87be27-868a-40fb-b7e4-67c963c6c4b2.png → df97421e-4b74-430e-b3d1-ed9bac6918a7.png} +0 -0
- /kotonebot/kaa/sprites/{7079f76b-e2a3-487c-8616-28ce505f5a0e.png → dfb14c98-f672-4906-80bd-d2a18bdd62cd.png} +0 -0
- /kotonebot/kaa/sprites/{b3f1b38f-77d0-4dd5-8c19-8a35bbc39516.png → e1e0d751-9006-4fa4-a933-4c0dc5e1f3d2.png} +0 -0
- /kotonebot/kaa/sprites/{aa8c4533-bd58-496e-a460-6fa47f7ded83.png → e394ff91-09d3-41e1-bbf5-bc42a71a623b.png} +0 -0
- /kotonebot/kaa/sprites/{72b7124a-baf2-4d60-9553-2634370a6d49.png → e5259dfb-3a49-4cfc-9f89-b42958ce6eec.png} +0 -0
- /kotonebot/kaa/sprites/{c072da5f-bd35-4e8b-8f32-6c5df18c3aea.png → e5eeb9af-8003-4981-bf56-2e32e76cec5d.png} +0 -0
- /kotonebot/kaa/sprites/{46ed11a3-f89e-4d5c-b534-da88d38b382b.png → e9c45a73-2011-4584-87b7-c4e566eba87f.png} +0 -0
- /kotonebot/kaa/sprites/{e3cfea3a-3dc4-412b-b226-f9034f5b51f1.png → eae83523-e58c-4db9-a356-4335a5da3dc8.png} +0 -0
- /kotonebot/kaa/sprites/{f3b4bb3b-f8d6-4ed0-8bde-0d3322bd8391.png → ed6d3e3d-0f04-4fa4-b0f9-fdbf324649f6.png} +0 -0
- /kotonebot/kaa/sprites/{fe031296-8000-4fdc-ab3f-b4aae2af6e5d.png → efb28e1d-7b52-4e20-aec5-117c798b7fbe.png} +0 -0
- /kotonebot/kaa/sprites/{b4afb6bd-e049-4b2e-b1ca-26333c7afcb0.png → f025027f-daa0-4943-8395-5d7d44cc7f15.png} +0 -0
- /kotonebot/kaa/sprites/{cd53bad6-81fa-4e15-8fe7-5a992367bf90.png → f12c2b0b-8273-4197-a8c6-2a8ef2788a9b.png} +0 -0
- /kotonebot/kaa/sprites/{86bcda3a-c7a6-4abf-ae4c-968788f3c667.png → f528ea0e-1e55-4e41-844f-bb1db9185ef8.png} +0 -0
- /kotonebot/kaa/sprites/{9b798f35-e745-4964-931d-3e0ae811174d.png → f611bbd9-c062-4989-96a4-65ff6c59673c.png} +0 -0
- /kotonebot/kaa/sprites/{de0b3d75-7dc8-4433-a275-2584e9d7ed0b.png → f6bfbf6a-6e53-4563-9345-3f3756d6ea9a.png} +0 -0
- /kotonebot/kaa/sprites/{6e338dd7-6834-4e9b-8ac1-afecdff16c43.png → f72bd3d6-850d-4190-9775-938f474d263c.png} +0 -0
- /kotonebot/kaa/sprites/{86501f18-e5fd-4a66-a360-20e34935f1a9.png → f8eec9b2-55ca-4195-b098-a5393d02a98a.png} +0 -0
- /kotonebot/kaa/sprites/{a1a03d83-6a3b-481a-a807-931f2b0f7cd2.png → faf069e1-f623-47c1-b879-cfa246fbc7df.png} +0 -0
- /kotonebot/kaa/sprites/{8e04cb5c-ce45-49d9-81a9-2e5f717d3eb2.png → faf360e8-7845-4fcf-b0a6-0f5f93c050f1.png} +0 -0
- {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/WHEEL +0 -0
- {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/entry_points.txt +0 -0
- {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from functools import lru_cache, partial
|
|
3
|
+
from typing import Callable, Any, overload, Literal, Generic, TypeVar, cast, get_args, get_origin
|
|
4
|
+
|
|
5
|
+
from cv2.typing import MatLike
|
|
6
|
+
|
|
7
|
+
from kotonebot.util import Interval
|
|
8
|
+
from kotonebot import device, image, ocr
|
|
9
|
+
from kotonebot.backend.core import Image
|
|
10
|
+
from kotonebot.backend.ocr import TextComparator
|
|
11
|
+
from kotonebot.client.protocol import ClickableObjectProtocol
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LoopAction:
|
|
15
|
+
def __init__(self, loop: 'Loop', func: Callable[[], ClickableObjectProtocol | None]):
|
|
16
|
+
self.loop = loop
|
|
17
|
+
self.func = func
|
|
18
|
+
self.result: ClickableObjectProtocol | None = None
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def found(self):
|
|
22
|
+
"""
|
|
23
|
+
是否找到结果。若父 Loop 未在运行中,则返回 False。
|
|
24
|
+
"""
|
|
25
|
+
if not self.loop.running:
|
|
26
|
+
return False
|
|
27
|
+
return bool(self.result)
|
|
28
|
+
|
|
29
|
+
def __bool__(self):
|
|
30
|
+
return self.found
|
|
31
|
+
|
|
32
|
+
def reset(self):
|
|
33
|
+
"""
|
|
34
|
+
重置 LoopAction,以复用此对象。
|
|
35
|
+
"""
|
|
36
|
+
self.result = None
|
|
37
|
+
|
|
38
|
+
def do(self):
|
|
39
|
+
"""
|
|
40
|
+
执行 LoopAction。
|
|
41
|
+
:return: 执行结果。
|
|
42
|
+
"""
|
|
43
|
+
if not self.loop.running:
|
|
44
|
+
return
|
|
45
|
+
if self.loop.found_anything:
|
|
46
|
+
# 本轮循环已执行任意操作,因此不需要再继续检测
|
|
47
|
+
return
|
|
48
|
+
self.result = self.func()
|
|
49
|
+
if self.result:
|
|
50
|
+
self.loop.found_anything = True
|
|
51
|
+
|
|
52
|
+
def click(self, *, at: tuple[int, int] | None = None):
|
|
53
|
+
"""
|
|
54
|
+
点击寻找结果。若结果为空,会跳过执行。
|
|
55
|
+
|
|
56
|
+
:return:
|
|
57
|
+
"""
|
|
58
|
+
if self.result:
|
|
59
|
+
if at is not None:
|
|
60
|
+
device.click(*at)
|
|
61
|
+
else:
|
|
62
|
+
device.click(self.result)
|
|
63
|
+
|
|
64
|
+
def call(self, func: Callable[[ClickableObjectProtocol], Any]):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Loop:
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
timeout: float = 300,
|
|
73
|
+
interval: float = 0.3,
|
|
74
|
+
auto_screenshot: bool = True
|
|
75
|
+
):
|
|
76
|
+
self.running = True
|
|
77
|
+
self.found_anything = False
|
|
78
|
+
self.auto_screenshot = auto_screenshot
|
|
79
|
+
"""
|
|
80
|
+
是否在每次循环开始时(Loop.tick() 被调用时)截图。
|
|
81
|
+
"""
|
|
82
|
+
self.__last_loop: float = -1
|
|
83
|
+
self.__interval = Interval(interval)
|
|
84
|
+
self.screenshot: MatLike | None = None
|
|
85
|
+
"""上次截图时的图像数据。"""
|
|
86
|
+
|
|
87
|
+
def __iter__(self):
|
|
88
|
+
self.__interval.reset()
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def __next__(self):
|
|
92
|
+
if not self.running:
|
|
93
|
+
raise StopIteration
|
|
94
|
+
self.found_anything = False
|
|
95
|
+
self.__last_loop = time.time()
|
|
96
|
+
return self.tick()
|
|
97
|
+
|
|
98
|
+
def tick(self):
|
|
99
|
+
self.__interval.wait()
|
|
100
|
+
if self.auto_screenshot:
|
|
101
|
+
self.screenshot = device.screenshot()
|
|
102
|
+
self.__last_loop = time.time()
|
|
103
|
+
self.found_anything = False
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def exit(self):
|
|
107
|
+
"""
|
|
108
|
+
结束循环。
|
|
109
|
+
"""
|
|
110
|
+
self.running = False
|
|
111
|
+
|
|
112
|
+
@overload
|
|
113
|
+
def when(self, condition: Image) -> LoopAction:
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
@overload
|
|
117
|
+
def when(self, condition: TextComparator) -> LoopAction:
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
def when(self, condition: Any):
|
|
121
|
+
"""
|
|
122
|
+
判断某个条件是否成立。
|
|
123
|
+
|
|
124
|
+
:param condition:
|
|
125
|
+
:return:
|
|
126
|
+
"""
|
|
127
|
+
if isinstance(condition, Image):
|
|
128
|
+
func = partial(image.find, condition)
|
|
129
|
+
elif isinstance(condition, TextComparator):
|
|
130
|
+
func = partial(ocr.find, condition)
|
|
131
|
+
else:
|
|
132
|
+
raise ValueError('Invalid condition type.')
|
|
133
|
+
la = LoopAction(self, func)
|
|
134
|
+
la.reset()
|
|
135
|
+
la.do()
|
|
136
|
+
return la
|
|
137
|
+
|
|
138
|
+
def until(self, condition: Any):
|
|
139
|
+
"""
|
|
140
|
+
当满足指定条件时,结束循环。
|
|
141
|
+
|
|
142
|
+
等价于 ``loop.when(...).call(lambda _: loop.exit())``
|
|
143
|
+
"""
|
|
144
|
+
return self.when(condition).call(lambda _: self.exit())
|
|
145
|
+
|
|
146
|
+
def click_if(self, condition: Any, *, at: tuple[int, int] | None = None):
|
|
147
|
+
"""
|
|
148
|
+
检测指定对象是否出现,若出现,点击该对象或指定位置。
|
|
149
|
+
|
|
150
|
+
``click_if()`` 等价于 ``loop.when(...).click(...)``。
|
|
151
|
+
|
|
152
|
+
:param condition: 检测目标。
|
|
153
|
+
:param at: 点击位置。若为 None,表示点击找到的目标。
|
|
154
|
+
"""
|
|
155
|
+
return self.when(condition).click(at=at)
|
|
156
|
+
|
|
157
|
+
StateType = TypeVar('StateType')
|
|
158
|
+
class StatedLoop(Loop, Generic[StateType]):
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
states: list[Any] | None = None,
|
|
162
|
+
initial_state: StateType | None = None,
|
|
163
|
+
*,
|
|
164
|
+
timeout: float = 300,
|
|
165
|
+
interval: float = 0.3,
|
|
166
|
+
auto_screenshot: bool = True
|
|
167
|
+
):
|
|
168
|
+
self.__tmp_states = states
|
|
169
|
+
self.__tmp_initial_state = initial_state
|
|
170
|
+
self.state: StateType
|
|
171
|
+
super().__init__(timeout=timeout, interval=interval, auto_screenshot=auto_screenshot)
|
|
172
|
+
|
|
173
|
+
def __iter__(self):
|
|
174
|
+
# __retrive_state_values() 只能在非 __init__ 中调用
|
|
175
|
+
self.__retrive_state_values()
|
|
176
|
+
return super().__iter__()
|
|
177
|
+
|
|
178
|
+
def __retrive_state_values(self):
|
|
179
|
+
# HACK: __orig_class__ 是 undocumented 属性
|
|
180
|
+
if not hasattr(self, '__orig_class__'):
|
|
181
|
+
# 如果 Foo 不是以参数化泛型的方式实例化的,可能没有 __orig_class__
|
|
182
|
+
if self.state is None:
|
|
183
|
+
raise ValueError('Either specify `states` or use StatedLoop[Literal[...]] syntax.')
|
|
184
|
+
else:
|
|
185
|
+
generic_type_args = get_args(self.__orig_class__) # type: ignore
|
|
186
|
+
if len(generic_type_args) != 1:
|
|
187
|
+
raise ValueError('StatedLoop must have exactly one generic type argument.')
|
|
188
|
+
state_values = get_args(generic_type_args[0])
|
|
189
|
+
if not state_values:
|
|
190
|
+
raise ValueError('StatedLoop must have at least one state value.')
|
|
191
|
+
self.states = cast(tuple[StateType, ...], state_values)
|
|
192
|
+
self.state = self.__tmp_initial_state or self.states[0]
|
|
193
|
+
return state_values
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def StatedLoop2(states: StateType) -> StatedLoop[StateType]:
|
|
197
|
+
state_values = get_args(states)
|
|
198
|
+
return cast(StatedLoop[StateType], Loop())
|
|
199
|
+
|
|
200
|
+
if __name__ == '__main__':
|
|
201
|
+
from kotonebot.kaa.tasks import R
|
|
202
|
+
from kotonebot.backend.ocr import contains
|
|
203
|
+
from kotonebot.backend.context import manual_context, init_context
|
|
204
|
+
|
|
205
|
+
# T = TypeVar('T')
|
|
206
|
+
# class Foo(Generic[T]):
|
|
207
|
+
# def get_literal_params(self) -> list | None:
|
|
208
|
+
# """
|
|
209
|
+
# 尝试获取泛型参数 T (如果它是 Literal 类型) 的参数列表。
|
|
210
|
+
# """
|
|
211
|
+
# # self.__orig_class__ 会是 Foo 的具体参数化类型,
|
|
212
|
+
# # 例如 Foo[Literal['p0', 'p1', 'p2', 'p3', 'ap']]
|
|
213
|
+
# if not hasattr(self, '__orig_class__'):
|
|
214
|
+
# # 如果 Foo 不是以参数化泛型的方式实例化的,可能没有 __orig_class__
|
|
215
|
+
# return None
|
|
216
|
+
#
|
|
217
|
+
# # generic_type_args 是传递给 Foo 的类型参数元组
|
|
218
|
+
# # 例如 (Literal['p0', 'p1', 'p2', 'p3', 'ap'],)
|
|
219
|
+
# generic_type_args = get_args(self.__orig_class__)
|
|
220
|
+
#
|
|
221
|
+
# if not generic_type_args:
|
|
222
|
+
# # Foo 没有类型参数
|
|
223
|
+
# return None
|
|
224
|
+
#
|
|
225
|
+
# # T_type 是 Foo 的第一个类型参数
|
|
226
|
+
# # 例如 Literal['p0', 'p1', 'p2', 'p3', 'ap']
|
|
227
|
+
# t_type = generic_type_args[0]
|
|
228
|
+
#
|
|
229
|
+
# # 检查 T_type 是否是 Literal 类型
|
|
230
|
+
# if get_origin(t_type) is Literal:
|
|
231
|
+
# # literal_args 是 Literal 类型的参数元组
|
|
232
|
+
# # 例如 ('p0', 'p1', 'p2', 'p3', 'ap')
|
|
233
|
+
# literal_args = get_args(t_type)
|
|
234
|
+
# return list(literal_args)
|
|
235
|
+
# else:
|
|
236
|
+
# # T 不是 Literal 类型
|
|
237
|
+
# return None
|
|
238
|
+
# f = Foo[Literal['p0', 'p1', 'p2', 'p3', 'ap']]()
|
|
239
|
+
# values = f.get_literal_params()
|
|
240
|
+
# 1
|
|
241
|
+
|
|
242
|
+
from typing_extensions import reveal_type
|
|
243
|
+
slp = StatedLoop[Literal['p0', 'p1', 'p2', 'p3', 'ap']]()
|
|
244
|
+
for l in slp:
|
|
245
|
+
reveal_type(l.states)
|
|
246
|
+
|
|
247
|
+
# init_context()
|
|
248
|
+
# manual_context().begin()
|
|
249
|
+
# for l in Loop():
|
|
250
|
+
# l.when(R.Produce.ButtonUse).click()
|
|
251
|
+
# l.when(R.Produce.ButtonRefillAP).click()
|
|
252
|
+
# l.when(contains("123")).click()
|
|
253
|
+
# l.click_if(contains("!23"), at=(1, 2))
|
|
254
|
+
|
|
255
|
+
# State = Literal['p0', 'p1', 'p2', 'p3', 'ap']
|
|
256
|
+
# for sl in StatedLoop[State]():
|
|
257
|
+
# match sl.state:
|
|
258
|
+
# case 'p0':
|
|
259
|
+
# sl.click_if(R.Produce.ButtonProduce)
|
|
260
|
+
# sl.click_if(contains('master'))
|
|
261
|
+
# sl.when(R.Produce.ButtonPIdolOverview).goto('p1')
|
|
262
|
+
# # AP 不足
|
|
263
|
+
# sl.when(R.Produce.TextAPInsufficient).goto('ap')
|
|
264
|
+
# case 'ap':
|
|
265
|
+
# pass
|
|
266
|
+
# # p1: 选择偶像
|
|
267
|
+
# case 'p1':
|
|
268
|
+
# sl.call(lambda _: select_idol(idol_skin_id), once=True)
|
|
269
|
+
# sl.when(R.Produce.TextAnotherIdolAvailableDialog).call(dialog.no)
|
|
270
|
+
# sl.click_if(R.Common.ButtonNextNoIcon)
|
|
271
|
+
# sl.until(R.Produce.TextStepIndicator2).goto('p2')
|
|
272
|
+
# case 'p2':
|
|
273
|
+
# sl.when(contains("123")).click()
|
|
274
|
+
# case 'p3':
|
|
275
|
+
# sl.click_if(contains("!23"), at=(1, 2))
|
|
276
|
+
# case _:
|
|
277
|
+
# assert_never(sl.state)
|
kotonebot/client/__init__.py
CHANGED
kotonebot/client/device.py
CHANGED
|
@@ -11,7 +11,7 @@ from adbutils._device import AdbDevice as AdbUtilsDevice
|
|
|
11
11
|
from ..backend.debug import result
|
|
12
12
|
from kotonebot.backend.core import HintBox
|
|
13
13
|
from kotonebot.primitives import Rect, Point, is_point
|
|
14
|
-
from .protocol import ClickableObjectProtocol, Commandable, Touchable, Screenshotable
|
|
14
|
+
from .protocol import ClickableObjectProtocol, Commandable, Touchable, Screenshotable, AndroidCommandable, WindowsCommandable
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
@@ -71,7 +71,6 @@ class Device:
|
|
|
71
71
|
横屏时为 'landscape',竖屏时为 'portrait'。
|
|
72
72
|
"""
|
|
73
73
|
|
|
74
|
-
self._command: Commandable
|
|
75
74
|
self._touch: Touchable
|
|
76
75
|
self._screenshot: Screenshotable
|
|
77
76
|
|
|
@@ -90,12 +89,6 @@ class Device:
|
|
|
90
89
|
def adb(self, value: AdbUtilsDevice) -> None:
|
|
91
90
|
self._adb = value
|
|
92
91
|
|
|
93
|
-
def launch_app(self, package_name: str) -> None:
|
|
94
|
-
"""
|
|
95
|
-
根据包名启动 app
|
|
96
|
-
"""
|
|
97
|
-
self._command.launch_app(package_name)
|
|
98
|
-
|
|
99
92
|
@overload
|
|
100
93
|
def click(self) -> None:
|
|
101
94
|
"""
|
|
@@ -306,17 +299,6 @@ class Device:
|
|
|
306
299
|
"""
|
|
307
300
|
return self._screenshot.screen_size
|
|
308
301
|
|
|
309
|
-
def current_package(self) -> str | None:
|
|
310
|
-
"""
|
|
311
|
-
获取前台 APP 的包名。
|
|
312
|
-
|
|
313
|
-
:return: 前台 APP 的包名。如果获取失败,则返回 None。
|
|
314
|
-
:exception: 如果设备不支持此功能,则抛出 NotImplementedError。
|
|
315
|
-
"""
|
|
316
|
-
ret = self._command.current_package()
|
|
317
|
-
logger.debug("current_package: %s", ret)
|
|
318
|
-
return ret
|
|
319
|
-
|
|
320
302
|
def detect_orientation(self) -> Literal['portrait', 'landscape'] | None:
|
|
321
303
|
"""
|
|
322
304
|
检测当前设备方向并设置 `self.orientation` 属性。
|
|
@@ -330,10 +312,30 @@ class AndroidDevice(Device):
|
|
|
330
312
|
def __init__(self, adb_connection: AdbUtilsDevice | None = None) -> None:
|
|
331
313
|
super().__init__('android')
|
|
332
314
|
self._adb: AdbUtilsDevice | None = adb_connection
|
|
315
|
+
self.commands: AndroidCommandable
|
|
316
|
+
|
|
317
|
+
def current_package(self) -> str | None:
|
|
318
|
+
"""
|
|
319
|
+
获取前台 APP 的包名。
|
|
320
|
+
|
|
321
|
+
:return: 前台 APP 的包名。如果获取失败,则返回 None。
|
|
322
|
+
:exception: 如果设备不支持此功能,则抛出 NotImplementedError。
|
|
323
|
+
"""
|
|
324
|
+
ret = self.commands.current_package()
|
|
325
|
+
logger.debug("current_package: %s", ret)
|
|
326
|
+
return ret
|
|
327
|
+
|
|
328
|
+
def launch_app(self, package_name: str) -> None:
|
|
329
|
+
"""
|
|
330
|
+
根据包名启动 app
|
|
331
|
+
"""
|
|
332
|
+
self.commands.launch_app(package_name)
|
|
333
|
+
|
|
333
334
|
|
|
334
335
|
class WindowsDevice(Device):
|
|
335
336
|
def __init__(self) -> None:
|
|
336
337
|
super().__init__('windows')
|
|
338
|
+
self.commands: WindowsCommandable
|
|
337
339
|
|
|
338
340
|
|
|
339
341
|
if __name__ == "__main__":
|
|
@@ -346,10 +348,10 @@ if __name__ == "__main__":
|
|
|
346
348
|
d = adb.device_list()[-1]
|
|
347
349
|
d.shell("dumpsys activity top | grep ACTIVITY | tail -n 1")
|
|
348
350
|
dd = AndroidDevice(d)
|
|
349
|
-
adb_imp = AdbRawImpl(
|
|
350
|
-
dd._command = adb_imp
|
|
351
|
+
adb_imp = AdbRawImpl(d)
|
|
351
352
|
dd._touch = adb_imp
|
|
352
353
|
dd._screenshot = adb_imp
|
|
354
|
+
dd.commands = adb_imp
|
|
353
355
|
# dd._screenshot = MinicapScreenshotImpl(dd)
|
|
354
356
|
# dd._screenshot = UiAutomator2Impl(dd)
|
|
355
357
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from .protocol import HostProtocol, Instance
|
|
1
|
+
from .protocol import HostProtocol, Instance, AdbHostConfig, WindowsHostConfig, RemoteWindowsHostConfig
|
|
2
2
|
from .custom import CustomInstance, create as create_custom
|
|
3
3
|
from .mumu12_host import Mumu12Host, Mumu12Instance
|
|
4
4
|
from .leidian_host import LeidianHost, LeidianInstance
|
|
5
5
|
|
|
6
6
|
__all__ = [
|
|
7
7
|
'HostProtocol', 'Instance',
|
|
8
|
+
'AdbHostConfig', 'WindowsHostConfig', 'RemoteWindowsHostConfig',
|
|
8
9
|
'CustomInstance', 'create_custom',
|
|
9
10
|
'Mumu12Host', 'Mumu12Instance',
|
|
10
11
|
'LeidianHost', 'LeidianInstance'
|
kotonebot/client/host/custom.py
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import subprocess
|
|
3
3
|
from psutil import process_iter
|
|
4
|
-
from .protocol import
|
|
5
|
-
from typing import
|
|
4
|
+
from .protocol import Instance, AdbHostConfig
|
|
5
|
+
from typing import ParamSpec, TypeVar, cast
|
|
6
6
|
from typing_extensions import override
|
|
7
7
|
|
|
8
8
|
from kotonebot import logging
|
|
9
|
+
from kotonebot.client import DeviceImpl
|
|
9
10
|
from kotonebot.client.device import Device
|
|
11
|
+
from kotonebot.client.registration import AdbBasedImpl, create_device
|
|
12
|
+
from kotonebot.client.implements.adb import AdbImplConfig
|
|
10
13
|
|
|
11
14
|
logger = logging.getLogger(__name__)
|
|
12
15
|
|
|
13
16
|
P = ParamSpec('P')
|
|
14
17
|
T = TypeVar('T')
|
|
15
18
|
|
|
16
|
-
class CustomInstance(Instance):
|
|
19
|
+
class CustomInstance(Instance[AdbHostConfig]):
|
|
17
20
|
def __init__(self, exe_path: str | None, emulator_args: str = "", *args, **kwargs):
|
|
18
21
|
super().__init__(*args, **kwargs)
|
|
19
22
|
self.exe_path: str | None = exe_path
|
|
@@ -65,6 +68,26 @@ class CustomInstance(Instance):
|
|
|
65
68
|
def refresh(self):
|
|
66
69
|
pass
|
|
67
70
|
|
|
71
|
+
@override
|
|
72
|
+
def create_device(self, impl: DeviceImpl, host_config: AdbHostConfig) -> Device:
|
|
73
|
+
"""为自定义实例创建 Device。"""
|
|
74
|
+
if self.adb_port is None:
|
|
75
|
+
raise ValueError("ADB port is not set and is required.")
|
|
76
|
+
|
|
77
|
+
# 为 ADB 相关的实现创建配置
|
|
78
|
+
if impl in ['adb', 'adb_raw', 'uiautomator2']:
|
|
79
|
+
config = AdbImplConfig(
|
|
80
|
+
addr=f'{self.adb_ip}:{self.adb_port}',
|
|
81
|
+
connect=True,
|
|
82
|
+
disconnect=True,
|
|
83
|
+
device_serial=self.adb_name,
|
|
84
|
+
timeout=host_config.timeout
|
|
85
|
+
)
|
|
86
|
+
impl = cast(AdbBasedImpl, impl) # make pylance happy
|
|
87
|
+
return create_device(impl, config)
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError(f'Unsupported device implementation for Custom: {impl}')
|
|
90
|
+
|
|
68
91
|
def __repr__(self) -> str:
|
|
69
92
|
return f'CustomInstance(#{self.id}# at "{self.exe_path}" with {self.adb_ip}:{self.adb_port})'
|
|
70
93
|
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import subprocess
|
|
3
|
+
from typing import cast
|
|
3
4
|
from functools import lru_cache
|
|
4
5
|
from typing_extensions import override
|
|
5
6
|
|
|
6
7
|
from kotonebot import logging
|
|
7
|
-
from kotonebot.client import DeviceImpl
|
|
8
|
+
from kotonebot.client import DeviceImpl
|
|
8
9
|
from kotonebot.client.device import Device
|
|
10
|
+
from kotonebot.client.registration import AdbBasedImpl, create_device
|
|
11
|
+
from kotonebot.client.implements.adb import AdbImplConfig
|
|
9
12
|
from kotonebot.util import Countdown, Interval
|
|
10
|
-
from .protocol import HostProtocol, Instance, copy_type
|
|
13
|
+
from .protocol import HostProtocol, Instance, copy_type, AdbHostConfig
|
|
11
14
|
|
|
12
15
|
logger = logging.getLogger(__name__)
|
|
13
16
|
|
|
@@ -18,7 +21,7 @@ else:
|
|
|
18
21
|
"""Stub for read_reg on non-Windows platforms."""
|
|
19
22
|
return default
|
|
20
23
|
|
|
21
|
-
class LeidianInstance(Instance):
|
|
24
|
+
class LeidianInstance(Instance[AdbHostConfig]):
|
|
22
25
|
@copy_type(Instance.__init__)
|
|
23
26
|
def __init__(self, *args, **kwargs):
|
|
24
27
|
super().__init__(*args, **kwargs)
|
|
@@ -70,16 +73,24 @@ class LeidianInstance(Instance):
|
|
|
70
73
|
return result.strip() == 'running'
|
|
71
74
|
|
|
72
75
|
@override
|
|
73
|
-
def create_device(self, impl: DeviceImpl,
|
|
76
|
+
def create_device(self, impl: DeviceImpl, host_config: AdbHostConfig) -> Device:
|
|
77
|
+
"""为雷电模拟器实例创建 Device。"""
|
|
74
78
|
if self.adb_port is None:
|
|
75
79
|
raise ValueError("ADB port is not set and is required.")
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
|
|
81
|
+
# 为 ADB 相关的实现创建配置
|
|
82
|
+
if impl in ['adb', 'adb_raw', 'uiautomator2']:
|
|
83
|
+
config = AdbImplConfig(
|
|
84
|
+
addr=f'{self.adb_ip}:{self.adb_port}',
|
|
85
|
+
connect=False, # 雷电模拟器不需要 adb connect
|
|
86
|
+
disconnect=False,
|
|
87
|
+
device_serial=self.adb_name,
|
|
88
|
+
timeout=host_config.timeout
|
|
89
|
+
)
|
|
90
|
+
impl = cast(AdbBasedImpl, impl) # make pylance happy
|
|
91
|
+
return create_device(impl, config)
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(f'Unsupported device implementation for Leidian: {impl}')
|
|
83
94
|
|
|
84
95
|
class LeidianHost(HostProtocol):
|
|
85
96
|
@staticmethod
|
|
@@ -2,13 +2,15 @@ import os
|
|
|
2
2
|
import json
|
|
3
3
|
import subprocess
|
|
4
4
|
from functools import lru_cache
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, cast
|
|
6
6
|
from typing_extensions import override
|
|
7
7
|
|
|
8
8
|
from kotonebot import logging
|
|
9
9
|
from kotonebot.client import DeviceImpl, Device
|
|
10
|
+
from kotonebot.client.registration import AdbBasedImpl, create_device
|
|
11
|
+
from kotonebot.client.implements.adb import AdbImplConfig
|
|
10
12
|
from kotonebot.util import Countdown, Interval
|
|
11
|
-
from .protocol import HostProtocol, Instance, copy_type
|
|
13
|
+
from .protocol import HostProtocol, Instance, copy_type, AdbHostConfig
|
|
12
14
|
|
|
13
15
|
logger = logging.getLogger(__name__)
|
|
14
16
|
|
|
@@ -19,7 +21,7 @@ else:
|
|
|
19
21
|
"""Stub for read_reg on non-Windows platforms."""
|
|
20
22
|
return default
|
|
21
23
|
|
|
22
|
-
class Mumu12Instance(Instance):
|
|
24
|
+
class Mumu12Instance(Instance[AdbHostConfig]):
|
|
23
25
|
@copy_type(Instance.__init__)
|
|
24
26
|
def __init__(self, *args, **kwargs):
|
|
25
27
|
super().__init__(*args, **kwargs)
|
|
@@ -70,6 +72,26 @@ class Mumu12Instance(Instance):
|
|
|
70
72
|
def running(self) -> bool:
|
|
71
73
|
return self.is_android_started
|
|
72
74
|
|
|
75
|
+
@override
|
|
76
|
+
def create_device(self, impl: DeviceImpl, host_config: AdbHostConfig) -> Device:
|
|
77
|
+
"""为MuMu12模拟器实例创建 Device。"""
|
|
78
|
+
if self.adb_port is None:
|
|
79
|
+
raise ValueError("ADB port is not set and is required.")
|
|
80
|
+
|
|
81
|
+
# 为 ADB 相关的实现创建配置
|
|
82
|
+
if impl in ['adb', 'adb_raw', 'uiautomator2']:
|
|
83
|
+
config = AdbImplConfig(
|
|
84
|
+
addr=f'{self.adb_ip}:{self.adb_port}',
|
|
85
|
+
connect=True,
|
|
86
|
+
disconnect=True,
|
|
87
|
+
device_serial=self.adb_name,
|
|
88
|
+
timeout=host_config.timeout
|
|
89
|
+
)
|
|
90
|
+
impl = cast(AdbBasedImpl, impl) # make pylance happy
|
|
91
|
+
return create_device(impl, config)
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(f'Unsupported device implementation for MuMu12: {impl}')
|
|
94
|
+
|
|
73
95
|
class Mumu12Host(HostProtocol):
|
|
74
96
|
@staticmethod
|
|
75
97
|
@lru_cache(maxsize=1)
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import socket
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from
|
|
5
|
-
from
|
|
4
|
+
from typing import Callable, TypeVar, Protocol, Any, Generic
|
|
5
|
+
from dataclasses import dataclass
|
|
6
6
|
|
|
7
7
|
from adbutils import adb, AdbTimeout, AdbError
|
|
8
8
|
from adbutils._device import AdbDevice
|
|
9
9
|
|
|
10
10
|
from kotonebot import logging
|
|
11
|
-
from kotonebot.client import Device,
|
|
11
|
+
from kotonebot.client import Device, DeviceImpl
|
|
12
|
+
|
|
12
13
|
from kotonebot.util import Countdown, Interval
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
@@ -17,6 +18,28 @@ _T = TypeVar("_T")
|
|
|
17
18
|
def copy_type(_: _T) -> Callable[[Any], _T]:
|
|
18
19
|
return lambda x: x
|
|
19
20
|
|
|
21
|
+
# --- 定义专用的 HostConfig 数据类 ---
|
|
22
|
+
@dataclass
|
|
23
|
+
class AdbHostConfig:
|
|
24
|
+
"""由外部为基于 ADB 的主机提供的配置。"""
|
|
25
|
+
timeout: float = 180
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class WindowsHostConfig:
|
|
29
|
+
"""由外部为 Windows 实现提供配置。"""
|
|
30
|
+
window_title: str
|
|
31
|
+
ahk_exe_path: str
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class RemoteWindowsHostConfig:
|
|
35
|
+
"""由外部为远程 Windows 实现提供配置。"""
|
|
36
|
+
windows_host_config: WindowsHostConfig
|
|
37
|
+
host: str
|
|
38
|
+
port: int
|
|
39
|
+
|
|
40
|
+
# --- 使用泛型改造 Instance 协议 ---
|
|
41
|
+
T_HostConfig = TypeVar("T_HostConfig")
|
|
42
|
+
|
|
20
43
|
def tcp_ping(host: str, port: int, timeout: float = 1.0) -> bool:
|
|
21
44
|
"""
|
|
22
45
|
通过 TCP ping 检查主机和端口是否可达。
|
|
@@ -36,7 +59,11 @@ def tcp_ping(host: str, port: int, timeout: float = 1.0) -> bool:
|
|
|
36
59
|
return False
|
|
37
60
|
|
|
38
61
|
|
|
39
|
-
class Instance(ABC):
|
|
62
|
+
class Instance(Generic[T_HostConfig], ABC):
|
|
63
|
+
"""
|
|
64
|
+
代表一个可运行环境的实例(如一个模拟器)。
|
|
65
|
+
使用泛型来约束 create_device 方法的配置参数类型。
|
|
66
|
+
"""
|
|
40
67
|
def __init__(self,
|
|
41
68
|
id: str,
|
|
42
69
|
name: str,
|
|
@@ -68,7 +95,7 @@ class Instance(ABC):
|
|
|
68
95
|
启动模拟器实例。
|
|
69
96
|
"""
|
|
70
97
|
raise NotImplementedError()
|
|
71
|
-
|
|
98
|
+
|
|
72
99
|
@abstractmethod
|
|
73
100
|
def stop(self):
|
|
74
101
|
"""
|
|
@@ -80,21 +107,16 @@ class Instance(ABC):
|
|
|
80
107
|
def running(self) -> bool:
|
|
81
108
|
raise NotImplementedError()
|
|
82
109
|
|
|
83
|
-
|
|
110
|
+
@abstractmethod
|
|
111
|
+
def create_device(self, impl: DeviceImpl, host_config: T_HostConfig) -> Device:
|
|
84
112
|
"""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
:
|
|
113
|
+
根据实现名称和类型化的主机配置创建设备。
|
|
114
|
+
|
|
115
|
+
:param impl: 设备实现的名称。
|
|
116
|
+
:param host_config: 一个类型化的数据对象,包含创建所需的所有外部配置。
|
|
117
|
+
:return: 配置好的 Device 实例。
|
|
88
118
|
"""
|
|
89
|
-
|
|
90
|
-
raise ValueError("ADB port is not set and is required.")
|
|
91
|
-
return create_device(
|
|
92
|
-
addr=f'{self.adb_ip}:{self.adb_port}',
|
|
93
|
-
impl=impl,
|
|
94
|
-
device_serial=self.adb_name,
|
|
95
|
-
connect=True,
|
|
96
|
-
timeout=timeout
|
|
97
|
-
)
|
|
119
|
+
raise NotImplementedError()
|
|
98
120
|
|
|
99
121
|
def wait_available(self, timeout: float = 180):
|
|
100
122
|
logger.info('Starting to wait for emulator %s(127.0.0.1:%d) to be available...', self.name, self.adb_port)
|