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
|
@@ -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)
|
|
@@ -5,16 +5,27 @@ from typing_extensions import override
|
|
|
5
5
|
import cv2
|
|
6
6
|
import numpy as np
|
|
7
7
|
from cv2.typing import MatLike
|
|
8
|
+
from adbutils._device import AdbDevice as AdbUtilsDevice
|
|
8
9
|
|
|
9
|
-
from ..device import
|
|
10
|
-
from ..protocol import
|
|
10
|
+
from ..device import AndroidDevice
|
|
11
|
+
from ..protocol import AndroidCommandable, Touchable, Screenshotable
|
|
12
|
+
from ..registration import register_impl, ImplConfig
|
|
13
|
+
from dataclasses import dataclass
|
|
11
14
|
|
|
12
15
|
logger = logging.getLogger(__name__)
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
# 定义配置模型
|
|
18
|
+
@dataclass
|
|
19
|
+
class AdbImplConfig(ImplConfig):
|
|
20
|
+
addr: str
|
|
21
|
+
connect: bool = True
|
|
22
|
+
disconnect: bool = True
|
|
23
|
+
device_serial: str | None = None
|
|
24
|
+
timeout: float = 180
|
|
25
|
+
|
|
26
|
+
class AdbImpl(AndroidCommandable, Touchable, Screenshotable):
|
|
27
|
+
def __init__(self, adb_connection: AdbUtilsDevice):
|
|
28
|
+
self.adb = adb_connection
|
|
18
29
|
|
|
19
30
|
@override
|
|
20
31
|
def launch_app(self, package_name: str) -> None:
|
|
@@ -36,6 +47,10 @@ class AdbImpl(Commandable, Touchable, Screenshotable):
|
|
|
36
47
|
package = activity.split('/')[0]
|
|
37
48
|
return package
|
|
38
49
|
|
|
50
|
+
def adb_shell(self, cmd: str) -> str:
|
|
51
|
+
"""执行 ADB shell 命令"""
|
|
52
|
+
return cast(str, self.adb.shell(cmd))
|
|
53
|
+
|
|
39
54
|
@override
|
|
40
55
|
def detect_orientation(self):
|
|
41
56
|
# 判断方向:https://stackoverflow.com/questions/10040624/check-if-device-is-landscape-via-adb
|
|
@@ -50,7 +65,9 @@ class AdbImpl(Commandable, Touchable, Screenshotable):
|
|
|
50
65
|
def screen_size(self) -> tuple[int, int]:
|
|
51
66
|
ret = cast(str, self.adb.shell("wm size")).strip('Physical size: ')
|
|
52
67
|
spiltted = tuple(map(int, ret.split("x")))
|
|
53
|
-
|
|
68
|
+
# 检测当前方向
|
|
69
|
+
orientation = self.detect_orientation()
|
|
70
|
+
landscape = orientation == 'landscape'
|
|
54
71
|
spiltted = tuple(sorted(spiltted, reverse=landscape))
|
|
55
72
|
if len(spiltted) != 2:
|
|
56
73
|
raise ValueError(f"Invalid screen size: {ret}")
|
|
@@ -66,3 +83,46 @@ class AdbImpl(Commandable, Touchable, Screenshotable):
|
|
|
66
83
|
if duration is not None:
|
|
67
84
|
logger.warning("Swipe duration is not supported with AdbDevice. Ignoring duration.")
|
|
68
85
|
self.adb.shell(f"input touchscreen swipe {x1} {y1} {x2} {y2}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _create_adb_device_base(config: AdbImplConfig, impl_class: type) -> AndroidDevice:
|
|
89
|
+
"""
|
|
90
|
+
通用的 ADB 设备创建工厂函数。
|
|
91
|
+
其他任意基于 ADB 的 Impl 可以直接复用这个函数。
|
|
92
|
+
|
|
93
|
+
:param config: ADB 实现配置
|
|
94
|
+
:param impl_class: 实现类或工厂函数。构造函数接收 adb_connection 参数。
|
|
95
|
+
"""
|
|
96
|
+
from adbutils import adb
|
|
97
|
+
|
|
98
|
+
if config.disconnect:
|
|
99
|
+
logger.debug('adb disconnect %s', config.addr)
|
|
100
|
+
adb.disconnect(config.addr)
|
|
101
|
+
if config.connect:
|
|
102
|
+
logger.debug('adb connect %s', config.addr)
|
|
103
|
+
result = adb.connect(config.addr)
|
|
104
|
+
if 'cannot connect to' in result:
|
|
105
|
+
raise ValueError(result)
|
|
106
|
+
serial = config.device_serial or config.addr
|
|
107
|
+
logger.debug('adb wait for %s', serial)
|
|
108
|
+
adb.wait_for(serial, timeout=config.timeout)
|
|
109
|
+
devices = adb.device_list()
|
|
110
|
+
logger.debug('adb device_list: %s', devices)
|
|
111
|
+
d = [d for d in devices if d.serial == serial]
|
|
112
|
+
if len(d) == 0:
|
|
113
|
+
raise ValueError(f"Device {config.addr} not found")
|
|
114
|
+
d = d[0]
|
|
115
|
+
|
|
116
|
+
device = AndroidDevice(d)
|
|
117
|
+
impl = impl_class(d)
|
|
118
|
+
device._touch = impl
|
|
119
|
+
device._screenshot = impl
|
|
120
|
+
device.commands = impl
|
|
121
|
+
|
|
122
|
+
return device
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@register_impl('adb', config_model=AdbImplConfig)
|
|
126
|
+
def create_adb_device(config: AdbImplConfig) -> AndroidDevice:
|
|
127
|
+
"""AdbImpl 工厂函数"""
|
|
128
|
+
return _create_adb_device_base(config, AdbImpl)
|
|
@@ -11,8 +11,9 @@ import numpy as np
|
|
|
11
11
|
from cv2.typing import MatLike
|
|
12
12
|
from adbutils._utils import adb_path
|
|
13
13
|
|
|
14
|
-
from .adb import AdbImpl
|
|
15
|
-
from
|
|
14
|
+
from .adb import AdbImpl, AdbImplConfig, _create_adb_device_base
|
|
15
|
+
from adbutils._device import AdbDevice as AdbUtilsDevice
|
|
16
|
+
from ..registration import register_impl
|
|
16
17
|
from kotonebot import logging
|
|
17
18
|
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
@@ -27,8 +28,8 @@ done
|
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
30
|
class AdbRawImpl(AdbImpl):
|
|
30
|
-
def __init__(self,
|
|
31
|
-
super().__init__(
|
|
31
|
+
def __init__(self, adb_connection: AdbUtilsDevice):
|
|
32
|
+
super().__init__(adb_connection)
|
|
32
33
|
self.__worker: Thread | None = None
|
|
33
34
|
self.__process: subprocess.Popen | None = None
|
|
34
35
|
self.__data: MatLike | None = None
|
|
@@ -156,4 +157,10 @@ class AdbRawImpl(AdbImpl):
|
|
|
156
157
|
logger.verbose(f"adb raw screenshot wait time: {time.time() - start_time:.4f}s")
|
|
157
158
|
data = self.__data
|
|
158
159
|
self.__data = None
|
|
159
|
-
return data
|
|
160
|
+
return data
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@register_impl('adb_raw', config_model=AdbImplConfig)
|
|
164
|
+
def create_adb_raw_device(config: AdbImplConfig):
|
|
165
|
+
"""AdbRawImpl 工厂函数"""
|
|
166
|
+
return _create_adb_device_base(config, AdbRawImpl)
|
|
@@ -14,6 +14,7 @@ import xmlrpc.server
|
|
|
14
14
|
from typing import Literal, cast, Any, Tuple
|
|
15
15
|
from functools import cached_property
|
|
16
16
|
from threading import Thread
|
|
17
|
+
from dataclasses import dataclass
|
|
17
18
|
|
|
18
19
|
import cv2
|
|
19
20
|
import numpy as np
|
|
@@ -22,10 +23,18 @@ from cv2.typing import MatLike
|
|
|
22
23
|
from kotonebot import logging
|
|
23
24
|
from ..device import Device, WindowsDevice
|
|
24
25
|
from ..protocol import Touchable, Screenshotable
|
|
25
|
-
from
|
|
26
|
+
from ..registration import register_impl, ImplConfig
|
|
27
|
+
from .windows import WindowsImpl, WindowsImplConfig, create_windows_device
|
|
26
28
|
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
28
30
|
|
|
31
|
+
# 定义配置模型
|
|
32
|
+
@dataclass
|
|
33
|
+
class RemoteWindowsImplConfig(ImplConfig):
|
|
34
|
+
windows_impl_config: WindowsImplConfig
|
|
35
|
+
host: str = "localhost"
|
|
36
|
+
port: int = 8000
|
|
37
|
+
|
|
29
38
|
def _encode_image(image: MatLike) -> str:
|
|
30
39
|
"""Encode an image as a base64 string."""
|
|
31
40
|
success, buffer = cv2.imencode('.png', image)
|
|
@@ -48,13 +57,13 @@ class RemoteWindowsServer:
|
|
|
48
57
|
This class wraps a WindowsImpl instance and exposes its methods via XML-RPC.
|
|
49
58
|
"""
|
|
50
59
|
|
|
51
|
-
def __init__(self, host="localhost", port=8000):
|
|
60
|
+
def __init__(self, windows_impl_config: WindowsImplConfig, host="localhost", port=8000):
|
|
52
61
|
"""Initialize the server with the given host and port."""
|
|
53
62
|
self.host = host
|
|
54
63
|
self.port = port
|
|
55
64
|
self.server = None
|
|
56
65
|
self.device = WindowsDevice()
|
|
57
|
-
self.impl =
|
|
66
|
+
self.impl = create_windows_device(windows_impl_config)
|
|
58
67
|
self.device._screenshot = self.impl
|
|
59
68
|
self.device._touch = self.impl
|
|
60
69
|
|
|
@@ -180,18 +189,11 @@ class RemoteWindowsImpl(Touchable, Screenshotable):
|
|
|
180
189
|
raise RuntimeError(f"Failed to swipe from ({x1}, {y1}) to ({x2}, {y2})")
|
|
181
190
|
|
|
182
191
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
logging.basicConfig(level=logging.INFO)
|
|
192
|
-
|
|
193
|
-
server = RemoteWindowsServer(args.host, args.port)
|
|
194
|
-
try:
|
|
195
|
-
server.start()
|
|
196
|
-
except KeyboardInterrupt:
|
|
197
|
-
logger.info("Server stopped by user")
|
|
192
|
+
# 编写并注册创建函数
|
|
193
|
+
@register_impl('remote_windows', config_model=RemoteWindowsImplConfig)
|
|
194
|
+
def create_remote_windows_device(config: RemoteWindowsImplConfig) -> Device:
|
|
195
|
+
device = WindowsDevice()
|
|
196
|
+
remote_impl = RemoteWindowsImpl(device, config.host, config.port)
|
|
197
|
+
device._touch = remote_impl
|
|
198
|
+
device._screenshot = remote_impl
|
|
199
|
+
return device
|
|
@@ -8,6 +8,8 @@ from cv2.typing import MatLike
|
|
|
8
8
|
from kotonebot import logging
|
|
9
9
|
from ..device import Device
|
|
10
10
|
from ..protocol import Screenshotable, Commandable, Touchable
|
|
11
|
+
from ..registration import register_impl
|
|
12
|
+
from .adb import AdbImplConfig, _create_adb_device_base
|
|
11
13
|
|
|
12
14
|
logger = logging.getLogger(__name__)
|
|
13
15
|
|
|
@@ -83,3 +85,9 @@ class UiAutomator2Impl(Screenshotable, Commandable, Touchable):
|
|
|
83
85
|
滑动屏幕
|
|
84
86
|
"""
|
|
85
87
|
self.u2_client.swipe(x1, y1, x2, y2, duration=duration or 0.1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@register_impl('uiautomator2', config_model=AdbImplConfig)
|
|
91
|
+
def create_uiautomator2_device(config: AdbImplConfig) -> Device:
|
|
92
|
+
"""UiAutomator2Impl 工厂函数"""
|
|
93
|
+
return _create_adb_device_base(config, UiAutomator2Impl)
|
|
@@ -2,6 +2,7 @@ from ctypes import windll
|
|
|
2
2
|
from typing import Literal
|
|
3
3
|
from importlib import resources
|
|
4
4
|
from functools import cached_property
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
|
|
6
7
|
import cv2
|
|
7
8
|
import win32ui
|
|
@@ -10,14 +11,21 @@ import numpy as np
|
|
|
10
11
|
from ahk import AHK, MsgBoxIcon
|
|
11
12
|
from cv2.typing import MatLike
|
|
12
13
|
|
|
13
|
-
from ..device import Device
|
|
14
|
+
from ..device import Device, WindowsDevice
|
|
14
15
|
from ..protocol import Commandable, Touchable, Screenshotable
|
|
16
|
+
from ..registration import register_impl, ImplConfig
|
|
17
|
+
|
|
18
|
+
# 1. 定义配置模型
|
|
19
|
+
@dataclass
|
|
20
|
+
class WindowsImplConfig(ImplConfig):
|
|
21
|
+
window_title: str
|
|
22
|
+
ahk_exe_path: str
|
|
15
23
|
|
|
16
24
|
class WindowsImpl(Touchable, Screenshotable):
|
|
17
|
-
def __init__(self, device: Device):
|
|
25
|
+
def __init__(self, device: Device, window_title: str, ahk_exe_path: str):
|
|
18
26
|
self.__hwnd: int | None = None
|
|
19
|
-
|
|
20
|
-
self.ahk = AHK(executable_path=
|
|
27
|
+
self.window_title = window_title
|
|
28
|
+
self.ahk = AHK(executable_path=ahk_exe_path)
|
|
21
29
|
self.device = device
|
|
22
30
|
|
|
23
31
|
# 设置 DPI aware,否则高缩放显示器上返回的坐标会错误
|
|
@@ -55,9 +63,9 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
55
63
|
@property
|
|
56
64
|
def hwnd(self) -> int:
|
|
57
65
|
if self.__hwnd is None:
|
|
58
|
-
self.__hwnd = win32gui.FindWindow(None,
|
|
66
|
+
self.__hwnd = win32gui.FindWindow(None, self.window_title)
|
|
59
67
|
if self.__hwnd is None or self.__hwnd == 0:
|
|
60
|
-
raise RuntimeError('Failed to find window')
|
|
68
|
+
raise RuntimeError(f'Failed to find window: {self.window_title}')
|
|
61
69
|
return self.__hwnd
|
|
62
70
|
|
|
63
71
|
def __client_rect(self) -> tuple[int, int, int, int]:
|
|
@@ -73,8 +81,8 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
73
81
|
return win32gui.ClientToScreen(hwnd, (x, y))
|
|
74
82
|
|
|
75
83
|
def screenshot(self) -> MatLike:
|
|
76
|
-
if not self.ahk.win_is_active(
|
|
77
|
-
self.ahk.win_activate(
|
|
84
|
+
if not self.ahk.win_is_active(self.window_title):
|
|
85
|
+
self.ahk.win_activate(self.window_title)
|
|
78
86
|
hwnd = self.hwnd
|
|
79
87
|
|
|
80
88
|
# TODO: 需要检查下面这些 WinAPI 的返回结果
|
|
@@ -130,7 +138,7 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
130
138
|
return 720, 1280
|
|
131
139
|
|
|
132
140
|
def detect_orientation(self) -> None | Literal['portrait'] | Literal['landscape']:
|
|
133
|
-
pos = self.ahk.win_get_position(
|
|
141
|
+
pos = self.ahk.win_get_position(self.window_title)
|
|
134
142
|
if pos is None:
|
|
135
143
|
return None
|
|
136
144
|
w, h = pos.width, pos.height
|
|
@@ -147,24 +155,39 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
147
155
|
if y == 0:
|
|
148
156
|
y = 2
|
|
149
157
|
x, y = int(x / self.scale_ratio), int(y / self.scale_ratio)
|
|
150
|
-
if not self.ahk.win_is_active(
|
|
151
|
-
self.ahk.win_activate(
|
|
158
|
+
if not self.ahk.win_is_active(self.window_title):
|
|
159
|
+
self.ahk.win_activate(self.window_title)
|
|
152
160
|
self.ahk.click(x, y)
|
|
153
161
|
|
|
154
162
|
def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float | None = None) -> None:
|
|
155
|
-
if not self.ahk.win_is_active(
|
|
156
|
-
self.ahk.win_activate(
|
|
163
|
+
if not self.ahk.win_is_active(self.window_title):
|
|
164
|
+
self.ahk.win_activate(self.window_title)
|
|
157
165
|
x1, y1 = int(x1 / self.scale_ratio), int(y1 / self.scale_ratio)
|
|
158
166
|
x2, y2 = int(x2 / self.scale_ratio), int(y2 / self.scale_ratio)
|
|
159
167
|
# TODO: 这个 speed 的单位是什么?
|
|
160
168
|
self.ahk.mouse_drag(x2, y2, from_position=(x1, y1), coord_mode='Client', speed=10)
|
|
161
169
|
|
|
162
170
|
|
|
171
|
+
# 3. 编写并注册创建函数
|
|
172
|
+
@register_impl('windows', config_model=WindowsImplConfig)
|
|
173
|
+
def create_windows_device(config: WindowsImplConfig) -> Device:
|
|
174
|
+
device = WindowsDevice()
|
|
175
|
+
impl = WindowsImpl(
|
|
176
|
+
device,
|
|
177
|
+
window_title=config.window_title,
|
|
178
|
+
ahk_exe_path=config.ahk_exe_path
|
|
179
|
+
)
|
|
180
|
+
device._touch = impl
|
|
181
|
+
device._screenshot = impl
|
|
182
|
+
return device
|
|
183
|
+
|
|
184
|
+
|
|
163
185
|
if __name__ == '__main__':
|
|
164
186
|
from ..device import Device
|
|
165
|
-
from time import sleep
|
|
166
187
|
device = Device()
|
|
167
|
-
|
|
188
|
+
# 在测试环境中直接使用默认路径
|
|
189
|
+
ahk_path = str(resources.files('kaa.res.bin') / 'AutoHotkey.exe')
|
|
190
|
+
impl = WindowsImpl(device, window_title='gakumas', ahk_exe_path=ahk_path)
|
|
168
191
|
device._screenshot = impl
|
|
169
192
|
device._touch = impl
|
|
170
193
|
device.swipe_scaled(0.5, 0.8, 0.5, 0.2)
|
kotonebot/client/protocol.py
CHANGED
|
@@ -28,6 +28,19 @@ class Commandable(Protocol):
|
|
|
28
28
|
def launch_app(self, package_name: str) -> None: ...
|
|
29
29
|
def current_package(self) -> str | None: ...
|
|
30
30
|
|
|
31
|
+
@runtime_checkable
|
|
32
|
+
class AndroidCommandable(Protocol):
|
|
33
|
+
"""定义 Android 平台的特定命令"""
|
|
34
|
+
def launch_app(self, package_name: str) -> None: ...
|
|
35
|
+
def current_package(self) -> str | None: ...
|
|
36
|
+
def adb_shell(self, cmd: str) -> str: ...
|
|
37
|
+
|
|
38
|
+
@runtime_checkable
|
|
39
|
+
class WindowsCommandable(Protocol):
|
|
40
|
+
"""定义 Windows 平台的特定命令"""
|
|
41
|
+
def get_foreground_window(self) -> tuple[int, str]: ...
|
|
42
|
+
def exec_command(self, command: str) -> tuple[int, str, str]: ...
|
|
43
|
+
|
|
31
44
|
@runtime_checkable
|
|
32
45
|
class Screenshotable(Protocol):
|
|
33
46
|
def __init__(self, device: 'Device'): ...
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import TypeVar, Callable, Dict, Type, Any, overload, Literal, cast, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from ..errors import KotonebotError
|
|
5
|
+
from .device import Device
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .implements.adb import AdbImplConfig
|
|
8
|
+
from .implements.remote_windows import RemoteWindowsImplConfig
|
|
9
|
+
from .implements.windows import WindowsImplConfig
|
|
10
|
+
|
|
11
|
+
AdbBasedImpl = Literal['adb', 'adb_raw', 'uiautomator2']
|
|
12
|
+
DeviceImpl = str | AdbBasedImpl | Literal['windows', 'remote_windows']
|
|
13
|
+
|
|
14
|
+
# --- 核心类型定义 ---
|
|
15
|
+
|
|
16
|
+
class ImplRegistrationError(KotonebotError):
|
|
17
|
+
"""与 impl 注册相关的错误"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ImplConfig:
|
|
22
|
+
"""所有设备实现配置模型的名义上的基类,便于类型约束。"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
T_Config = TypeVar("T_Config", bound=ImplConfig)
|
|
26
|
+
|
|
27
|
+
# 定义两种创建者函数类型
|
|
28
|
+
CreatorWithConfig = Callable[[Any], Device]
|
|
29
|
+
CreatorWithoutConfig = Callable[[], Device]
|
|
30
|
+
|
|
31
|
+
# --- 底层 API: 公开的注册表 ---
|
|
32
|
+
|
|
33
|
+
# 注册表结构: {'impl_name': (创建函数, 配置模型类 或 None)}
|
|
34
|
+
DEVICE_CREATORS: Dict[str, tuple[Callable[..., Device], Type[ImplConfig] | None]] = {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def register_impl(name: str, config_model: Type[ImplConfig] | None = None) -> Callable[..., Any]:
|
|
38
|
+
"""
|
|
39
|
+
一个统一的装饰器,用于向 DEVICE_CREATORS 注册表中注册一个设备实现。
|
|
40
|
+
|
|
41
|
+
:param name: 实现的名称 (e.g., 'windows', 'adb')
|
|
42
|
+
:param config_model: (可选) 与该实现关联的 dataclass 配置模型
|
|
43
|
+
"""
|
|
44
|
+
def decorator(creator_func: Callable[..., Device]) -> Callable[..., Device]:
|
|
45
|
+
if name in DEVICE_CREATORS:
|
|
46
|
+
raise ImplRegistrationError(f"实现 '{name}' 已被注册。")
|
|
47
|
+
DEVICE_CREATORS[name] = (creator_func, config_model)
|
|
48
|
+
return creator_func
|
|
49
|
+
return decorator
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# --- 高层 API: 带 overload 的便利函数 ---
|
|
53
|
+
|
|
54
|
+
# 为需要配置的已知 impl 提供 overload
|
|
55
|
+
@overload
|
|
56
|
+
def create_device(impl_name: Literal['windows'], config: 'WindowsImplConfig') -> Device: ...
|
|
57
|
+
|
|
58
|
+
@overload
|
|
59
|
+
def create_device(impl_name: Literal['remote_windows'], config: 'RemoteWindowsImplConfig') -> Device: ...
|
|
60
|
+
|
|
61
|
+
@overload
|
|
62
|
+
def create_device(impl_name: AdbBasedImpl, config: 'AdbImplConfig') -> Device: ...
|
|
63
|
+
|
|
64
|
+
# 函数的实际实现
|
|
65
|
+
def create_device(impl_name: DeviceImpl, config: ImplConfig | None = None) -> Device:
|
|
66
|
+
"""
|
|
67
|
+
根据名称和可选的配置对象,统一创建设备。
|
|
68
|
+
"""
|
|
69
|
+
creator_tuple = DEVICE_CREATORS.get(impl_name)
|
|
70
|
+
if not creator_tuple:
|
|
71
|
+
raise ImplRegistrationError(f"未找到名为 '{impl_name}' 的实现。")
|
|
72
|
+
|
|
73
|
+
creator_func, registered_config_model = creator_tuple
|
|
74
|
+
|
|
75
|
+
# 情况 A: 实现需要配置
|
|
76
|
+
if registered_config_model is not None:
|
|
77
|
+
creator_with_config = cast(CreatorWithConfig, creator_func)
|
|
78
|
+
if config is None:
|
|
79
|
+
raise ValueError(f"实现 '{impl_name}' 需要一个配置对象,但传入的是 None。")
|
|
80
|
+
if not isinstance(config, registered_config_model):
|
|
81
|
+
raise TypeError(f"为 '{impl_name}' 传入的配置类型错误,应为 '{registered_config_model.__name__}',实际为 '{type(config).__name__}'。")
|
|
82
|
+
return creator_with_config(config)
|
|
83
|
+
|
|
84
|
+
# 情况 B: 实现无需配置
|
|
85
|
+
else:
|
|
86
|
+
creator_without_config = cast(CreatorWithoutConfig, creator_func)
|
|
87
|
+
if config is not None:
|
|
88
|
+
print(f"提示:实现 '{impl_name}' 无需配置,但你提供了一个配置对象,它将被忽略。")
|
|
89
|
+
return creator_without_config()
|
kotonebot/config/base_config.py
CHANGED
|
@@ -3,7 +3,7 @@ from typing import Generic, TypeVar, Literal
|
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, ConfigDict
|
|
5
5
|
|
|
6
|
-
from kotonebot.client
|
|
6
|
+
from kotonebot.client import DeviceImpl
|
|
7
7
|
|
|
8
8
|
T = TypeVar('T')
|
|
9
9
|
BackendType = Literal['custom', 'mumu12', 'leidian', 'dmm']
|
|
@@ -44,6 +44,10 @@ class BackendConfig(ConfigBaseModel):
|
|
|
44
44
|
"""模拟器 exe 文件路径"""
|
|
45
45
|
emulator_args: str = ""
|
|
46
46
|
"""模拟器启动时的命令行参数"""
|
|
47
|
+
windows_window_title: str = 'gakumas'
|
|
48
|
+
"""Windows 截图方式的窗口标题"""
|
|
49
|
+
windows_ahk_path: str | None = None
|
|
50
|
+
"""Windows 截图方式的 AutoHotkey 可执行文件路径,为 None 时使用默认路径"""
|
|
47
51
|
|
|
48
52
|
class PushConfig(ConfigBaseModel):
|
|
49
53
|
"""推送配置。"""
|
kotonebot/debug_entry.py
CHANGED
|
@@ -17,10 +17,15 @@ def run_script(script_path: str) -> None:
|
|
|
17
17
|
|
|
18
18
|
print(f"正在运行脚本: {script_path}")
|
|
19
19
|
# 运行脚本
|
|
20
|
-
from kotonebot.backend.context import init_context
|
|
20
|
+
from kotonebot.backend.context import init_context, manual_context
|
|
21
|
+
from kotonebot.kaa.main.kaa import Kaa
|
|
21
22
|
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s] [%(lineno)d] %(message)s')
|
|
22
23
|
logging.getLogger('kotonebot').setLevel(logging.DEBUG)
|
|
23
|
-
|
|
24
|
+
config_path = './config.json'
|
|
25
|
+
kaa_instance = Kaa(config_path)
|
|
26
|
+
init_context(config_type=BaseConfig, target_device=kaa_instance._on_create_device())
|
|
27
|
+
kaa_instance._on_after_init_context()
|
|
28
|
+
manual_context().begin()
|
|
24
29
|
runpy.run_module(module_name, run_name="__main__")
|
|
25
30
|
|
|
26
31
|
def main():
|