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
|
@@ -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,31 +2,51 @@ 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
|
|
8
9
|
import win32gui
|
|
9
10
|
import numpy as np
|
|
10
|
-
from ahk import AHK
|
|
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
|
-
self.emergency = False
|
|
23
30
|
|
|
24
31
|
# 设置 DPI aware,否则高缩放显示器上返回的坐标会错误
|
|
25
32
|
windll.user32.SetProcessDPIAware()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
# TODO: 这个应该移动到其他地方去
|
|
34
|
+
def _stop():
|
|
35
|
+
from kotonebot.backend.context.context import vars
|
|
36
|
+
vars.flow.request_interrupt()
|
|
37
|
+
self.ahk.msg_box('任务已停止。', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
|
|
38
|
+
|
|
39
|
+
def _toggle_pause():
|
|
40
|
+
from kotonebot.backend.context.context import vars
|
|
41
|
+
if vars.flow.is_paused:
|
|
42
|
+
self.ahk.msg_box('任务即将恢复。\n关闭此消息框后将会继续执行', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
|
|
43
|
+
vars.flow.request_resume()
|
|
44
|
+
else:
|
|
45
|
+
vars.flow.request_pause()
|
|
46
|
+
self.ahk.msg_box('任务已暂停。\n关闭此消息框后再按一次快捷键恢复执行。', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
|
|
47
|
+
|
|
48
|
+
self.ahk.add_hotkey('^F4', _toggle_pause) # Ctrl+F4 暂停/恢复
|
|
49
|
+
self.ahk.add_hotkey('^F3', _stop) # Ctrl+F3 停止
|
|
30
50
|
self.ahk.start_hotkeys()
|
|
31
51
|
# 将点击坐标设置为相对 Client
|
|
32
52
|
self.ahk.set_coord_mode('Mouse', 'Client')
|
|
@@ -43,9 +63,9 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
43
63
|
@property
|
|
44
64
|
def hwnd(self) -> int:
|
|
45
65
|
if self.__hwnd is None:
|
|
46
|
-
self.__hwnd = win32gui.FindWindow(None,
|
|
66
|
+
self.__hwnd = win32gui.FindWindow(None, self.window_title)
|
|
47
67
|
if self.__hwnd is None or self.__hwnd == 0:
|
|
48
|
-
raise RuntimeError('Failed to find window')
|
|
68
|
+
raise RuntimeError(f'Failed to find window: {self.window_title}')
|
|
49
69
|
return self.__hwnd
|
|
50
70
|
|
|
51
71
|
def __client_rect(self) -> tuple[int, int, int, int]:
|
|
@@ -60,14 +80,9 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
60
80
|
"""将 Client 区域坐标转换为屏幕坐标"""
|
|
61
81
|
return win32gui.ClientToScreen(hwnd, (x, y))
|
|
62
82
|
|
|
63
|
-
def __wait_not_emergency(self):
|
|
64
|
-
from time import sleep # TODO: 改为 kotonebot.backend.context.sleep
|
|
65
|
-
while self.emergency:
|
|
66
|
-
sleep(0.2)
|
|
67
|
-
|
|
68
83
|
def screenshot(self) -> MatLike:
|
|
69
|
-
if not self.ahk.win_is_active(
|
|
70
|
-
self.ahk.win_activate(
|
|
84
|
+
if not self.ahk.win_is_active(self.window_title):
|
|
85
|
+
self.ahk.win_activate(self.window_title)
|
|
71
86
|
hwnd = self.hwnd
|
|
72
87
|
|
|
73
88
|
# TODO: 需要检查下面这些 WinAPI 的返回结果
|
|
@@ -123,7 +138,7 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
123
138
|
return 720, 1280
|
|
124
139
|
|
|
125
140
|
def detect_orientation(self) -> None | Literal['portrait'] | Literal['landscape']:
|
|
126
|
-
pos = self.ahk.win_get_position(
|
|
141
|
+
pos = self.ahk.win_get_position(self.window_title)
|
|
127
142
|
if pos is None:
|
|
128
143
|
return None
|
|
129
144
|
w, h = pos.width, pos.height
|
|
@@ -133,7 +148,6 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
133
148
|
return 'portrait'
|
|
134
149
|
|
|
135
150
|
def click(self, x: int, y: int) -> None:
|
|
136
|
-
self.__wait_not_emergency()
|
|
137
151
|
# x, y = self.__client_to_screen(self.hwnd, x, y)
|
|
138
152
|
# (0, 0) 很可能会点到窗口边框上
|
|
139
153
|
if x == 0:
|
|
@@ -141,25 +155,39 @@ class WindowsImpl(Touchable, Screenshotable):
|
|
|
141
155
|
if y == 0:
|
|
142
156
|
y = 2
|
|
143
157
|
x, y = int(x / self.scale_ratio), int(y / self.scale_ratio)
|
|
144
|
-
if not self.ahk.win_is_active(
|
|
145
|
-
self.ahk.win_activate(
|
|
158
|
+
if not self.ahk.win_is_active(self.window_title):
|
|
159
|
+
self.ahk.win_activate(self.window_title)
|
|
146
160
|
self.ahk.click(x, y)
|
|
147
161
|
|
|
148
162
|
def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float | None = None) -> None:
|
|
149
|
-
self.
|
|
150
|
-
|
|
151
|
-
self.ahk.win_activate('gakumas')
|
|
163
|
+
if not self.ahk.win_is_active(self.window_title):
|
|
164
|
+
self.ahk.win_activate(self.window_title)
|
|
152
165
|
x1, y1 = int(x1 / self.scale_ratio), int(y1 / self.scale_ratio)
|
|
153
166
|
x2, y2 = int(x2 / self.scale_ratio), int(y2 / self.scale_ratio)
|
|
154
167
|
# TODO: 这个 speed 的单位是什么?
|
|
155
168
|
self.ahk.mouse_drag(x2, y2, from_position=(x1, y1), coord_mode='Client', speed=10)
|
|
156
169
|
|
|
157
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
|
+
|
|
158
185
|
if __name__ == '__main__':
|
|
159
186
|
from ..device import Device
|
|
160
|
-
from time import sleep
|
|
161
187
|
device = Device()
|
|
162
|
-
|
|
188
|
+
# 在测试环境中直接使用默认路径
|
|
189
|
+
ahk_path = str(resources.files('kaa.res.bin') / 'AutoHotkey.exe')
|
|
190
|
+
impl = WindowsImpl(device, window_title='gakumas', ahk_exe_path=ahk_path)
|
|
163
191
|
device._screenshot = impl
|
|
164
192
|
device._touch = impl
|
|
165
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():
|
kotonebot/kaa/clear_logs.py
CHANGED
kotonebot/kaa/common.py
CHANGED
|
@@ -425,6 +425,20 @@ class StartGameConfig(ConfigBaseModel):
|
|
|
425
425
|
|
|
426
426
|
kuyo_package_name: str = 'org.kuyo.game'
|
|
427
427
|
"""Kuyo包名"""
|
|
428
|
+
|
|
429
|
+
disable_gakumas_localify: bool = False
|
|
430
|
+
"""
|
|
431
|
+
自动检测并禁用 Gakumas Localify 汉化插件。
|
|
432
|
+
|
|
433
|
+
(目前仅对 DMM 版有效。)
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
dmm_game_path: str | None = None
|
|
437
|
+
"""
|
|
438
|
+
DMM 版游戏路径。若不填写,会自动检测。
|
|
439
|
+
|
|
440
|
+
例:`F:\\Games\\gakumas\\gakumas.exe`
|
|
441
|
+
"""
|
|
428
442
|
|
|
429
443
|
class EndGameConfig(ConfigBaseModel):
|
|
430
444
|
exit_kaa: bool = False
|
|
@@ -439,6 +453,13 @@ class EndGameConfig(ConfigBaseModel):
|
|
|
439
453
|
"""关闭系统"""
|
|
440
454
|
hibernate: bool = False
|
|
441
455
|
"""休眠系统"""
|
|
456
|
+
restore_gakumas_localify: bool = False
|
|
457
|
+
"""
|
|
458
|
+
恢复 Gakumas Localify 汉化插件状态至启动前。通常与
|
|
459
|
+
`disable_gakumas_localify` 配对使用。
|
|
460
|
+
|
|
461
|
+
(目前仅对 DMM 版有效。)
|
|
462
|
+
"""
|
|
442
463
|
|
|
443
464
|
class BaseConfig(ConfigBaseModel):
|
|
444
465
|
purchase: PurchaseConfig = PurchaseConfig()
|
kotonebot/kaa/game_ui/common.py
CHANGED
|
@@ -2,10 +2,10 @@ import cv2
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
from cv2.typing import MatLike
|
|
4
4
|
|
|
5
|
-
from kotonebot import
|
|
6
|
-
from kotonebot.backend.color import HsvColor
|
|
7
|
-
from kotonebot.primitives import RectTuple, Rect
|
|
5
|
+
from kotonebot.primitives import Rect
|
|
8
6
|
from kotonebot.backend.core import Image
|
|
7
|
+
from kotonebot.backend.color import HsvColor
|
|
8
|
+
from kotonebot import action, color, image, device
|
|
9
9
|
from kotonebot.backend.preprocessor import HsvColorFilter
|
|
10
10
|
|
|
11
11
|
|
|
@@ -88,7 +88,4 @@ class WhiteFilter(HsvColorFilter):
|
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
if __name__ == '__main__':
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
init_context()
|
|
94
|
-
manual_context().begin()
|
|
91
|
+
pass
|
kotonebot/kaa/game_ui/dialog.py
CHANGED
|
@@ -1,45 +1,65 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
1
3
|
from kotonebot.kaa.tasks import R
|
|
2
4
|
from kotonebot import device, image
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
def expect_yes(*, msg: str | None = None):
|
|
5
9
|
"""
|
|
6
10
|
点击对话框上的✔️按钮。若不存在,会等待其出现,直至超时异常。
|
|
7
11
|
|
|
8
12
|
前置条件:当前打开了任意对话框\n
|
|
9
13
|
结束状态:点击了肯定意义按钮(✔️图标,橙色背景)后瞬间
|
|
14
|
+
|
|
15
|
+
:param msg: 成功点击后输出的日志信息。信息中的动词建议使用过去式。
|
|
10
16
|
"""
|
|
11
17
|
device.click(image.expect(R.Common.IconButtonCheck))
|
|
18
|
+
if msg is not None:
|
|
19
|
+
logger.debug(msg)
|
|
12
20
|
|
|
13
|
-
def yes() -> bool:
|
|
21
|
+
def yes(*, msg: str | None = None) -> bool:
|
|
14
22
|
"""
|
|
15
23
|
点击对话框上的✔️按钮。
|
|
16
24
|
|
|
17
25
|
前置条件:当前打开了任意对话框\n
|
|
18
26
|
结束状态:点击了肯定意义按钮(✔️图标,橙色背景)后瞬间
|
|
27
|
+
|
|
28
|
+
:param msg: 成功点击后输出的日志信息。信息中的动词建议使用过去式。
|
|
19
29
|
"""
|
|
20
30
|
if image.find(R.Common.IconButtonCheck):
|
|
21
31
|
device.click()
|
|
32
|
+
if msg is not None:
|
|
33
|
+
logger.debug(msg)
|
|
22
34
|
return True
|
|
23
35
|
return False
|
|
24
36
|
|
|
25
|
-
def expect_no():
|
|
37
|
+
def expect_no(*, msg: str | None = None):
|
|
26
38
|
"""
|
|
27
39
|
点击对话框上的✖️按钮。若不存在,会等待其出现,直至超时异常。
|
|
28
40
|
|
|
29
41
|
前置条件:当前打开了任意对话框\n
|
|
30
42
|
结束状态:点击了否定意义按钮(✖️图标,白色背景)后瞬间
|
|
43
|
+
|
|
44
|
+
:param msg: 成功点击后输出的日志信息。信息中的动词建议使用过去式。
|
|
31
45
|
"""
|
|
32
46
|
device.click(image.expect(R.Common.IconButtonCross))
|
|
47
|
+
if msg is not None:
|
|
48
|
+
logger.debug(msg)
|
|
33
49
|
|
|
34
|
-
def no():
|
|
50
|
+
def no(*, msg: str | None = None):
|
|
35
51
|
"""
|
|
36
52
|
点击对话框上的✖️按钮。
|
|
37
53
|
|
|
38
54
|
前置条件:当前打开了任意对话框\n
|
|
39
55
|
结束状态:点击了否定意义按钮(✖️图标,白色背景)后瞬间
|
|
56
|
+
|
|
57
|
+
:param msg: 成功点击后输出的日志信息。信息中的动词建议使用过去式。
|
|
40
58
|
"""
|
|
41
59
|
if image.find(R.Common.IconButtonCross):
|
|
42
60
|
device.click()
|
|
61
|
+
if msg is not None:
|
|
62
|
+
logger.debug(msg)
|
|
43
63
|
return True
|
|
44
64
|
return False
|
|
45
65
|
|