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.
Files changed (202) hide show
  1. kotonebot/backend/bot.py +3 -3
  2. kotonebot/backend/context/context.py +52 -36
  3. kotonebot/backend/debug/server.py +3 -5
  4. kotonebot/backend/dispatch.py +1 -17
  5. kotonebot/backend/flow_controller.py +195 -0
  6. kotonebot/backend/loop.py +277 -0
  7. kotonebot/client/__init__.py +4 -1
  8. kotonebot/client/device.py +23 -21
  9. kotonebot/client/host/__init__.py +2 -1
  10. kotonebot/client/host/custom.py +26 -3
  11. kotonebot/client/host/leidian_host.py +22 -11
  12. kotonebot/client/host/mumu12_host.py +25 -3
  13. kotonebot/client/host/protocol.py +40 -18
  14. kotonebot/client/implements/__init__.py +6 -0
  15. kotonebot/client/implements/adb.py +67 -7
  16. kotonebot/client/implements/adb_raw.py +12 -5
  17. kotonebot/client/implements/remote_windows.py +20 -18
  18. kotonebot/client/implements/uiautomator2.py +8 -0
  19. kotonebot/client/implements/windows.py +56 -28
  20. kotonebot/client/protocol.py +13 -0
  21. kotonebot/client/registration.py +89 -0
  22. kotonebot/config/base_config.py +5 -1
  23. kotonebot/debug_entry.py +7 -2
  24. kotonebot/kaa/clear_logs.py +0 -3
  25. kotonebot/kaa/common.py +21 -0
  26. kotonebot/kaa/game_ui/common.py +4 -7
  27. kotonebot/kaa/game_ui/dialog.py +24 -4
  28. kotonebot/kaa/game_ui/idols_overview.py +28 -12
  29. kotonebot/kaa/game_ui/primary_button.py +55 -0
  30. kotonebot/kaa/game_ui/scrollable.py +0 -5
  31. kotonebot/kaa/main/cli.py +9 -4
  32. kotonebot/kaa/main/dmm_host.py +33 -7
  33. kotonebot/kaa/main/gr.py +156 -26
  34. kotonebot/kaa/main/kaa.py +113 -41
  35. kotonebot/kaa/metadata.py +27 -0
  36. kotonebot/kaa/resources/__pycache__/__init__.cpython-310.pyc +0 -0
  37. kotonebot/kaa/resources/game.db +0 -0
  38. kotonebot/kaa/resources/game_ver.txt +0 -0
  39. kotonebot/kaa/resources/idol_cards/i_card-skin-kllj-3-012_0.png +0 -0
  40. kotonebot/kaa/resources/idol_cards/i_card-skin-kllj-3-012_1.png +0 -0
  41. kotonebot/kaa/resources/idol_cards/i_card-skin-ssmk-3-012_0.png +0 -0
  42. kotonebot/kaa/resources/idol_cards/i_card-skin-ssmk-3-012_1.png +0 -0
  43. kotonebot/kaa/sprites/24e99232-9434-457f-a9a0-69dd7ecf675f.png +0 -0
  44. kotonebot/kaa/sprites/2ededcf5-1d80-4e2a-9c83-2a31998331ce.png +0 -0
  45. kotonebot/kaa/sprites/3b473fe6-e147-477f-b088-9b8fb042a4f6.png +0 -0
  46. kotonebot/kaa/sprites/55f7db71-0a18-4b3d-b847-57959b8d2e32.png +0 -0
  47. kotonebot/kaa/sprites/6cd80be8-c9b3-4ba5-bf17-3ffc9b000743.png +0 -0
  48. kotonebot/kaa/sprites/74ec3510-583d-4a76-ac69-38480fbf1387.png +0 -0
  49. kotonebot/kaa/sprites/a0bd6a5f-784d-4f0a-9d66-10f4b80c8d3e.png +0 -0
  50. kotonebot/kaa/sprites/d3424d31-0502-4623-996e-f0194e5085ce.png +0 -0
  51. kotonebot/kaa/sprites/e6b45405-cd9f-4c6e-a9f1-6ec953747c65.png +0 -0
  52. kotonebot/kaa/sprites/f5c16d2f-ebc5-4617-9b96-971696af7c52.png +0 -0
  53. kotonebot/kaa/tasks/R.py +157 -135
  54. kotonebot/kaa/tasks/end_game.py +14 -0
  55. kotonebot/kaa/tasks/produce/common.py +1 -0
  56. kotonebot/kaa/tasks/produce/in_purodyuusu.py +0 -4
  57. kotonebot/kaa/tasks/produce/non_lesson_actions.py +1 -4
  58. kotonebot/kaa/tasks/produce/produce.py +119 -70
  59. kotonebot/kaa/tasks/start_game.py +56 -7
  60. kotonebot/kaa/util/paths.py +6 -1
  61. kotonebot/tools/mirror.py +23 -13
  62. kotonebot/util.py +32 -0
  63. {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/METADATA +38 -1
  64. {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/RECORD +200 -183
  65. ksaa-2025.6.23.0.dist-info/licenses/LICENSE +674 -0
  66. kotonebot/client/factory.py +0 -92
  67. kotonebot/kaa/sprites/b260d390-2b1a-4974-92db-d68ff7b5b8e6.png +0 -0
  68. /kotonebot/kaa/sprites/{f6d36222-847e-4300-8f90-c98c7a8f36e3.png → 0235ae3d-1741-4a81-8053-f60bc395ee85.png} +0 -0
  69. /kotonebot/kaa/sprites/{48a02c33-b7af-4ab5-bda1-3a2ec62a7076.png → 038c69e6-7cb0-4c8d-99da-b537e48a86b7.png} +0 -0
  70. /kotonebot/kaa/sprites/{b99f0684-8200-4080-9b54-808c4ec12c99.png → 080b3cf5-ce35-4382-9bfa-d6a12efb09b3.png} +0 -0
  71. /kotonebot/kaa/sprites/{7c65fc91-602b-4d1b-82f9-c9b561a55d9b.png → 09fb5e41-6caa-4246-a415-8317b12d8d81.png} +0 -0
  72. /kotonebot/kaa/sprites/{5e74dc61-0ad5-4e57-b331-e3d2ed67b6d2.png → 0a6c6b1e-7a80-428e-a483-1858bfb8ac59.png} +0 -0
  73. /kotonebot/kaa/sprites/{aad29ac0-b9ad-4c38-93f5-e8036ad7b96b.png → 0ad2f603-a3ad-4a92-a398-c411145fd8e9.png} +0 -0
  74. /kotonebot/kaa/sprites/{e98037f8-3b4a-4de8-adc1-ca0f736b6c98.png → 0d4b669e-5e43-4458-b65a-5565797335ae.png} +0 -0
  75. /kotonebot/kaa/sprites/{382bc600-72f6-433f-809e-9fe2fdb31b32.png → 0fbcb3f6-9ca0-43c7-975b-e7c15c26c897.png} +0 -0
  76. /kotonebot/kaa/sprites/{6d39d7a2-0b1f-4f49-9ab8-062bca9b5470.png → 15465879-eb84-4ca6-ae0f-d3c5ac71e723.png} +0 -0
  77. /kotonebot/kaa/sprites/{474ce25b-8a3d-435e-8d7a-ab6566ae61a0.png → 1639278d-67e0-4452-a2b5-ec4cf4011f42.png} +0 -0
  78. /kotonebot/kaa/sprites/{7d4f53fb-02bc-4139-b32c-3345a5f92a79.png → 17414e9e-b046-49e0-9596-cad1f4673a9e.png} +0 -0
  79. /kotonebot/kaa/sprites/{9a72b63e-8bd1-47dc-8298-cdf02220201e.png → 17a8b0a5-5b71-4612-8a68-16c51f03c71b.png} +0 -0
  80. /kotonebot/kaa/sprites/{a1610e9f-93f0-4ef2-b32a-b2effd1673e8.png → 1aaceb21-dc5c-4a58-96d3-d2c66b2fb357.png} +0 -0
  81. /kotonebot/kaa/sprites/{237636f2-aec0-40be-b7bb-e08607a89307.png → 1aae0ab3-f619-47e0-8bf6-3d3791f7d445.png} +0 -0
  82. /kotonebot/kaa/sprites/{872c37e7-e596-4449-86f4-5ba65836a7a7.png → 1b6be6dd-2711-441a-9fa6-3281a6ad10c7.png} +0 -0
  83. /kotonebot/kaa/sprites/{a829f038-5421-4cc4-a494-105a973351e2.png → 1bb00a74-2fdc-4973-84ae-dc1edee15f81.png} +0 -0
  84. /kotonebot/kaa/sprites/{e5922ce7-9aa7-4806-acdd-9c35408a0f37.png → 1ccdcacf-4f7d-442c-8e2f-09aa7fe14e56.png} +0 -0
  85. /kotonebot/kaa/sprites/{1df6ff4b-17f8-4ee6-8df0-0b53e26b7f1e.png → 1d5f7454-672d-4e87-a368-ec4bb32f9b1f.png} +0 -0
  86. /kotonebot/kaa/sprites/{51f38e84-56c8-4c80-9a67-35196ef54b89.png → 1f2981d8-5eb6-4332-aa9b-17f4db2d4163.png} +0 -0
  87. /kotonebot/kaa/sprites/{f4b226eb-ed9e-4e5e-9ba0-1b777e401aa3.png → 25910d8e-d57f-409f-b0be-ee68c5af4920.png} +0 -0
  88. /kotonebot/kaa/sprites/{e9e399f7-4e2f-4392-bb8f-850c4a272dfa.png → 264e056f-3bcc-4b16-94d4-f767c78ab6f5.png} +0 -0
  89. /kotonebot/kaa/sprites/{cc0ac4cf-01fd-48f9-8707-2d6a2aa0ba8c.png → 26580772-ff20-4dcf-8496-f939e0f2890d.png} +0 -0
  90. /kotonebot/kaa/sprites/{7dd31493-9f3b-4382-a43a-f8eb1c745ad8.png → 29cb7b0f-5684-4516-8fae-4439f7f28e08.png} +0 -0
  91. /kotonebot/kaa/sprites/{415c45a8-9b71-4e0b-8cd2-f80a0a4aafb7.png → 2d145ade-b926-4276-b872-baec4ef78308.png} +0 -0
  92. /kotonebot/kaa/sprites/{d2732870-dd6b-4b7b-b21e-4ff2a4958b32.png → 2dd34393-ee6f-4b19-8a9c-76556d1bd7d7.png} +0 -0
  93. /kotonebot/kaa/sprites/{0d1918db-e000-4452-8490-ace623127e76.png → 2e00ac54-d23a-4d51-82f2-829c012552a6.png} +0 -0
  94. /kotonebot/kaa/sprites/{267fcd93-6a5c-4954-a06b-e97f327e9141.png → 2f74b04f-1992-4584-8dc6-e789b081ddc3.png} +0 -0
  95. /kotonebot/kaa/sprites/{08c2352f-0150-453a-aa0e-948dd0deef49.png → 302fde7d-de85-44c7-ba57-fe4b5f6db5fa.png} +0 -0
  96. /kotonebot/kaa/sprites/{e508161a-e595-4daf-a364-625aaf67e481.png → 3cc9685d-6347-4114-9d39-7683c2dc9df6.png} +0 -0
  97. /kotonebot/kaa/sprites/{d0c085fa-3089-4adf-979c-d4b274b32c3b.png → 42d8b5dc-ef27-4411-b8b5-d981e562990b.png} +0 -0
  98. /kotonebot/kaa/sprites/{2ef87b7f-d69e-4da2-878f-1cc35e48b15c.png → 448ea37d-a10d-4102-ac9f-a58403ee8914.png} +0 -0
  99. /kotonebot/kaa/sprites/{6dcc0bb3-984f-4c92-b0a3-74281d684182.png → 4a310bdd-0a7d-4669-9d57-6ae69bc8f022.png} +0 -0
  100. /kotonebot/kaa/sprites/{52e2b832-7837-47ca-a089-1f1593a99b08.png → 4b20b364-5f5f-4b34-960e-b0cfd08b1662.png} +0 -0
  101. /kotonebot/kaa/sprites/{34ec068a-d6fd-47ac-bd2f-0c31f93c19f3.png → 4e8f3e59-edfa-4523-939d-bc46988fc909.png} +0 -0
  102. /kotonebot/kaa/sprites/{157cfa99-d40c-48c2-8cf4-70835535522f.png → 4eaa5db2-d624-4df4-b2e4-01f72755f1a4.png} +0 -0
  103. /kotonebot/kaa/sprites/{56a70bc5-bd16-493b-b5b6-770f42f13de5.png → 5152e3fa-079c-4592-8df7-656101694df8.png} +0 -0
  104. /kotonebot/kaa/sprites/{b5009ffd-7e53-41ae-b78d-918ba0e7eda4.png → 52f7c8b1-af88-43c5-a999-de4da3caa03a.png} +0 -0
  105. /kotonebot/kaa/sprites/{465f1030-e538-4b22-a6f7-0087287b2e12.png → 534d1310-3503-46d3-8ecd-426d9bcf183e.png} +0 -0
  106. /kotonebot/kaa/sprites/{465d4e1a-c5f9-41b7-aa31-ce26d5c69c45.png → 5868ebfb-5e61-4ac6-b270-acb1e89b738f.png} +0 -0
  107. /kotonebot/kaa/sprites/{3e10014e-2063-424c-aeda-df985d351c12.png → 58a5f51d-d1a3-480b-8dde-ecead34d9f02.png} +0 -0
  108. /kotonebot/kaa/sprites/{4ffd7c97-86dd-4171-92e7-8e8e83e8f394.png → 5d3d61e9-bf13-4668-b0c7-fed730ea2ed6.png} +0 -0
  109. /kotonebot/kaa/sprites/{f4a1201f-9305-4ec5-8962-2bef0144ab46.png → 5dd0cc39-4102-4f0f-8ad3-342bbf825977.png} +0 -0
  110. /kotonebot/kaa/sprites/{dc17dea0-7749-4ecb-bbc0-1a52fb992dcf.png → 5ee8e9a8-b133-4ab7-a52c-e7e4e1d1ce7b.png} +0 -0
  111. /kotonebot/kaa/sprites/{172318e0-5e7b-4b61-9a17-6e2e66b27e69.png → 60be53a6-98e9-4fc6-9285-85506dc0e335.png} +0 -0
  112. /kotonebot/kaa/sprites/{8da0b45e-3d90-40f0-9781-c1ff4cd0e060.png → 6114d53e-dd69-4cee-82a7-7e43b338ff8d.png} +0 -0
  113. /kotonebot/kaa/sprites/{26433c9e-0ea2-4da4-9207-8f3fbad04235.png → 6156116e-56dd-4b41-a6de-ccec55dab7c8.png} +0 -0
  114. /kotonebot/kaa/sprites/{bdb09790-f702-4af2-ab67-0b3f05fd93ca.png → 65d2a5e5-48aa-4390-bb87-bfd8e044e4f6.png} +0 -0
  115. /kotonebot/kaa/sprites/{5a4a763f-b69b-4405-994d-470a78476f67.png → 66172bc0-3869-4a48-a26d-0dc3fa404ab8.png} +0 -0
  116. /kotonebot/kaa/sprites/{e0a5c772-49d3-468a-bcf5-0e3f24365253.png → 66678caa-7b25-4ff7-8c3a-017f67279349.png} +0 -0
  117. /kotonebot/kaa/sprites/{7f0783fe-89d9-4405-b7a3-6334ba543826.png → 68838f92-5916-424e-a37e-47e0b3834eb3.png} +0 -0
  118. /kotonebot/kaa/sprites/{c4fed66b-e195-4288-8b1f-ef4b0ef35174.png → 694d3a07-9697-4bda-984d-967779d530f7.png} +0 -0
  119. /kotonebot/kaa/sprites/{37e3a757-bcbc-4a37-bbf9-a38162a8627e.png → 69b022a8-e42f-4985-bd16-b4a530ad8a20.png} +0 -0
  120. /kotonebot/kaa/sprites/{5eddc669-dff6-462c-a495-81ddf951429e.png → 6aa34982-e7ab-4001-808f-8c5cf370d087.png} +0 -0
  121. /kotonebot/kaa/sprites/{428f770b-e149-417e-bb8a-38c1cba024e1.png → 7516c9a1-87ec-4d2f-ac5e-40d1aec27104.png} +0 -0
  122. /kotonebot/kaa/sprites/{63bfee9f-546e-4c3c-a82f-c2707bc62b90.png → 7aa2435c-da90-4d52-b831-88dbec57dde9.png} +0 -0
  123. /kotonebot/kaa/sprites/{d3b97b8f-f13a-413d-a097-5c456b8aea0d.png → 7b7dad18-a838-4267-b3c7-6ab43b8361f0.png} +0 -0
  124. /kotonebot/kaa/sprites/{ef539419-d100-43d5-83a1-14e65b85d816.png → 7cabdf91-2f15-4991-b165-eb9cdd557af6.png} +0 -0
  125. /kotonebot/kaa/sprites/{12356f5d-4188-4aa5-abb3-d219ca0167ca.png → 7f6cb60e-6ab3-4db4-86e1-8558250062a4.png} +0 -0
  126. /kotonebot/kaa/sprites/{1d6d5160-9525-42bf-a8d4-43c347d213eb.png → 83acbca8-3567-4262-a1e7-50f158348d45.png} +0 -0
  127. /kotonebot/kaa/sprites/{4b121018-f5c8-4c31-a5d9-48947982b191.png → 83cf48ba-d1af-4674-a52e-6bb80a626db7.png} +0 -0
  128. /kotonebot/kaa/sprites/{76c07ba7-afea-405c-806a-7691ce73f83a.png → 83f17b06-2792-4035-bc90-1800279ae131.png} +0 -0
  129. /kotonebot/kaa/sprites/{c287899b-b9a3-4ee2-a8fd-ebda89099b4b.png → 84bb4986-0295-4a45-9486-86e31fcdf1f1.png} +0 -0
  130. /kotonebot/kaa/sprites/{7b46a3e5-18d3-45a0-9500-1d7c854a12bb.png → 8b9c115b-d4ce-492e-88cb-08bd01c43570.png} +0 -0
  131. /kotonebot/kaa/sprites/{263708c5-0177-4c6a-8df3-611ec314fa56.png → 8e48af2b-c083-4480-a27c-c59c31a7ede1.png} +0 -0
  132. /kotonebot/kaa/sprites/{ced345fa-b375-457d-8b72-802424512e6c.png → 8f1e5644-1079-44b1-9d44-97bae3817bc5.png} +0 -0
  133. /kotonebot/kaa/sprites/{c94488b2-0ff0-4467-bbb0-405d3a2c55a3.png → 90def878-cd6e-46e5-9dc4-9c92dd904b80.png} +0 -0
  134. /kotonebot/kaa/sprites/{a69febc4-2d01-4628-be92-29b421604c4d.png → 920a3974-4875-4d2b-bbac-caa175c03ce0.png} +0 -0
  135. /kotonebot/kaa/sprites/{ddb2e9ee-e16a-4745-8598-b3f9c2f011be.png → 94433cf2-dd04-4b74-ae14-e5e3d945f579.png} +0 -0
  136. /kotonebot/kaa/sprites/{4ddc656f-ef7e-4580-ad12-9e382ea76e4f.png → 969eb98c-f761-4007-b8bd-2b3fb8d1f08c.png} +0 -0
  137. /kotonebot/kaa/sprites/{7d968a7f-f4a8-40a0-b9bf-e73ec1f2d7b5.png → 97adee05-1586-4589-93e6-c6c3a9cdae9d.png} +0 -0
  138. /kotonebot/kaa/sprites/{d364470a-d077-405f-a242-0d4fad32bc40.png → 9aef1aa8-41e1-42a7-83ad-b4e212ee3976.png} +0 -0
  139. /kotonebot/kaa/sprites/{9ce49f31-6b3a-4bc7-a9c8-9330d6f650f7.png → 9c7f1bcc-0bc8-454b-95fb-56a803030771.png} +0 -0
  140. /kotonebot/kaa/sprites/{99c36126-0173-49f3-a6b8-819bf9bd9306.png → 9cd7e1f1-13c4-471e-abee-223ffd9126cb.png} +0 -0
  141. /kotonebot/kaa/sprites/{370afc82-eea9-4063-afb7-5d5fec075510.png → 9d5dd50c-3341-41a4-a3dd-ccc00edcf201.png} +0 -0
  142. /kotonebot/kaa/sprites/{a5d05106-5a60-414c-972d-a886664abde4.png → 9f99b0e9-8cc0-4877-8f8a-d26257ef5386.png} +0 -0
  143. /kotonebot/kaa/sprites/{3720275d-cbf7-4513-a4a4-09f712b2286a.png → a1853223-d6b9-4660-af4e-91e59bdddc3c.png} +0 -0
  144. /kotonebot/kaa/sprites/{588f3405-4110-48dd-99ec-f89c004cf02e.png → a1bba683-a740-4eaf-8c37-3b1042b963b0.png} +0 -0
  145. /kotonebot/kaa/sprites/{60e43a33-18ee-45f1-bd9a-57bb6539611b.png → a1cf80ee-64bf-4d54-a7db-ffadf3c97026.png} +0 -0
  146. /kotonebot/kaa/sprites/{14cc953d-0ff6-493f-9478-691b5cbd0d07.png → a3b15425-0bfd-4668-a3a9-2ab64da8505f.png} +0 -0
  147. /kotonebot/kaa/sprites/{f400e1fe-a04d-442b-a6de-a448813443fd.png → a4075991-98da-4d06-bcde-28054cd124d2.png} +0 -0
  148. /kotonebot/kaa/sprites/{cecd2638-070f-4710-9142-2dc492660241.png → a7da51c2-8444-4770-a16b-b6543e40197f.png} +0 -0
  149. /kotonebot/kaa/sprites/{9d7a7bbb-b48b-458c-9964-963a57e10a7d.png → a8b3fdce-9e4a-495c-839e-0142f67fee69.png} +0 -0
  150. /kotonebot/kaa/sprites/{e55da40e-5fb5-48e0-91b1-2db0b3b32750.png → aa3b717f-67f8-445f-a6bf-b89f7200d95b.png} +0 -0
  151. /kotonebot/kaa/sprites/{8cc764de-f162-40a4-ba6f-40d60eac5633.png → abcc709c-1f4d-4832-a063-eafbad94ac31.png} +0 -0
  152. /kotonebot/kaa/sprites/{e6e66cdf-69c8-4862-a770-1f81c62f96ac.png → ad172750-d1a6-4a25-8ce8-9483f4b5affb.png} +0 -0
  153. /kotonebot/kaa/sprites/{eb229797-bffc-4a48-96de-85480af1608a.png → aefdec38-e3b6-4762-aed8-33107686ddac.png} +0 -0
  154. /kotonebot/kaa/sprites/{23217053-c549-4ac7-b8c0-3324728e6acd.png → b01c25d2-21bf-4db9-b5b2-ec35817f1b04.png} +0 -0
  155. /kotonebot/kaa/sprites/{9d908390-6f0f-46e6-a8fc-a7dfe23eda18.png → b1e7f24e-f246-406e-9073-58e2f3e4e819.png} +0 -0
  156. /kotonebot/kaa/sprites/{863550ee-e9b8-4aa2-8ebb-e906b09dde3b.png → b5d4265c-82e0-470d-a48e-8aa4edbdef5a.png} +0 -0
  157. /kotonebot/kaa/sprites/{608de0a3-ce93-4344-8f9b-3cb3fab5ad12.png → b65724b4-ec8f-48a9-9ab2-e7ce5b8f35b2.png} +0 -0
  158. /kotonebot/kaa/sprites/{22ab93e6-0192-48e1-a85f-73ee8db00395.png → b8aada91-eedd-4c53-99f3-f903d547235e.png} +0 -0
  159. /kotonebot/kaa/sprites/{09a01092-0d32-4894-a47e-9983b39753df.png → ba759205-ce99-4417-8cab-d32fe55dff4c.png} +0 -0
  160. /kotonebot/kaa/sprites/{934e5383-b63c-472e-afdb-d184c92bdfe3.png → bb0ae68d-44d5-47c6-91dd-a529bc34225b.png} +0 -0
  161. /kotonebot/kaa/sprites/{55d105dc-c488-47d8-9b95-aba5707cfc2a.png → bed9575d-ccad-4f4e-825e-47b2a9cb0925.png} +0 -0
  162. /kotonebot/kaa/sprites/{3a0e917b-f419-4398-8638-77f696d47cd4.png → bfec58df-2628-454f-92aa-769cad186448.png} +0 -0
  163. /kotonebot/kaa/sprites/{1e9e795c-5dc1-4851-8d49-86181b9f4efe.png → c25c7ce5-ad8d-4b2c-83eb-ae6d4871a6c3.png} +0 -0
  164. /kotonebot/kaa/sprites/{c30d08f7-5c07-487a-86c3-122c082bb07b.png → c3ec61aa-f62e-46fd-b359-48864ea7a150.png} +0 -0
  165. /kotonebot/kaa/sprites/{685a7a90-8196-4a6a-a67a-4e7078965982.png → c484ca57-6b19-46b5-a107-1805a7488d48.png} +0 -0
  166. /kotonebot/kaa/sprites/{9e74a9e6-6ea5-4551-9578-8e561195bd3d.png → c571e1c0-ff85-4b2e-97ed-97d33b8d92e3.png} +0 -0
  167. /kotonebot/kaa/sprites/{a2ea2e0f-4f92-4769-9bc4-1ee10c43712c.png → c5e16f75-9b5a-4853-a144-275f3d20c186.png} +0 -0
  168. /kotonebot/kaa/sprites/{37f421f8-f615-43d1-a10f-f9a915a940d1.png → c5ea3dfd-6761-466e-b44c-86592ef05323.png} +0 -0
  169. /kotonebot/kaa/sprites/{276a66cc-95b1-4f86-b700-afa9c9f18149.png → ca2adfb8-1e88-4a31-b3cc-1f70568ce4a3.png} +0 -0
  170. /kotonebot/kaa/sprites/{bbfcc1ea-b441-4e81-85d2-52ce3bf4eb62.png → cb24b3df-1ec3-4ffd-95f1-ca4b0dd5b7da.png} +0 -0
  171. /kotonebot/kaa/sprites/{9f6aef36-f2e5-4208-9748-b6bbdb5546fd.png → cdbbb5ba-5117-488a-8172-48cfc92c14c6.png} +0 -0
  172. /kotonebot/kaa/sprites/{3851e552-fd41-4a15-a6b6-9636c773f67a.png → cf66ae12-48bc-4512-a988-0e5de337c76d.png} +0 -0
  173. /kotonebot/kaa/sprites/{74db3d1c-d6be-4d1f-92be-9bb3ffad7f51.png → d1699baa-467b-46d1-9276-aae78fe8f589.png} +0 -0
  174. /kotonebot/kaa/sprites/{8383a31c-a075-4b5f-b839-cbeb63a7aef3.png → d3e6e2e9-c5b5-463f-98f4-de72c9320d9d.png} +0 -0
  175. /kotonebot/kaa/sprites/{51b836ff-5f2c-4612-85f1-9db74761f300.png → d4c47cc7-5b3e-4c3e-8e80-78cb94e7eed9.png} +0 -0
  176. /kotonebot/kaa/sprites/{2e0f97d4-ce0b-4930-addd-cbdaedffc918.png → d5d854fc-1b87-43d4-b32d-66eccb66fa41.png} +0 -0
  177. /kotonebot/kaa/sprites/{593f4c04-4ca4-4bb0-8fea-ad3af32316f7.png → d60a395b-d907-416a-b091-36bfee4948b3.png} +0 -0
  178. /kotonebot/kaa/sprites/{19fae7eb-addd-4ea2-99ac-0f9262a4d9d6.png → dad4ff8c-07f4-4b07-abd3-5d02bbcc4562.png} +0 -0
  179. /kotonebot/kaa/sprites/{8778f70d-6bb8-4378-aed7-d77c798ab032.png → dbd537c7-d1d3-4d64-9ccd-43dd4519bedd.png} +0 -0
  180. /kotonebot/kaa/sprites/{7250e776-3195-406c-ac6f-ec7dbed49cbe.png → dde03640-6c3a-4b13-a783-f672816bfec4.png} +0 -0
  181. /kotonebot/kaa/sprites/{4b87be27-868a-40fb-b7e4-67c963c6c4b2.png → df97421e-4b74-430e-b3d1-ed9bac6918a7.png} +0 -0
  182. /kotonebot/kaa/sprites/{7079f76b-e2a3-487c-8616-28ce505f5a0e.png → dfb14c98-f672-4906-80bd-d2a18bdd62cd.png} +0 -0
  183. /kotonebot/kaa/sprites/{b3f1b38f-77d0-4dd5-8c19-8a35bbc39516.png → e1e0d751-9006-4fa4-a933-4c0dc5e1f3d2.png} +0 -0
  184. /kotonebot/kaa/sprites/{aa8c4533-bd58-496e-a460-6fa47f7ded83.png → e394ff91-09d3-41e1-bbf5-bc42a71a623b.png} +0 -0
  185. /kotonebot/kaa/sprites/{72b7124a-baf2-4d60-9553-2634370a6d49.png → e5259dfb-3a49-4cfc-9f89-b42958ce6eec.png} +0 -0
  186. /kotonebot/kaa/sprites/{c072da5f-bd35-4e8b-8f32-6c5df18c3aea.png → e5eeb9af-8003-4981-bf56-2e32e76cec5d.png} +0 -0
  187. /kotonebot/kaa/sprites/{46ed11a3-f89e-4d5c-b534-da88d38b382b.png → e9c45a73-2011-4584-87b7-c4e566eba87f.png} +0 -0
  188. /kotonebot/kaa/sprites/{e3cfea3a-3dc4-412b-b226-f9034f5b51f1.png → eae83523-e58c-4db9-a356-4335a5da3dc8.png} +0 -0
  189. /kotonebot/kaa/sprites/{f3b4bb3b-f8d6-4ed0-8bde-0d3322bd8391.png → ed6d3e3d-0f04-4fa4-b0f9-fdbf324649f6.png} +0 -0
  190. /kotonebot/kaa/sprites/{fe031296-8000-4fdc-ab3f-b4aae2af6e5d.png → efb28e1d-7b52-4e20-aec5-117c798b7fbe.png} +0 -0
  191. /kotonebot/kaa/sprites/{b4afb6bd-e049-4b2e-b1ca-26333c7afcb0.png → f025027f-daa0-4943-8395-5d7d44cc7f15.png} +0 -0
  192. /kotonebot/kaa/sprites/{cd53bad6-81fa-4e15-8fe7-5a992367bf90.png → f12c2b0b-8273-4197-a8c6-2a8ef2788a9b.png} +0 -0
  193. /kotonebot/kaa/sprites/{86bcda3a-c7a6-4abf-ae4c-968788f3c667.png → f528ea0e-1e55-4e41-844f-bb1db9185ef8.png} +0 -0
  194. /kotonebot/kaa/sprites/{9b798f35-e745-4964-931d-3e0ae811174d.png → f611bbd9-c062-4989-96a4-65ff6c59673c.png} +0 -0
  195. /kotonebot/kaa/sprites/{de0b3d75-7dc8-4433-a275-2584e9d7ed0b.png → f6bfbf6a-6e53-4563-9345-3f3756d6ea9a.png} +0 -0
  196. /kotonebot/kaa/sprites/{6e338dd7-6834-4e9b-8ac1-afecdff16c43.png → f72bd3d6-850d-4190-9775-938f474d263c.png} +0 -0
  197. /kotonebot/kaa/sprites/{86501f18-e5fd-4a66-a360-20e34935f1a9.png → f8eec9b2-55ca-4195-b098-a5393d02a98a.png} +0 -0
  198. /kotonebot/kaa/sprites/{a1a03d83-6a3b-481a-a807-931f2b0f7cd2.png → faf069e1-f623-47c1-b879-cfa246fbc7df.png} +0 -0
  199. /kotonebot/kaa/sprites/{8e04cb5c-ce45-49d9-81a9-2e5f717d3eb2.png → faf360e8-7845-4fcf-b0a6-0f5f93c050f1.png} +0 -0
  200. {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/WHEEL +0 -0
  201. {ksaa-2025.5.23.3.dist-info → ksaa-2025.6.23.0.dist-info}/entry_points.txt +0 -0
  202. {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 Device
10
- from ..protocol import Commandable, Touchable, Screenshotable
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
- class AdbImpl(Commandable, Touchable, Screenshotable):
15
- def __init__(self, device: Device):
16
- self.device = device
17
- self.adb = device.adb
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
- landscape = self.device.orientation == 'landscape'
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 ..device import Device
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, device: Device):
31
- super().__init__(device)
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 .windows import WindowsImpl
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 = WindowsImpl(self.device)
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
- if __name__ == "__main__":
184
- import argparse
185
-
186
- parser = argparse.ArgumentParser(description="Remote Windows XML-RPC Server")
187
- parser.add_argument("--host", default="0.0.0.0", help="Host to bind to")
188
- parser.add_argument("--port", type=int, default=8000, help="Port to bind to")
189
- args = parser.parse_args()
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
- # TODO: 硬编码路径
20
- self.ahk = AHK(executable_path=str(resources.files('kaa.res.bin') / 'AutoHotkey.exe'))
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
- def toggle_emergency():
27
- self.emergency = True
28
- self.ahk.msg_box('已启用紧急暂停模式')
29
- self.ahk.add_hotkey('^F4', toggle_emergency)
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, 'gakumas')
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('gakumas'):
70
- self.ahk.win_activate('gakumas')
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('gakumas')
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('gakumas'):
145
- self.ahk.win_activate('gakumas')
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.__wait_not_emergency()
150
- if not self.ahk.win_is_active('gakumas'):
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
- impl = WindowsImpl(device)
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)
@@ -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()
@@ -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.factory import DeviceImpl
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
- init_context(config_type=BaseConfig)
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():
@@ -28,7 +28,4 @@ def clear_logs():
28
28
  logger.info('Clearing logs done.')
29
29
 
30
30
  if __name__ == '__main__':
31
- from kotonebot.backend.context import init_context, manual_context
32
- init_context()
33
- manual_context().begin()
34
31
  clear_logs()
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()
@@ -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 action, color, image
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
- from kotonebot.backend.context import init_context, manual_context, device
92
-
93
- init_context()
94
- manual_context().begin()
91
+ pass
@@ -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
- def expect_yes():
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