ksaa 2025.6.8.0__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/context/context.py +31 -25
- kotonebot/backend/debug/server.py +0 -2
- kotonebot/backend/dispatch.py +1 -17
- 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 +38 -15
- 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/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/kaa.py +113 -41
- kotonebot/kaa/metadata.py +15 -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/produce/in_purodyuusu.py +0 -4
- kotonebot/kaa/tasks/produce/non_lesson_actions.py +0 -3
- kotonebot/kaa/tasks/produce/produce.py +119 -70
- kotonebot/kaa/tasks/start_game.py +7 -6
- kotonebot/kaa/util/paths.py +6 -1
- kotonebot/tools/mirror.py +23 -13
- kotonebot/util.py +32 -0
- {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/METADATA +1 -1
- {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/RECORD +194 -179
- kotonebot/client/factory.py +0 -92
- kotonebot/kaa/sprites/38b8f119-8de1-49bf-8a5f-a44371de4569.png +0 -0
- /kotonebot/kaa/sprites/{321b54c8-c69b-4437-931d-655882dabeed.png → 0235ae3d-1741-4a81-8053-f60bc395ee85.png} +0 -0
- /kotonebot/kaa/sprites/{4d6c198a-0e34-4c80-86d1-7789b2082d64.png → 038c69e6-7cb0-4c8d-99da-b537e48a86b7.png} +0 -0
- /kotonebot/kaa/sprites/{4bd66b38-78d3-4a27-816b-d315039a8bb6.png → 080b3cf5-ce35-4382-9bfa-d6a12efb09b3.png} +0 -0
- /kotonebot/kaa/sprites/{ecd44a4a-e1ca-4a86-ae0d-2306a672613f.png → 09fb5e41-6caa-4246-a415-8317b12d8d81.png} +0 -0
- /kotonebot/kaa/sprites/{4f58031c-36b9-4baf-bccf-11c05d83bd19.png → 0a6c6b1e-7a80-428e-a483-1858bfb8ac59.png} +0 -0
- /kotonebot/kaa/sprites/{e41bb5df-164d-4760-9db6-d5d5d763bb5f.png → 0ad2f603-a3ad-4a92-a398-c411145fd8e9.png} +0 -0
- /kotonebot/kaa/sprites/{c1b1125f-542b-4e63-9c86-5d2e35a89a10.png → 0d4b669e-5e43-4458-b65a-5565797335ae.png} +0 -0
- /kotonebot/kaa/sprites/{74521b6f-de5e-4ba8-a3c9-c6ba01070498.png → 0fbcb3f6-9ca0-43c7-975b-e7c15c26c897.png} +0 -0
- /kotonebot/kaa/sprites/{7b3c6860-fbbe-48aa-aadf-8062fa88f1fe.png → 15465879-eb84-4ca6-ae0f-d3c5ac71e723.png} +0 -0
- /kotonebot/kaa/sprites/{309105d4-073a-47c9-8c5c-d593aa497723.png → 1639278d-67e0-4452-a2b5-ec4cf4011f42.png} +0 -0
- /kotonebot/kaa/sprites/{157dd58d-e319-4e0c-a37a-638e1a31c60b.png → 17414e9e-b046-49e0-9596-cad1f4673a9e.png} +0 -0
- /kotonebot/kaa/sprites/{05ff5efd-0f76-47ce-bc89-237266bc17c0.png → 17a8b0a5-5b71-4612-8a68-16c51f03c71b.png} +0 -0
- /kotonebot/kaa/sprites/{54bb5fcf-be61-4af8-bb19-a3c5113cf9e1.png → 1aaceb21-dc5c-4a58-96d3-d2c66b2fb357.png} +0 -0
- /kotonebot/kaa/sprites/{1f8f8c46-43ec-4449-be9f-2e96da11da1e.png → 1aae0ab3-f619-47e0-8bf6-3d3791f7d445.png} +0 -0
- /kotonebot/kaa/sprites/{b1e88fda-434a-4d06-8891-883a7beafa12.png → 1b6be6dd-2711-441a-9fa6-3281a6ad10c7.png} +0 -0
- /kotonebot/kaa/sprites/{ef39804e-8d2d-40f6-8d13-bd2877d1353a.png → 1bb00a74-2fdc-4973-84ae-dc1edee15f81.png} +0 -0
- /kotonebot/kaa/sprites/{804a4b05-1ca3-4915-bf23-a66ccdc34404.png → 1ccdcacf-4f7d-442c-8e2f-09aa7fe14e56.png} +0 -0
- /kotonebot/kaa/sprites/{6a7d061e-682b-42c1-97d5-121c52921e8b.png → 1d5f7454-672d-4e87-a368-ec4bb32f9b1f.png} +0 -0
- /kotonebot/kaa/sprites/{624555b9-31e3-46a3-8078-55619eaa40b9.png → 1f2981d8-5eb6-4332-aa9b-17f4db2d4163.png} +0 -0
- /kotonebot/kaa/sprites/{ff77d221-8444-4d0f-8c93-0223a97e2a98.png → 25910d8e-d57f-409f-b0be-ee68c5af4920.png} +0 -0
- /kotonebot/kaa/sprites/{fbd816df-7ae6-40a4-b4d4-5708f5ba4a7c.png → 264e056f-3bcc-4b16-94d4-f767c78ab6f5.png} +0 -0
- /kotonebot/kaa/sprites/{0bbfb5b0-f0f7-4ad9-86f7-a2adb01a6371.png → 26580772-ff20-4dcf-8496-f939e0f2890d.png} +0 -0
- /kotonebot/kaa/sprites/{0a430873-8c12-4ce2-a5de-ab9790c08344.png → 29cb7b0f-5684-4516-8fae-4439f7f28e08.png} +0 -0
- /kotonebot/kaa/sprites/{52620946-b92c-42c7-9fcc-0ff13388fd0d.png → 2d145ade-b926-4276-b872-baec4ef78308.png} +0 -0
- /kotonebot/kaa/sprites/{7e8164f6-a9e4-4600-b776-77b35a490933.png → 2dd34393-ee6f-4b19-8a9c-76556d1bd7d7.png} +0 -0
- /kotonebot/kaa/sprites/{550fc45d-22c0-4a7c-a324-2c4ff107ab39.png → 2e00ac54-d23a-4d51-82f2-829c012552a6.png} +0 -0
- /kotonebot/kaa/sprites/{d58d9987-b1ca-4669-a9a6-9a0fd1847fa6.png → 2f74b04f-1992-4584-8dc6-e789b081ddc3.png} +0 -0
- /kotonebot/kaa/sprites/{668002b6-805b-467b-a703-203fb75444f4.png → 302fde7d-de85-44c7-ba57-fe4b5f6db5fa.png} +0 -0
- /kotonebot/kaa/sprites/{80fb515c-4323-422d-a24c-80a3bf702f1d.png → 3cc9685d-6347-4114-9d39-7683c2dc9df6.png} +0 -0
- /kotonebot/kaa/sprites/{869b1771-b2ce-4872-ab75-2157c47c5b13.png → 42d8b5dc-ef27-4411-b8b5-d981e562990b.png} +0 -0
- /kotonebot/kaa/sprites/{ba955365-1328-4018-83a2-bfe818f8f172.png → 448ea37d-a10d-4102-ac9f-a58403ee8914.png} +0 -0
- /kotonebot/kaa/sprites/{111a03e6-e401-4ad5-b5dc-d5124e8f1ab0.png → 4a310bdd-0a7d-4669-9d57-6ae69bc8f022.png} +0 -0
- /kotonebot/kaa/sprites/{05bfd235-2077-479a-8c43-11fb49316000.png → 4b20b364-5f5f-4b34-960e-b0cfd08b1662.png} +0 -0
- /kotonebot/kaa/sprites/{cd4cbd7b-df80-45bb-a053-cc333a806934.png → 4e8f3e59-edfa-4523-939d-bc46988fc909.png} +0 -0
- /kotonebot/kaa/sprites/{67a81c23-4ce4-45e3-bfa8-1136fb14b831.png → 4eaa5db2-d624-4df4-b2e4-01f72755f1a4.png} +0 -0
- /kotonebot/kaa/sprites/{705d9ecf-faf8-40f4-b701-637cec822c1e.png → 5152e3fa-079c-4592-8df7-656101694df8.png} +0 -0
- /kotonebot/kaa/sprites/{4e21f02d-d5e1-49eb-946a-8edc74d0f1f7.png → 52f7c8b1-af88-43c5-a999-de4da3caa03a.png} +0 -0
- /kotonebot/kaa/sprites/{f74fc514-61f1-4068-bdf4-754fcaf8ac89.png → 534d1310-3503-46d3-8ecd-426d9bcf183e.png} +0 -0
- /kotonebot/kaa/sprites/{9bb1a400-935a-4c5e-838b-fca502c49aba.png → 5868ebfb-5e61-4ac6-b270-acb1e89b738f.png} +0 -0
- /kotonebot/kaa/sprites/{82a5e224-a161-43d4-8e15-2946480336dd.png → 58a5f51d-d1a3-480b-8dde-ecead34d9f02.png} +0 -0
- /kotonebot/kaa/sprites/{c1ad996a-925e-41b5-b26c-d48b8ded21d2.png → 5d3d61e9-bf13-4668-b0c7-fed730ea2ed6.png} +0 -0
- /kotonebot/kaa/sprites/{ad784a29-f0e2-4037-976b-45143db8441c.png → 5dd0cc39-4102-4f0f-8ad3-342bbf825977.png} +0 -0
- /kotonebot/kaa/sprites/{4a235eaa-46f1-4f71-b54d-ada0ade44572.png → 5ee8e9a8-b133-4ab7-a52c-e7e4e1d1ce7b.png} +0 -0
- /kotonebot/kaa/sprites/{09433024-3dfe-4fcb-9d67-cf442b58f31b.png → 60be53a6-98e9-4fc6-9285-85506dc0e335.png} +0 -0
- /kotonebot/kaa/sprites/{90a9c819-ee6b-4b3e-a0e4-ce484c6e413d.png → 6114d53e-dd69-4cee-82a7-7e43b338ff8d.png} +0 -0
- /kotonebot/kaa/sprites/{4c72f39e-1182-4479-867f-8207744ee4fd.png → 6156116e-56dd-4b41-a6de-ccec55dab7c8.png} +0 -0
- /kotonebot/kaa/sprites/{559df9b9-e74d-45af-a300-77d3424e69c6.png → 65d2a5e5-48aa-4390-bb87-bfd8e044e4f6.png} +0 -0
- /kotonebot/kaa/sprites/{c0fc7de5-b842-4c70-9cf2-89d833bfe7a6.png → 66172bc0-3869-4a48-a26d-0dc3fa404ab8.png} +0 -0
- /kotonebot/kaa/sprites/{fdfecda8-2fe0-487e-b5fe-6358274bb9ff.png → 66678caa-7b25-4ff7-8c3a-017f67279349.png} +0 -0
- /kotonebot/kaa/sprites/{f8cbb089-a63e-4e40-9238-26d14da2c459.png → 68838f92-5916-424e-a37e-47e0b3834eb3.png} +0 -0
- /kotonebot/kaa/sprites/{fb927b92-7a82-4869-8c61-581d49cd7201.png → 694d3a07-9697-4bda-984d-967779d530f7.png} +0 -0
- /kotonebot/kaa/sprites/{db94ffb7-a09d-4a51-b420-b930851c9db4.png → 69b022a8-e42f-4985-bd16-b4a530ad8a20.png} +0 -0
- /kotonebot/kaa/sprites/{13535ecd-bdf5-4ff7-aa2c-6ecb20fe9c06.png → 6aa34982-e7ab-4001-808f-8c5cf370d087.png} +0 -0
- /kotonebot/kaa/sprites/{572077d4-7ee2-449a-b4f0-66239b0d7aff.png → 7516c9a1-87ec-4d2f-ac5e-40d1aec27104.png} +0 -0
- /kotonebot/kaa/sprites/{d7f604a6-c663-44db-b1d3-c9792eb3daa4.png → 7aa2435c-da90-4d52-b831-88dbec57dde9.png} +0 -0
- /kotonebot/kaa/sprites/{56a8c0ca-4659-4de2-9bce-55ce86a40d75.png → 7b7dad18-a838-4267-b3c7-6ab43b8361f0.png} +0 -0
- /kotonebot/kaa/sprites/{1f9165e6-77ee-403e-9b76-99c114527dbb.png → 7cabdf91-2f15-4991-b165-eb9cdd557af6.png} +0 -0
- /kotonebot/kaa/sprites/{5ca50448-cc21-499a-a685-c5e1c4240c48.png → 7f6cb60e-6ab3-4db4-86e1-8558250062a4.png} +0 -0
- /kotonebot/kaa/sprites/{a82e06fb-a02e-4be5-a7b4-4d2758914fcf.png → 83acbca8-3567-4262-a1e7-50f158348d45.png} +0 -0
- /kotonebot/kaa/sprites/{b3d1f8d4-1393-4206-b78e-a5495d8fb930.png → 83cf48ba-d1af-4674-a52e-6bb80a626db7.png} +0 -0
- /kotonebot/kaa/sprites/{22d5a7d5-59a0-4137-b02e-1a7af86d4250.png → 83f17b06-2792-4035-bc90-1800279ae131.png} +0 -0
- /kotonebot/kaa/sprites/{1bfce48a-b151-406e-a6f5-863cb68cbbf2.png → 84bb4986-0295-4a45-9486-86e31fcdf1f1.png} +0 -0
- /kotonebot/kaa/sprites/{73f4204e-8281-47cc-9dce-6bb8280c4ce5.png → 8b9c115b-d4ce-492e-88cb-08bd01c43570.png} +0 -0
- /kotonebot/kaa/sprites/{f0786a26-81b5-4e76-b577-db70ab3fab50.png → 8e48af2b-c083-4480-a27c-c59c31a7ede1.png} +0 -0
- /kotonebot/kaa/sprites/{c7aa3f89-8a5e-4685-9297-4fc892bfdd81.png → 8f1e5644-1079-44b1-9d44-97bae3817bc5.png} +0 -0
- /kotonebot/kaa/sprites/{cc829886-23e8-4d47-b4c4-37ca20381180.png → 90def878-cd6e-46e5-9dc4-9c92dd904b80.png} +0 -0
- /kotonebot/kaa/sprites/{d8136469-6d73-4f82-a79c-fee83c3e93a8.png → 920a3974-4875-4d2b-bbac-caa175c03ce0.png} +0 -0
- /kotonebot/kaa/sprites/{c4f28132-4aba-40ba-be6c-b1a31d014635.png → 94433cf2-dd04-4b74-ae14-e5e3d945f579.png} +0 -0
- /kotonebot/kaa/sprites/{ad8ee599-88a8-429c-8982-99086e552dcc.png → 969eb98c-f761-4007-b8bd-2b3fb8d1f08c.png} +0 -0
- /kotonebot/kaa/sprites/{31a52dc8-b7fb-4ff6-a261-eb90a918603c.png → 97adee05-1586-4589-93e6-c6c3a9cdae9d.png} +0 -0
- /kotonebot/kaa/sprites/{02e0aa0d-4094-4bdc-8625-1a063733615b.png → 9aef1aa8-41e1-42a7-83ad-b4e212ee3976.png} +0 -0
- /kotonebot/kaa/sprites/{e2fae2a4-bb19-4f49-b382-47e504ec26d3.png → 9c7f1bcc-0bc8-454b-95fb-56a803030771.png} +0 -0
- /kotonebot/kaa/sprites/{8e8a0b57-0231-4ed6-958e-3196f7c9ca7b.png → 9cd7e1f1-13c4-471e-abee-223ffd9126cb.png} +0 -0
- /kotonebot/kaa/sprites/{5bf00cba-9aab-4ffe-b20b-8f5e74279276.png → 9d5dd50c-3341-41a4-a3dd-ccc00edcf201.png} +0 -0
- /kotonebot/kaa/sprites/{e866d8b1-4f61-45bf-8170-5229f53c047a.png → 9f99b0e9-8cc0-4877-8f8a-d26257ef5386.png} +0 -0
- /kotonebot/kaa/sprites/{1a3d18a4-7d91-4182-9e40-e60d73fed365.png → a1853223-d6b9-4660-af4e-91e59bdddc3c.png} +0 -0
- /kotonebot/kaa/sprites/{ed5d2bf3-405c-4165-88d0-9f8a4be968c6.png → a1bba683-a740-4eaf-8c37-3b1042b963b0.png} +0 -0
- /kotonebot/kaa/sprites/{910d307b-6461-4950-badb-509975d0b797.png → a1cf80ee-64bf-4d54-a7db-ffadf3c97026.png} +0 -0
- /kotonebot/kaa/sprites/{645744c4-3bbf-4ea2-8f46-4725fff874b3.png → a3b15425-0bfd-4668-a3a9-2ab64da8505f.png} +0 -0
- /kotonebot/kaa/sprites/{7cea265a-fca5-43d0-b2da-067c8fff4d72.png → a4075991-98da-4d06-bcde-28054cd124d2.png} +0 -0
- /kotonebot/kaa/sprites/{89c0e675-3fd4-456a-8ae7-3bfb248b7281.png → a7da51c2-8444-4770-a16b-b6543e40197f.png} +0 -0
- /kotonebot/kaa/sprites/{5be4f6c7-2baa-42f3-a5a5-99ca0830cc30.png → a8b3fdce-9e4a-495c-839e-0142f67fee69.png} +0 -0
- /kotonebot/kaa/sprites/{72c1804f-db8c-40fa-bc79-6533f2861a5d.png → aa3b717f-67f8-445f-a6bf-b89f7200d95b.png} +0 -0
- /kotonebot/kaa/sprites/{7736a171-9221-4cda-8288-908997abeff2.png → abcc709c-1f4d-4832-a063-eafbad94ac31.png} +0 -0
- /kotonebot/kaa/sprites/{12e8d3f1-494a-46f5-908f-f621a0c57ec6.png → ad172750-d1a6-4a25-8ce8-9483f4b5affb.png} +0 -0
- /kotonebot/kaa/sprites/{a33ae1c3-ac69-4783-977e-9f0f1b1a2f8d.png → aefdec38-e3b6-4762-aed8-33107686ddac.png} +0 -0
- /kotonebot/kaa/sprites/{22ee3f1b-ddcb-48bf-85dd-8423f85e3199.png → b01c25d2-21bf-4db9-b5b2-ec35817f1b04.png} +0 -0
- /kotonebot/kaa/sprites/{5edef075-4843-4dc9-840f-ae445cbe0730.png → b1e7f24e-f246-406e-9073-58e2f3e4e819.png} +0 -0
- /kotonebot/kaa/sprites/{a105d17d-d44c-4798-aeff-70808c91cb47.png → b5d4265c-82e0-470d-a48e-8aa4edbdef5a.png} +0 -0
- /kotonebot/kaa/sprites/{48c257f8-3583-49f7-b01a-ba7d251086ac.png → b65724b4-ec8f-48a9-9ab2-e7ce5b8f35b2.png} +0 -0
- /kotonebot/kaa/sprites/{469006bc-a011-46e4-bcde-992be888456e.png → b8aada91-eedd-4c53-99f3-f903d547235e.png} +0 -0
- /kotonebot/kaa/sprites/{e5d15c50-45ee-4de9-8b97-4630cbd8f01a.png → ba759205-ce99-4417-8cab-d32fe55dff4c.png} +0 -0
- /kotonebot/kaa/sprites/{afa0f85f-dcb7-48a1-a1ae-624456b9b65c.png → bb0ae68d-44d5-47c6-91dd-a529bc34225b.png} +0 -0
- /kotonebot/kaa/sprites/{79bc56c5-eae6-4389-9116-4a9e182d6bf3.png → bed9575d-ccad-4f4e-825e-47b2a9cb0925.png} +0 -0
- /kotonebot/kaa/sprites/{f9b2f64d-f829-4b13-9704-c5f00fb956be.png → bfec58df-2628-454f-92aa-769cad186448.png} +0 -0
- /kotonebot/kaa/sprites/{9c1fa1d4-2ae5-4093-abaa-cebb51135721.png → c25c7ce5-ad8d-4b2c-83eb-ae6d4871a6c3.png} +0 -0
- /kotonebot/kaa/sprites/{05f8f996-a38a-45d9-8ff9-720b6c4fd599.png → c3ec61aa-f62e-46fd-b359-48864ea7a150.png} +0 -0
- /kotonebot/kaa/sprites/{826bf80a-112e-4356-9111-4b7e692ccc82.png → c484ca57-6b19-46b5-a107-1805a7488d48.png} +0 -0
- /kotonebot/kaa/sprites/{cd8d3d55-f102-419e-a6fd-2ad5ecc4fd2e.png → c571e1c0-ff85-4b2e-97ed-97d33b8d92e3.png} +0 -0
- /kotonebot/kaa/sprites/{0decaa6a-b9a9-4d9b-829a-0fc1f67538b4.png → c5e16f75-9b5a-4853-a144-275f3d20c186.png} +0 -0
- /kotonebot/kaa/sprites/{d747239f-7620-4f23-9ac7-cf2d34889266.png → c5ea3dfd-6761-466e-b44c-86592ef05323.png} +0 -0
- /kotonebot/kaa/sprites/{695f8d57-b2c9-4a6d-9ae9-1b49ee3eb22a.png → ca2adfb8-1e88-4a31-b3cc-1f70568ce4a3.png} +0 -0
- /kotonebot/kaa/sprites/{333fe6d3-0be7-40fb-a2a3-364f98758f2a.png → cb24b3df-1ec3-4ffd-95f1-ca4b0dd5b7da.png} +0 -0
- /kotonebot/kaa/sprites/{0bab7080-9a37-4c27-9821-8df36d49b678.png → cdbbb5ba-5117-488a-8172-48cfc92c14c6.png} +0 -0
- /kotonebot/kaa/sprites/{a3c12040-2b11-4038-b830-215fb7b6f359.png → cf66ae12-48bc-4512-a988-0e5de337c76d.png} +0 -0
- /kotonebot/kaa/sprites/{994aa6a6-0189-4a79-8125-56136973b581.png → d1699baa-467b-46d1-9276-aae78fe8f589.png} +0 -0
- /kotonebot/kaa/sprites/{48d2f007-8c1b-43fc-bd3f-f0b10d2a0c88.png → d3e6e2e9-c5b5-463f-98f4-de72c9320d9d.png} +0 -0
- /kotonebot/kaa/sprites/{b2f7a430-5777-4861-8435-5419394e5ee8.png → d4c47cc7-5b3e-4c3e-8e80-78cb94e7eed9.png} +0 -0
- /kotonebot/kaa/sprites/{fd610de1-b023-4747-8c1b-72f89b1c5647.png → d5d854fc-1b87-43d4-b32d-66eccb66fa41.png} +0 -0
- /kotonebot/kaa/sprites/{c2667677-cbff-4dd4-a401-e0963e7224f4.png → d60a395b-d907-416a-b091-36bfee4948b3.png} +0 -0
- /kotonebot/kaa/sprites/{43d65b3f-bbae-4e29-a7bf-539379c1b072.png → dad4ff8c-07f4-4b07-abd3-5d02bbcc4562.png} +0 -0
- /kotonebot/kaa/sprites/{b44f5e5b-e999-4427-8a9f-f76c6485f4a5.png → dbd537c7-d1d3-4d64-9ccd-43dd4519bedd.png} +0 -0
- /kotonebot/kaa/sprites/{2f81e70a-f2af-420f-9b6b-6338c391911f.png → dde03640-6c3a-4b13-a783-f672816bfec4.png} +0 -0
- /kotonebot/kaa/sprites/{da3e7cee-d77b-4e62-b703-ed257706dd39.png → df97421e-4b74-430e-b3d1-ed9bac6918a7.png} +0 -0
- /kotonebot/kaa/sprites/{0439399f-5fca-43f9-a105-99342ff04d2e.png → dfb14c98-f672-4906-80bd-d2a18bdd62cd.png} +0 -0
- /kotonebot/kaa/sprites/{03458cf9-8ab0-4908-9a56-9c5c7e06bc9b.png → e1e0d751-9006-4fa4-a933-4c0dc5e1f3d2.png} +0 -0
- /kotonebot/kaa/sprites/{f65a4495-f044-44de-bf0d-c869e637d5d3.png → e394ff91-09d3-41e1-bbf5-bc42a71a623b.png} +0 -0
- /kotonebot/kaa/sprites/{de709296-e411-465f-86fc-5daf5ca83cef.png → e5259dfb-3a49-4cfc-9f89-b42958ce6eec.png} +0 -0
- /kotonebot/kaa/sprites/{371a3192-101c-45ef-9da4-2ed389770c0f.png → e5eeb9af-8003-4981-bf56-2e32e76cec5d.png} +0 -0
- /kotonebot/kaa/sprites/{73af305f-840f-4325-abae-b3519fba672a.png → e9c45a73-2011-4584-87b7-c4e566eba87f.png} +0 -0
- /kotonebot/kaa/sprites/{7a479871-547e-4098-9cdd-6c9bb9f7449a.png → eae83523-e58c-4db9-a356-4335a5da3dc8.png} +0 -0
- /kotonebot/kaa/sprites/{e7022542-4317-4a61-8098-f3f733d8400e.png → ed6d3e3d-0f04-4fa4-b0f9-fdbf324649f6.png} +0 -0
- /kotonebot/kaa/sprites/{24cd42fd-bb3b-4fc6-b40d-d3e3e36fe9f1.png → efb28e1d-7b52-4e20-aec5-117c798b7fbe.png} +0 -0
- /kotonebot/kaa/sprites/{6bf5556a-8f4e-41d7-8ab5-d683ad1b47a8.png → f025027f-daa0-4943-8395-5d7d44cc7f15.png} +0 -0
- /kotonebot/kaa/sprites/{b3e72de2-e103-49a3-b0f2-8593cff87c4e.png → f12c2b0b-8273-4197-a8c6-2a8ef2788a9b.png} +0 -0
- /kotonebot/kaa/sprites/{d22ca9e0-001d-43d6-a557-1c76c5a27c81.png → f528ea0e-1e55-4e41-844f-bb1db9185ef8.png} +0 -0
- /kotonebot/kaa/sprites/{b604e25d-680a-4e02-a012-2e593b5bc276.png → f611bbd9-c062-4989-96a4-65ff6c59673c.png} +0 -0
- /kotonebot/kaa/sprites/{b1d1dfab-51f5-4ae9-b718-cdf46774c0ff.png → f6bfbf6a-6e53-4563-9345-3f3756d6ea9a.png} +0 -0
- /kotonebot/kaa/sprites/{7344e180-251b-465a-a8cf-974477f7ad44.png → f72bd3d6-850d-4190-9775-938f474d263c.png} +0 -0
- /kotonebot/kaa/sprites/{96cf0633-6531-499b-b1cc-6670ab710293.png → f8eec9b2-55ca-4195-b098-a5393d02a98a.png} +0 -0
- /kotonebot/kaa/sprites/{92b7d830-3db5-434e-b156-0ac855db480d.png → faf069e1-f623-47c1-b879-cfa246fbc7df.png} +0 -0
- /kotonebot/kaa/sprites/{a00fcf77-2800-4f7d-993d-4636b36045bb.png → faf360e8-7845-4fcf-b0a6-0f5f93c050f1.png} +0 -0
- {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/WHEEL +0 -0
- {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/entry_points.txt +0 -0
- {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/licenses/LICENSE +0 -0
- {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/top_level.txt +0 -0
|
@@ -25,7 +25,7 @@ from typing_extensions import deprecated
|
|
|
25
25
|
import cv2
|
|
26
26
|
from cv2.typing import MatLike
|
|
27
27
|
|
|
28
|
-
from kotonebot.client.device import Device
|
|
28
|
+
from kotonebot.client.device import Device, AndroidDevice, WindowsDevice
|
|
29
29
|
from kotonebot.backend.flow_controller import FlowController
|
|
30
30
|
import kotonebot.backend.image as raw_image
|
|
31
31
|
from kotonebot.backend.image import (
|
|
@@ -46,12 +46,12 @@ from kotonebot.backend.color import (
|
|
|
46
46
|
from kotonebot.backend.ocr import (
|
|
47
47
|
Ocr, OcrResult, OcrResultList, jp, en, StringMatchFunction
|
|
48
48
|
)
|
|
49
|
-
from kotonebot.client.
|
|
49
|
+
from kotonebot.client.registration import AdbBasedImpl, create_device
|
|
50
50
|
from kotonebot.config.manager import load_config, save_config
|
|
51
51
|
from kotonebot.config.base_config import UserConfig
|
|
52
52
|
from kotonebot.backend.core import Image, HintBox
|
|
53
53
|
from kotonebot.errors import KotonebotWarning
|
|
54
|
-
from kotonebot.client
|
|
54
|
+
from kotonebot.client import DeviceImpl
|
|
55
55
|
from kotonebot.backend.preprocessor import PreprocessorProtocol
|
|
56
56
|
from kotonebot.primitives import Rect
|
|
57
57
|
|
|
@@ -717,9 +717,10 @@ class Forwarded:
|
|
|
717
717
|
raise ValueError(f"Forwarded object {self._FORWARD_name} called before initialization.")
|
|
718
718
|
setattr(self._FORWARD_getter(), name, value)
|
|
719
719
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
720
|
+
|
|
721
|
+
T_Device = TypeVar('T_Device', bound=Device)
|
|
722
|
+
class ContextDevice(Generic[T_Device], Device):
|
|
723
|
+
def __init__(self, device: T_Device):
|
|
723
724
|
self._device = device
|
|
724
725
|
|
|
725
726
|
def screenshot(self, *, force: bool = False):
|
|
@@ -746,26 +747,42 @@ class ContextDevice(Device):
|
|
|
746
747
|
current._screenshot = img
|
|
747
748
|
return img
|
|
748
749
|
|
|
749
|
-
def __getattribute__(self, name: str)
|
|
750
|
-
if name in ['_device', 'screenshot']:
|
|
750
|
+
def __getattribute__(self, name: str):
|
|
751
|
+
if name in ['_device', 'screenshot', 'of_android', 'of_windows']:
|
|
751
752
|
return object.__getattribute__(self, name)
|
|
752
753
|
else:
|
|
753
754
|
return getattr(self._device, name)
|
|
754
755
|
|
|
755
756
|
def __setattr__(self, name: str, value: Any):
|
|
756
|
-
if name in ['_device', 'screenshot']:
|
|
757
|
+
if name in ['_device', 'screenshot', 'of_android', 'of_windows']:
|
|
757
758
|
return object.__setattr__(self, name, value)
|
|
758
759
|
else:
|
|
759
760
|
return setattr(self._device, name, value)
|
|
760
761
|
|
|
762
|
+
def of_android(self) -> 'ContextDevice | AndroidDevice':
|
|
763
|
+
"""
|
|
764
|
+
确保此 ContextDevice 底层为 Android 平台。
|
|
765
|
+
同时通过返回的对象可以调用 Android 平台特有的方法。
|
|
766
|
+
"""
|
|
767
|
+
if not isinstance(self._device, AndroidDevice):
|
|
768
|
+
raise ValueError("Device is not AndroidDevice")
|
|
769
|
+
return self
|
|
770
|
+
|
|
771
|
+
def of_windows(self) -> 'ContextDevice | WindowsDevice':
|
|
772
|
+
"""
|
|
773
|
+
确保此 ContextDevice 底层为 Windows 平台。
|
|
774
|
+
同时通过返回的对象可以调用 Windows 平台特有的方法。
|
|
775
|
+
"""
|
|
776
|
+
if not isinstance(self._device, WindowsDevice):
|
|
777
|
+
raise ValueError("Device is not WindowsDevice")
|
|
778
|
+
return self
|
|
761
779
|
|
|
762
780
|
class Context(Generic[T]):
|
|
763
781
|
def __init__(
|
|
764
782
|
self,
|
|
765
783
|
config_path: str,
|
|
766
784
|
config_type: Type[T],
|
|
767
|
-
|
|
768
|
-
device: Optional[Device] = None
|
|
785
|
+
device: Device
|
|
769
786
|
):
|
|
770
787
|
self.__ocr = ContextOcr(self)
|
|
771
788
|
self.__image = ContextImage(self)
|
|
@@ -773,14 +790,7 @@ class Context(Generic[T]):
|
|
|
773
790
|
self.__vars = ContextGlobalVars()
|
|
774
791
|
self.__debug = ContextDebug(self)
|
|
775
792
|
self.__config = ContextConfig[T](self, config_path, config_type)
|
|
776
|
-
|
|
777
|
-
ip = self.config.current.backend.adb_ip
|
|
778
|
-
port = self.config.current.backend.adb_port
|
|
779
|
-
# TODO: 处理链接失败情况
|
|
780
|
-
if screenshot_impl is None:
|
|
781
|
-
screenshot_impl = self.config.current.backend.screenshot_impl
|
|
782
|
-
logger.info(f'Using "{screenshot_impl}" as screenshot implementation')
|
|
783
|
-
self.__device = ContextDevice(device or create_device(f'{ip}:{port}', screenshot_impl))
|
|
793
|
+
self.__device = ContextDevice(device)
|
|
784
794
|
|
|
785
795
|
def inject(
|
|
786
796
|
self,
|
|
@@ -891,8 +901,7 @@ def init_context(
|
|
|
891
901
|
config_path: str = 'config.json',
|
|
892
902
|
config_type: Type[T] = dict[str, Any],
|
|
893
903
|
force: bool = False,
|
|
894
|
-
|
|
895
|
-
target_device: Device | None = None,
|
|
904
|
+
target_device: Device,
|
|
896
905
|
):
|
|
897
906
|
"""
|
|
898
907
|
初始化 Context 模块。
|
|
@@ -903,8 +912,6 @@ def init_context(
|
|
|
903
912
|
默认为 `dict[str, Any]`,即普通的 JSON 数据,不包含任何类型信息。
|
|
904
913
|
:param force: 是否强制重新初始化。
|
|
905
914
|
若为 `True`,则忽略已存在的 Context 实例,并重新创建一个新的实例。
|
|
906
|
-
:param screenshot_impl: 截图实现。
|
|
907
|
-
若为 `None`,则使用默认配置文件中指定的截图实现。
|
|
908
915
|
:param target_device: 目标设备
|
|
909
916
|
"""
|
|
910
917
|
global _c, device, ocr, image, color, vars, debug, config
|
|
@@ -913,8 +920,7 @@ def init_context(
|
|
|
913
920
|
_c = Context(
|
|
914
921
|
config_path=config_path,
|
|
915
922
|
config_type=config_type,
|
|
916
|
-
|
|
917
|
-
device=target_device
|
|
923
|
+
device=target_device,
|
|
918
924
|
)
|
|
919
925
|
device._FORWARD_getter = lambda: _c.device # type: ignore
|
|
920
926
|
ocr._FORWARD_getter = lambda: _c.ocr # type: ignore
|
|
@@ -216,8 +216,6 @@ def wait_message_all_done():
|
|
|
216
216
|
threading.Thread(target=_wait, daemon=True).start()
|
|
217
217
|
|
|
218
218
|
if __name__ == "__main__":
|
|
219
|
-
from kotonebot.backend.context import init_context
|
|
220
|
-
init_context()
|
|
221
219
|
debug_vars.debug.hide_server_log = False
|
|
222
220
|
process = subprocess.Popen(["pylsp", "--port", "5479", "--ws"])
|
|
223
221
|
print("LSP started. PID=", process.pid)
|
kotonebot/backend/dispatch.py
CHANGED
|
@@ -328,20 +328,4 @@ class SimpleDispatcher:
|
|
|
328
328
|
self.result = self.timeout_result
|
|
329
329
|
break
|
|
330
330
|
device.screenshot()
|
|
331
|
-
return self.result
|
|
332
|
-
|
|
333
|
-
if __name__ == '__main__':
|
|
334
|
-
from .context.task_action import action
|
|
335
|
-
from .context import init_context
|
|
336
|
-
init_context()
|
|
337
|
-
@action('inner', dispatcher=True)
|
|
338
|
-
def inner(ctx: DispatcherContext):
|
|
339
|
-
print('inner')
|
|
340
|
-
ctx.finish()
|
|
341
|
-
|
|
342
|
-
@action('test', dispatcher=True)
|
|
343
|
-
def test(ctx: DispatcherContext):
|
|
344
|
-
print('test')
|
|
345
|
-
inner()
|
|
346
|
-
ctx.finish()
|
|
347
|
-
test()
|
|
331
|
+
return self.result
|
|
@@ -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
|