ksaa 2025.5.16.1__py3-none-any.whl → 2025.5.23.1__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 (191) hide show
  1. kotonebot/__init__.py +0 -2
  2. kotonebot/backend/bot.py +19 -34
  3. kotonebot/backend/color.py +20 -17
  4. kotonebot/backend/context/context.py +41 -8
  5. kotonebot/backend/core.py +7 -30
  6. kotonebot/backend/dispatch.py +1 -1
  7. kotonebot/backend/image.py +26 -26
  8. kotonebot/backend/ocr.py +18 -17
  9. kotonebot/backend/preprocessor.py +0 -1
  10. kotonebot/client/__init__.py +8 -1
  11. kotonebot/client/device.py +7 -20
  12. kotonebot/client/factory.py +33 -5
  13. kotonebot/client/host/__init__.py +8 -1
  14. kotonebot/client/host/custom.py +7 -2
  15. kotonebot/client/host/leidian_host.py +200 -0
  16. kotonebot/client/host/mumu12_host.py +177 -0
  17. kotonebot/client/host/protocol.py +68 -38
  18. kotonebot/client/implements/adb.py +1 -1
  19. kotonebot/client/protocol.py +1 -1
  20. kotonebot/config/base_config.py +8 -3
  21. kotonebot/interop/win/reg.py +37 -0
  22. kotonebot/kaa/common.py +18 -2
  23. kotonebot/kaa/game_ui/badge.py +6 -6
  24. kotonebot/kaa/game_ui/common.py +6 -6
  25. kotonebot/kaa/game_ui/commu_event_buttons.py +4 -3
  26. kotonebot/kaa/game_ui/idols_overview.py +6 -6
  27. kotonebot/kaa/game_ui/schedule.py +162 -0
  28. kotonebot/kaa/game_ui/scrollable.py +7 -6
  29. kotonebot/kaa/kaa_context.py +8 -0
  30. kotonebot/kaa/main/cli.py +7 -0
  31. kotonebot/kaa/main/dmm_host.py +53 -0
  32. kotonebot/kaa/main/gr.py +508 -298
  33. kotonebot/kaa/main/kaa.py +77 -1
  34. kotonebot/kaa/metadata.py +26 -0
  35. kotonebot/kaa/resources/__pycache__/__init__.cpython-310.pyc +0 -0
  36. kotonebot/kaa/resources/game.db +0 -0
  37. kotonebot/kaa/resources/game_ver.txt +0 -0
  38. kotonebot/kaa/sprites/4503db6b-7224-4b81-9971-e7cfa56e10f2.png +0 -0
  39. kotonebot/kaa/tasks/R.py +137 -133
  40. kotonebot/kaa/tasks/daily/contest.py +123 -89
  41. kotonebot/kaa/tasks/daily/mission_reward.py +4 -4
  42. kotonebot/kaa/tasks/end_game.py +2 -9
  43. kotonebot/kaa/tasks/produce/cards.py +7 -6
  44. kotonebot/kaa/tasks/produce/common.py +16 -12
  45. kotonebot/kaa/tasks/produce/in_purodyuusu.py +5 -5
  46. kotonebot/kaa/tasks/produce/p_drink.py +4 -3
  47. kotonebot/kaa/tasks/produce/produce.py +5 -3
  48. kotonebot/kaa/tasks/start_game.py +2 -2
  49. kotonebot/primitives/__init__.py +17 -0
  50. kotonebot/primitives/geometry.py +290 -0
  51. kotonebot/primitives/visual.py +63 -0
  52. kotonebot/util.py +20 -20
  53. {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/METADATA +1 -1
  54. {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/RECORD +191 -183
  55. {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/WHEEL +1 -1
  56. /kotonebot/{client/host/mumu_host.py → interop/win/__init__.py} +0 -0
  57. /kotonebot/kaa/sprites/{7971b595-44d3-4ce7-91ae-a8e5a25c543b.png → 02ff2b86-db5a-4850-ac98-4288a3f71ed8.png} +0 -0
  58. /kotonebot/kaa/sprites/{bc64dd37-48ba-45a5-af6d-457a7fb2c9fd.png → 04422a5d-2499-40fd-a08f-095ecbd051a8.png} +0 -0
  59. /kotonebot/kaa/sprites/{17da97eb-93c5-438f-8688-0be5b0756019.png → 0672e1c6-6abc-452c-82b3-af6eb7454cfa.png} +0 -0
  60. /kotonebot/kaa/sprites/{5089ea64-3f32-4520-a252-4ee278815cdd.png → 07960be4-b575-4f7f-be64-7e344dc118e0.png} +0 -0
  61. /kotonebot/kaa/sprites/{0f67c0a6-45d0-4953-b1a9-b5eb2c916173.png → 09cd4336-7d2a-4bce-9d08-8ac64a2bdcc3.png} +0 -0
  62. /kotonebot/kaa/sprites/{5ed9bef4-ab11-4186-9b21-a479eec687ce.png → 09fda5a8-c546-48f0-ae29-ebe4f4208d8b.png} +0 -0
  63. /kotonebot/kaa/sprites/{7dec3bd9-f8ee-430f-af54-0c03c4c91546.png → 0b5c2d3f-28b1-456f-a452-05ca006f8b33.png} +0 -0
  64. /kotonebot/kaa/sprites/{76b0e3b3-3438-4e88-aca8-da18c8e50ddd.png → 0c359f2a-72d4-4203-9ca3-968a1948eee8.png} +0 -0
  65. /kotonebot/kaa/sprites/{9e110778-1b66-4700-b462-64c1dea368da.png → 0ecc3e37-189c-40ad-9ee6-fb576b72a13b.png} +0 -0
  66. /kotonebot/kaa/sprites/{bbcb97e2-89b7-4de2-b480-a0442fc33b1e.png → 11c2ff97-3303-4d6d-8e4f-6da2148bbe20.png} +0 -0
  67. /kotonebot/kaa/sprites/{36d712ce-9227-4aef-86ee-4f78415b0c80.png → 128886a7-7629-4158-9538-3619542f775b.png} +0 -0
  68. /kotonebot/kaa/sprites/{ba07fa5e-3451-4520-8b10-0b240e5decec.png → 13a85dbd-e226-47cc-b6f5-c0d3b40654f5.png} +0 -0
  69. /kotonebot/kaa/sprites/{f71df091-fc71-48d4-8124-b3c46f4c4436.png → 13c8fa4a-0ae5-4ee1-a433-20f6c724466e.png} +0 -0
  70. /kotonebot/kaa/sprites/{02e69560-ec42-4453-b771-8600b5726b93.png → 18042dda-0222-4333-9d79-3afc3dc75220.png} +0 -0
  71. /kotonebot/kaa/sprites/{420e4ff4-82a3-4ea3-98a7-63efe65003a6.png → 18670c18-07e3-4378-a8fc-4f9b331db00f.png} +0 -0
  72. /kotonebot/kaa/sprites/{027dd54f-09d8-452d-8eff-66b06aecd264.png → 1959c932-1bbf-4fee-b3a1-2ecfbc6d4209.png} +0 -0
  73. /kotonebot/kaa/sprites/{b673cc06-14ef-4a22-b484-13a17b01b0d4.png → 1a8ec83b-ebba-4ff7-9737-104d3aa2ff98.png} +0 -0
  74. /kotonebot/kaa/sprites/{1e6d734e-7bc5-4f3f-a717-3931f44a7709.png → 1b6bc9ae-3f4e-4722-ae6f-d76bd65b071e.png} +0 -0
  75. /kotonebot/kaa/sprites/{0bd326fd-fad0-4e6c-957c-da08583ef231.png → 1e4671aa-2cdc-4432-9d83-c635e0a3d09d.png} +0 -0
  76. /kotonebot/kaa/sprites/{e941ae91-59bf-4a69-945b-1bcd7cf241c6.png → 2490bd58-bcf7-4c94-ab24-04e752137939.png} +0 -0
  77. /kotonebot/kaa/sprites/{f790aed5-b9f3-4c93-a14f-47629f53052d.png → 25cfb896-fed1-4361-ae64-fc45fc8a98dd.png} +0 -0
  78. /kotonebot/kaa/sprites/{6e81e54e-b69b-434a-8bbf-6e2551bddeb8.png → 2730beb1-a6c5-463d-9748-393764bc8e34.png} +0 -0
  79. /kotonebot/kaa/sprites/{3f0b91e1-898a-4f44-b116-38483bc30461.png → 2766af5c-4ad1-44e2-ab84-e8a6ac7bb172.png} +0 -0
  80. /kotonebot/kaa/sprites/{4fca090a-b94a-433a-8438-832a54ff65e8.png → 2cc68858-7af0-4a12-8c78-c4e4dcf9fff1.png} +0 -0
  81. /kotonebot/kaa/sprites/{04b4ccde-a374-4df3-b0fa-552ae1518348.png → 2e68066d-9a56-48cc-8711-1d4c080ee320.png} +0 -0
  82. /kotonebot/kaa/sprites/{4a9e612b-9e52-4eb5-a851-3e5ad68030e9.png → 32d50687-b3a2-4b34-a2f9-4418b5e14fff.png} +0 -0
  83. /kotonebot/kaa/sprites/{2179ff5c-7964-42b1-9a95-c0bd8af1b692.png → 344bedf1-fcca-4025-9ff6-cea99c91ddb4.png} +0 -0
  84. /kotonebot/kaa/sprites/{99e95c59-d5da-448d-b839-51428b2686f0.png → 346da82f-bfca-4a0f-bae4-ca9c8e4b794e.png} +0 -0
  85. /kotonebot/kaa/sprites/{be7504d5-48e5-404c-a74d-f7fa86acf276.png → 379a8811-932c-4c75-854f-943631c921d5.png} +0 -0
  86. /kotonebot/kaa/sprites/{d11d8839-90ef-433c-8555-fad5478009a0.png → 38f8f55f-e9dc-4c10-b97a-b7254d871ae9.png} +0 -0
  87. /kotonebot/kaa/sprites/{03c9182c-b5ba-4404-b6f9-50b06a86e102.png → 39b1738f-af9a-4c85-9315-2858f0b1d1ab.png} +0 -0
  88. /kotonebot/kaa/sprites/{fb529219-fc53-43e1-83ca-2313d1d33636.png → 3a58e019-6328-412a-9d2f-85a73900371e.png} +0 -0
  89. /kotonebot/kaa/sprites/{d4673e38-cf76-4062-9a71-0f31936fe49b.png → 3c2f70b5-9095-48d6-a012-4d68a6b0a975.png} +0 -0
  90. /kotonebot/kaa/sprites/{e753b92e-99bf-4d81-9673-92b5f67d129b.png → 3d016208-f8ed-45be-bfb1-ed15b231d0b8.png} +0 -0
  91. /kotonebot/kaa/sprites/{0b0e76ce-60cc-45cc-b204-227e59d39c26.png → 3e2af23e-041f-4128-8a78-cbd1297538cf.png} +0 -0
  92. /kotonebot/kaa/sprites/{edb144e1-7c82-4fcf-b426-c6294ad2112d.png → 429c30b5-7d3a-40a1-853a-ec51c8f238e7.png} +0 -0
  93. /kotonebot/kaa/sprites/{de619141-10a4-4447-89b3-6cabcbf52d8a.png → 440eeea3-b710-485e-ab40-80e2b6ce66f0.png} +0 -0
  94. /kotonebot/kaa/sprites/{1607c23d-3ab4-418b-8114-1a3bb782d246.png → 46f24175-8657-497d-97e6-1c1ee356e4cd.png} +0 -0
  95. /kotonebot/kaa/sprites/{3eb037db-4cd8-4c00-a655-fa73f612adfd.png → 4a128979-bf11-42fe-a966-a8f5ee143128.png} +0 -0
  96. /kotonebot/kaa/sprites/{7b07a31c-9e2b-4ce0-8998-ccc0592de5e0.png → 4a5ec641-38ec-46b0-83c4-d7edacb754b0.png} +0 -0
  97. /kotonebot/kaa/sprites/{e97e58cb-3412-4a8c-9a9e-dd45daa5ae02.png → 4a9c1ac7-0a61-4d40-b749-e8896322581f.png} +0 -0
  98. /kotonebot/kaa/sprites/{9fd268df-96b7-4b48-a6d1-b5da2161da8e.png → 4af7631b-3674-4491-a3fb-a514bfd6b204.png} +0 -0
  99. /kotonebot/kaa/sprites/{32a1072b-c359-4dad-80f6-c9ca558e5f99.png → 4c2c5378-d946-41c2-980e-afb8493f71ee.png} +0 -0
  100. /kotonebot/kaa/sprites/{1a668ad4-d6b0-4aed-bb99-8b1ac85659d3.png → 4c4dd8e5-5370-4381-b6d4-c260b32015cf.png} +0 -0
  101. /kotonebot/kaa/sprites/{32793e3b-613b-4b6d-bc2e-25f9993f6274.png → 4c5b8c40-3dfa-4091-95c3-257a36e9a6d3.png} +0 -0
  102. /kotonebot/kaa/sprites/{d3fa5988-1184-4478-abb9-1c9799bd4100.png → 4d0b8533-b3e2-4ea1-8204-a8d5e67e1f64.png} +0 -0
  103. /kotonebot/kaa/sprites/{0de838fb-24e9-4fe8-8e2a-cc26bce6d7d8.png → 50e462d5-ca14-4934-b35a-da3ebb8fb33f.png} +0 -0
  104. /kotonebot/kaa/sprites/{d2830bf7-e0c4-4d00-868a-6df22bdf6fcd.png → 557389eb-d0da-4c5a-a322-de3e32a81909.png} +0 -0
  105. /kotonebot/kaa/sprites/{979c7468-d9ef-4cc1-9449-2aa716caabb0.png → 57b7006a-ada9-4d9d-9655-71765251882e.png} +0 -0
  106. /kotonebot/kaa/sprites/{8f39322e-92d2-4cc6-80ef-341884b0a598.png → 59ef821b-4803-4fe3-83b8-305902f8459a.png} +0 -0
  107. /kotonebot/kaa/sprites/{a0b83694-4945-4476-aa2e-f268841ac372.png → 5af71b93-310c-48ab-b438-dba296fb9e52.png} +0 -0
  108. /kotonebot/kaa/sprites/{a03e79b3-7f44-4211-b625-2510ba8031bc.png → 5b6b5bf8-cd03-4360-9550-e15d8db6241a.png} +0 -0
  109. /kotonebot/kaa/sprites/{2fd33c51-b3a7-4ffb-9555-d25961186eee.png → 5d963589-623d-4a49-9c72-af91134720b0.png} +0 -0
  110. /kotonebot/kaa/sprites/{f4c8a757-674e-45ca-af2c-0bc409b7bca8.png → 5f01b811-9aad-4439-a70b-c099a42c36fa.png} +0 -0
  111. /kotonebot/kaa/sprites/{a1c29e77-138c-4580-a777-666815039524.png → 601d0743-7143-4c04-989b-b80fc620e678.png} +0 -0
  112. /kotonebot/kaa/sprites/{4aed7e90-76b1-4837-a6ad-e2ed2e3240ec.png → 63e71ec7-6290-4efa-89cf-69c53d805a58.png} +0 -0
  113. /kotonebot/kaa/sprites/{c15ad104-3814-467c-a211-1e54be35fc66.png → 66f5fab8-274c-4ab9-983d-ca2f8d90ea94.png} +0 -0
  114. /kotonebot/kaa/sprites/{8031a52b-afe1-4ebd-9599-1d123bc0e48b.png → 6abb63c1-025a-425e-8f33-29f83f504d75.png} +0 -0
  115. /kotonebot/kaa/sprites/{ad26884a-8371-420f-be89-7fe82ddaf1e8.png → 6d6550a6-f270-474e-b55c-b2017ef87ab2.png} +0 -0
  116. /kotonebot/kaa/sprites/{bf158b17-4d93-40f1-b9cf-4ae9226bcfc1.png → 7057e165-9ed4-485e-9609-94854ef269dd.png} +0 -0
  117. /kotonebot/kaa/sprites/{fa64ed03-4277-4ae2-a958-adb93c393457.png → 78c2c39e-6621-41d1-9a1e-7e171b001795.png} +0 -0
  118. /kotonebot/kaa/sprites/{f32e04e8-2e9a-49c2-9734-d0a02ed2d892.png → 798a9587-3f8c-424e-90af-89a08ed62ced.png} +0 -0
  119. /kotonebot/kaa/sprites/{b677c2ba-62da-4afd-8ba7-a9b40362b5bb.png → 7bbac9cd-417f-4bd5-975a-a8a25c7bd922.png} +0 -0
  120. /kotonebot/kaa/sprites/{d1a8f72b-813b-4d8a-a596-5576758d7fe2.png → 7c2f9216-56f2-4eec-8a45-1bd864316dad.png} +0 -0
  121. /kotonebot/kaa/sprites/{fe7e4f2b-b795-43b6-bf0a-4e65c9133a40.png → 7c992bae-5ded-473b-8e60-4dede8434e5d.png} +0 -0
  122. /kotonebot/kaa/sprites/{e73cacc9-08aa-4b7a-acd3-3dbe95d9f51c.png → 7e7e8772-b31a-4bbe-919e-90373a4ad5a7.png} +0 -0
  123. /kotonebot/kaa/sprites/{39151ffc-365e-45b2-a2f4-9d4c76d41524.png → 80171520-d132-4395-b2ed-e6451335b3d5.png} +0 -0
  124. /kotonebot/kaa/sprites/{04180b4a-1532-42dd-9280-0f47c1e68959.png → 82cf688e-ac85-4425-917d-10b6db9b6cc3.png} +0 -0
  125. /kotonebot/kaa/sprites/{ebcaaba7-57f8-4bcb-93da-a288a0fcc95e.png → 87292951-43c1-4039-9b92-c59c1b25b5f7.png} +0 -0
  126. /kotonebot/kaa/sprites/{4dd7db2d-3107-4fc5-8f30-cfa70bc28de1.png → 893a6bba-905e-4123-9e35-f829fd6c8b2a.png} +0 -0
  127. /kotonebot/kaa/sprites/{f9e0be54-237b-4092-8784-1a4d2eb43097.png → 8a605d31-3160-49d1-9098-45d8ee18e8d6.png} +0 -0
  128. /kotonebot/kaa/sprites/{334d4915-4600-4c81-bbb0-c52291b7d089.png → 8cb3b6c2-7fae-41cf-8f33-a0e3ea668f25.png} +0 -0
  129. /kotonebot/kaa/sprites/{6abe81b7-1a04-44e9-b390-affe47799c46.png → 8e1ca316-39e6-4b9f-b497-84ad8848a3e1.png} +0 -0
  130. /kotonebot/kaa/sprites/{f14cc536-f38c-4568-9812-d3ed95e639d3.png → 8f675963-122a-4932-9f33-07dcdcb3a51a.png} +0 -0
  131. /kotonebot/kaa/sprites/{0df088e0-737f-44ec-b39f-76239d087382.png → 907dc373-e2f5-4959-84cb-d7971a7fefd8.png} +0 -0
  132. /kotonebot/kaa/sprites/{7d5b4d44-cb46-49fb-96be-edaab5396836.png → 9359526e-c5db-4d63-adc4-08b7d704b422.png} +0 -0
  133. /kotonebot/kaa/sprites/{2860421f-2db8-4850-9f0c-0aca8bbf16d0.png → 96178cef-4868-4378-9f9c-c08780def3d6.png} +0 -0
  134. /kotonebot/kaa/sprites/{a477c873-98bd-45f0-a9a6-324e714c69cd.png → 985ec867-bc7c-47c5-8fcd-75f736f27507.png} +0 -0
  135. /kotonebot/kaa/sprites/{669f9e7c-b665-47a8-b4f5-fb2748bf88af.png → 9c98c4e6-5335-4c8b-92fa-24ec440b055e.png} +0 -0
  136. /kotonebot/kaa/sprites/{1373ec63-fd98-4be9-a925-08144f1d5ba5.png → a015cea0-d2c7-439a-b942-d4d2dad276e5.png} +0 -0
  137. /kotonebot/kaa/sprites/{6fec0efa-336f-4726-b1d9-dc83c23b297d.png → a05abc9d-9f0e-4d83-94bb-4f0b1e061035.png} +0 -0
  138. /kotonebot/kaa/sprites/{957dbdc4-8367-4a59-9bfc-d35ea11af9b8.png → a2e41d98-dd91-4e51-ba57-4f8ddfd07cfa.png} +0 -0
  139. /kotonebot/kaa/sprites/{8e78e1f1-ea2f-4779-a8cd-56336f936c81.png → a402b549-a4dd-4ff3-ad51-2aa5f8e620af.png} +0 -0
  140. /kotonebot/kaa/sprites/{ff493975-0f30-468f-a3ea-17505a2b0385.png → a5efb332-8452-49d8-ade3-882bbd445553.png} +0 -0
  141. /kotonebot/kaa/sprites/{dfe2fc89-eaf7-4027-bb53-dd17a9741e01.png → a9f47d53-fbef-4cb0-9d2d-845ef3d1f719.png} +0 -0
  142. /kotonebot/kaa/sprites/{45ae4d17-1887-4f2e-8cce-e5a4cc42e10b.png → b10596fe-22fa-4284-ab4b-1d653d4f0f07.png} +0 -0
  143. /kotonebot/kaa/sprites/{9cc7997f-3aeb-4b20-ae42-468ebfbc851e.png → b326c50f-c88c-4b99-80a7-046053423a19.png} +0 -0
  144. /kotonebot/kaa/sprites/{6d706642-441f-4bcc-b377-0fce3484be22.png → b44085c7-9777-415a-aae1-aae47c2f7321.png} +0 -0
  145. /kotonebot/kaa/sprites/{54d57991-e5d0-4346-b1f8-63e429d8a3fe.png → b629f0a7-df4f-4407-908b-a031c70e8b6f.png} +0 -0
  146. /kotonebot/kaa/sprites/{63f28640-766f-4698-b556-f0a0cbac6a1a.png → b8165178-a9ce-491a-ba57-d25c7af8533f.png} +0 -0
  147. /kotonebot/kaa/sprites/{0c3cc689-90f5-4c29-be80-04e2f2ab799a.png → b8eeacac-5679-42b9-a5b9-138598c8c6b6.png} +0 -0
  148. /kotonebot/kaa/sprites/{f89cafb7-657b-4ab4-8917-1964b242ff2e.png → b9ad5832-889a-4a5f-adc0-5dfd55126d6a.png} +0 -0
  149. /kotonebot/kaa/sprites/{16576157-f99b-4a64-b41b-3ee712c2304e.png → bcb95d32-32e1-4251-83de-ef5164d22a48.png} +0 -0
  150. /kotonebot/kaa/sprites/{7e64561b-0dab-420a-8f79-45ed6801939b.png → c0ddf15a-a6f6-42f6-86b7-e17cde60b437.png} +0 -0
  151. /kotonebot/kaa/sprites/{6d4f414d-ccd8-4a25-aa32-1b77ce0035d6.png → c24e874e-6a3e-472c-8745-edf7cac88954.png} +0 -0
  152. /kotonebot/kaa/sprites/{2b87ed7c-b01b-4ea7-ab34-ec4e37e036c0.png → c362b54d-d535-4227-8612-9bfe11fd7891.png} +0 -0
  153. /kotonebot/kaa/sprites/{1e7924dc-9dd4-4d54-add8-b394c75af1c7.png → c51132e9-f45c-40c9-8026-7d70b13dca8b.png} +0 -0
  154. /kotonebot/kaa/sprites/{5663551f-5abc-4ef8-a3fd-ba57b20cb35c.png → c60bec93-231f-498d-b3c6-0a78c9b8f37f.png} +0 -0
  155. /kotonebot/kaa/sprites/{89d6407a-982c-4dbc-8dd7-89cf79429eec.png → c64ff0a8-9f96-4ca7-902b-4f7298c1c431.png} +0 -0
  156. /kotonebot/kaa/sprites/{bda40c0d-55a9-41ad-ba8a-e8aafc52608b.png → c819d999-fef6-47bb-a94f-055d616033c7.png} +0 -0
  157. /kotonebot/kaa/sprites/{1b8b2a66-fb45-41d7-8441-04425a47bdeb.png → c85e58d3-ec5a-43ea-aa2a-04ee28e9ea21.png} +0 -0
  158. /kotonebot/kaa/sprites/{b332eb23-6653-4d17-b779-2451a3b261ab.png → c883049b-187c-4685-a63a-cff516851a95.png} +0 -0
  159. /kotonebot/kaa/sprites/{60f292d1-6c2d-4cfb-af76-568610817722.png → c8f0cc49-5df8-4d7a-9ef6-2f021c2380a5.png} +0 -0
  160. /kotonebot/kaa/sprites/{7383d7d0-8ee2-4413-b590-51cff541cab3.png → c9a8d73f-f487-45ff-aa05-a61393d27f66.png} +0 -0
  161. /kotonebot/kaa/sprites/{817c22a3-ecfe-4949-a784-59342e61a880.png → cb4c357f-b01b-4b95-b928-5affdf4ae684.png} +0 -0
  162. /kotonebot/kaa/sprites/{dff3509c-f0ec-44e2-8e0b-3c6b465d45f3.png → cc0e38b2-c2ae-4fe1-adb0-6ea73a602563.png} +0 -0
  163. /kotonebot/kaa/sprites/{c7a33e9e-2f1c-4171-b1f7-7d7dcc1a7bd3.png → cdd40676-b49d-42ad-9972-7149aa13d9bb.png} +0 -0
  164. /kotonebot/kaa/sprites/{5d912f4e-c319-4d45-85e6-de6e684a6278.png → cf76eb98-44ff-406e-913a-503a0233b4cd.png} +0 -0
  165. /kotonebot/kaa/sprites/{7b195535-7362-492f-8d87-b8eed15c6fc0.png → d21117c2-3679-4bb0-a5b3-5af5d12d8d3e.png} +0 -0
  166. /kotonebot/kaa/sprites/{7d778f82-f908-4edb-9b12-d8460589ab11.png → d37bc673-9668-4b7e-bc9d-54495129dcc0.png} +0 -0
  167. /kotonebot/kaa/sprites/{08750a02-0881-443a-9c69-c90a2ccf2a63.png → dac24ef9-e53d-4d95-aa09-fa694606c3ab.png} +0 -0
  168. /kotonebot/kaa/sprites/{31271771-5149-447c-8fae-ccc308c0a838.png → dd084696-be33-4a00-b6df-b25131b78b40.png} +0 -0
  169. /kotonebot/kaa/sprites/{a35aed53-ff78-41f0-9a19-c69fda40aeef.png → de1e3c4a-f6ed-467a-889c-25c01a613477.png} +0 -0
  170. /kotonebot/kaa/sprites/{639465a0-3c63-4307-a72c-4f9e543d2095.png → e1eb4596-73a1-471e-892b-c83cfb706bb3.png} +0 -0
  171. /kotonebot/kaa/sprites/{91940a9e-2cfc-423a-86d6-49429c051ea0.png → e1ec3ee5-e73c-44b6-b173-ab1c9382cba4.png} +0 -0
  172. /kotonebot/kaa/sprites/{096d3df5-2c95-41dd-bcc3-2e3afab47420.png → e4d076bd-7824-4496-a498-d6dc01ad90ba.png} +0 -0
  173. /kotonebot/kaa/sprites/{50a9e2a0-1166-4f1d-ad15-d8493ce0e49e.png → e551148d-1b0d-4518-8711-4161863c93ef.png} +0 -0
  174. /kotonebot/kaa/sprites/{45671af1-d731-4bb1-83f7-c9d23348308f.png → e61d855e-84d3-4a0e-b996-43fce262c2c3.png} +0 -0
  175. /kotonebot/kaa/sprites/{608217f6-b880-42ab-9be9-b4d56df87be2.png → e795f9c5-fdda-4fbc-8d38-a92512a590f4.png} +0 -0
  176. /kotonebot/kaa/sprites/{7d4d708f-c629-4f52-a0af-11aee7fcb878.png → eb446c0a-1dd2-4693-838e-98a76c02eac5.png} +0 -0
  177. /kotonebot/kaa/sprites/{35e6ad3a-8020-4619-b8ec-6506a8aa51fc.png → eb4bde42-de6e-4c62-8dc4-8885efc834a8.png} +0 -0
  178. /kotonebot/kaa/sprites/{9a5e7bc9-830f-4b01-8f94-6d4f589754c0.png → ec547e11-4952-4089-9744-1e89bd44389c.png} +0 -0
  179. /kotonebot/kaa/sprites/{40e3dac1-74fb-4501-a20f-18422d070554.png → ed4a8cff-b3df-4d7a-bfc4-8da776a8e548.png} +0 -0
  180. /kotonebot/kaa/sprites/{39d57097-6839-4030-bb51-24d6cd880897.png → efc08f1c-e78f-4df8-bff9-b45cde37f9dd.png} +0 -0
  181. /kotonebot/kaa/sprites/{9f56889b-0251-4ec1-8ccb-5cfd3bddd396.png → f02d997a-a3a8-4e18-96f1-e4176c14a5e0.png} +0 -0
  182. /kotonebot/kaa/sprites/{97bbc57d-780a-40f4-8fe1-bcaef697b601.png → f5e2e4e1-40be-481d-9293-9d47a5bce7cc.png} +0 -0
  183. /kotonebot/kaa/sprites/{bee4fbe2-365f-4145-b8f4-87c2c460cd71.png → f62dd2a2-bac6-4dc8-9a19-3e995ab80a3b.png} +0 -0
  184. /kotonebot/kaa/sprites/{666b1e8c-19fb-49e9-a761-1069f635f450.png → f64cfd95-8c87-47e6-96c3-8b7df2624ae3.png} +0 -0
  185. /kotonebot/kaa/sprites/{847cb208-a71c-42e3-97f1-18d4aa886b41.png → f8bcbc2c-9831-4e8d-9ffd-e532aa3193c3.png} +0 -0
  186. /kotonebot/kaa/sprites/{9e5c5ffc-450d-4ddc-a4ae-27c33267b056.png → fb50c7d1-b983-4e36-a998-ea17a1bdb18b.png} +0 -0
  187. /kotonebot/kaa/sprites/{f1a60528-1f35-451f-92e3-b1d5407e0f4e.png → fb97d804-42e4-45e2-8b22-01d16b5d0b6b.png} +0 -0
  188. /kotonebot/kaa/sprites/{a3140a53-ffb4-4094-a39c-240e9fe42308.png → fe337541-a6a3-4325-97b4-2bf27f51f39f.png} +0 -0
  189. /kotonebot/kaa/sprites/{1a0f2a10-c23b-47c7-aa7e-89b606f4d8ae.png → ffd3c6b7-cac2-4d45-ae37-792404ea2b40.png} +0 -0
  190. {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/entry_points.txt +0 -0
  191. {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,21 @@
1
1
  import time
2
2
  import socket
3
- from typing import Protocol, NamedTuple
4
- from dataclasses import dataclass
3
+ from abc import ABC, abstractmethod
4
+ from typing_extensions import ParamSpec, Concatenate
5
+ from typing import Callable, TypeVar, Generic, Protocol, runtime_checkable, Type, Any
5
6
 
6
7
  from adbutils import adb, AdbTimeout, AdbError
7
8
  from adbutils._device import AdbDevice
8
9
 
9
10
  from kotonebot import logging
11
+ from kotonebot.client import Device, create_device, DeviceImpl
10
12
  from kotonebot.util import Countdown, Interval
11
13
 
12
14
  logger = logging.getLogger(__name__)
15
+ # https://github.com/python/typing/issues/769#issuecomment-903760354
16
+ _T = TypeVar("_T")
17
+ def copy_type(_: _T) -> Callable[[Any], _T]:
18
+ return lambda x: x
13
19
 
14
20
  def tcp_ping(host: str, port: int, timeout: float = 1.0) -> bool:
15
21
  """
@@ -30,34 +36,77 @@ def tcp_ping(host: str, port: int, timeout: float = 1.0) -> bool:
30
36
  return False
31
37
 
32
38
 
33
- @dataclass
34
- class Instance:
35
- id: str
36
- name: str
37
- adb_port: int
38
- adb_ip: str = '127.0.0.1'
39
- adb_emulator_name: str = 'emulator-5554'
39
+ class Instance(ABC):
40
+ def __init__(self,
41
+ id: str,
42
+ name: str,
43
+ adb_port: int | None = None,
44
+ adb_ip: str = '127.0.0.1',
45
+ adb_name: str | None = None
46
+ ):
47
+ self.id: str = id
48
+ self.name: str = name
49
+ self.adb_port: int | None = adb_port
50
+ self.adb_ip: str = adb_ip
51
+ self.adb_name: str | None = adb_name
40
52
 
53
+ def require_adb_port(self) -> int:
54
+ if self.adb_port is None:
55
+ raise ValueError("ADB port is not set and is required.")
56
+ return self.adb_port
57
+
58
+ @abstractmethod
59
+ def refresh(self):
60
+ """
61
+ 刷新实例信息,如 ADB 端口号等。
62
+ """
63
+ raise NotImplementedError()
64
+
65
+ @abstractmethod
41
66
  def start(self):
67
+ """
68
+ 启动模拟器实例。
69
+ """
42
70
  raise NotImplementedError()
43
71
 
72
+ @abstractmethod
44
73
  def stop(self):
74
+ """
75
+ 停止模拟器实例。
76
+ """
45
77
  raise NotImplementedError()
46
78
 
79
+ @abstractmethod
47
80
  def running(self) -> bool:
48
81
  raise NotImplementedError()
49
82
 
83
+ def create_device(self, impl: DeviceImpl, *, timeout: float = 180) -> Device:
84
+ """
85
+ 创建 Device 实例,可用于控制模拟器系统。
86
+
87
+ :return: Device 实例
88
+ """
89
+ if self.adb_port is None:
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
+ )
98
+
50
99
  def wait_available(self, timeout: float = 180):
51
100
  logger.info('Starting to wait for emulator %s(127.0.0.1:%d) to be available...', self.name, self.adb_port)
52
101
  state = 0
53
- port = self.adb_port
54
- emulator_name = self.adb_emulator_name
102
+ port = self.require_adb_port()
103
+ emulator_name = self.adb_name
55
104
  cd = Countdown(timeout)
56
105
  it = Interval(1)
57
106
  d: AdbDevice | None = None
58
107
  while True:
59
108
  if cd.expired():
60
- raise TimeoutError(f"Emulator {self.name} is not available.")
109
+ raise TimeoutError(f'Emulator "{self.name}" is not available.')
61
110
  it.wait()
62
111
  try:
63
112
  match state:
@@ -121,38 +170,19 @@ class Instance:
121
170
  time.sleep(1)
122
171
  logger.info('Emulator %s(127.0.0.1:%d) now is available.', self.name, self.adb_port)
123
172
 
173
+ def __repr__(self) -> str:
174
+ return f'{self.__class__.__name__}(name="{self.name}", id="{self.id}", adb="{self.adb_ip}:{self.adb_port}"({self.adb_name}))'
124
175
 
125
176
  class HostProtocol(Protocol):
126
177
  @staticmethod
127
178
  def installed() -> bool: ...
179
+
128
180
  @staticmethod
129
181
  def list() -> list[Instance]: ...
182
+
183
+ @staticmethod
184
+ def query(*, id: str) -> Instance | None: ...
130
185
 
131
186
 
132
187
  if __name__ == '__main__':
133
- from . import bluestack_global
134
- from pprint import pprint
135
- logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s][%(levelname)s] %(message)s')
136
- # bluestack_global.
137
- ins = Instance(id='1', name='test', adb_port=5555)
138
- ins.wait_available()
139
-
140
- #
141
- # while not tcp_ping('127.0.0.1', 16384):
142
- # print('waiting for bluestacks to start...')
143
-
144
- # while True:
145
- # print('connecting to bluestacks...')
146
- # try:
147
- # adb.connect('127.0.0.1:16384', timeout=0.1)
148
- # print('connected to bluestacks')
149
- # if d := adb.device_list()[0]:
150
- # if d.get_state() == 'device':
151
- # if d.shell('getprop sys.boot_completed').strip() == '1':
152
- # if 'launcher' in d.app_current().package:
153
- # break
154
- # except Exception as e:
155
- # print(e)
156
- # time.sleep(0.5)
157
- # time.sleep(1)
158
- # print('bluestacks is ready')
188
+ pass
@@ -32,7 +32,7 @@ class AdbImpl(Commandable, Touchable, Screenshotable):
32
32
  if result_text == '':
33
33
  logger.error("No current package found")
34
34
  return None
35
- _, activity, _, pid = result_text.split(' ')
35
+ _, activity, *_ = result_text.split(' ')
36
36
  package = activity.split('/')[0]
37
37
  return package
38
38
 
@@ -2,7 +2,7 @@ from typing import Protocol, TYPE_CHECKING, runtime_checkable, Literal
2
2
 
3
3
  from cv2.typing import MatLike
4
4
 
5
- from kotonebot.util import Rect
5
+ from kotonebot.primitives import Rect
6
6
  if TYPE_CHECKING:
7
7
  from .device import Device
8
8
 
@@ -3,14 +3,19 @@ from typing import Generic, TypeVar, Literal
3
3
 
4
4
  from pydantic import BaseModel, ConfigDict
5
5
 
6
+ from kotonebot.client.factory import DeviceImpl
7
+
6
8
  T = TypeVar('T')
9
+ BackendType = Literal['custom', 'mumu12', 'leidian', 'dmm']
7
10
 
8
11
  class ConfigBaseModel(BaseModel):
9
12
  model_config = ConfigDict(use_attribute_docstrings=True)
10
13
 
11
14
  class BackendConfig(ConfigBaseModel):
12
- type: Literal['custom'] = 'custom'
15
+ type: BackendType = 'custom'
13
16
  """后端类型。"""
17
+ instance_id: str | None = None
18
+ """模拟器实例 ID。"""
14
19
  adb_ip: str = '127.0.0.1'
15
20
  """adb 连接的 ip 地址。"""
16
21
  adb_port: int = 5555
@@ -22,7 +27,7 @@ class BackendConfig(ConfigBaseModel):
22
27
  雷电模拟器需要设置正确的模拟器名,否则 自动启动模拟器 功能将无法正常工作。
23
28
  其他功能不受影响。
24
29
  """
25
- screenshot_impl: Literal['adb', 'adb_raw', 'uiautomator2', 'windows', 'remote_windows'] = 'adb'
30
+ screenshot_impl: DeviceImpl = 'adb'
26
31
  """
27
32
  截图方法。暂时推荐使用【adb】截图方式。
28
33
 
@@ -76,7 +81,7 @@ class UserConfig(ConfigBaseModel, Generic[T]):
76
81
 
77
82
 
78
83
  class RootConfig(ConfigBaseModel, Generic[T]):
79
- version: int = 4
84
+ version: int = 5
80
85
  """配置版本。"""
81
86
  user_configs: list[UserConfig[T]] = []
82
87
  """用户配置。"""
@@ -0,0 +1,37 @@
1
+ import winreg
2
+ from typing import Any, Literal
3
+
4
+ RegKey = Literal["HKLM", "HKCU", "HKCR", "HKU", "HKCC"]
5
+
6
+ def read_reg(key: RegKey, subkey: str, name: str, *, default: Any = None) -> Any:
7
+ """
8
+ 读取注册表项的值。
9
+
10
+ :param key: 注册表键,例如 "HKLM" (HKEY_LOCAL_MACHINE), "HKCU" (HKEY_CURRENT_USER) 等。
11
+ :param subkey: 注册表子键的路径。
12
+ :param name: 要读取的值的名称。
13
+ :param default: 如果注册表项不存在时返回的默认值。
14
+ :return: 注册表项的值,如果不存在则返回默认值。
15
+ """
16
+ try:
17
+ hkey = {
18
+ "HKLM": winreg.HKEY_LOCAL_MACHINE,
19
+ "HKCU": winreg.HKEY_CURRENT_USER,
20
+ "HKCR": winreg.HKEY_CLASSES_ROOT,
21
+ "HKU": winreg.HKEY_USERS,
22
+ "HKCC": winreg.HKEY_CURRENT_CONFIG,
23
+ }[key]
24
+ except KeyError:
25
+ raise ValueError(f"Invalid key: {key}")
26
+
27
+ try:
28
+ with winreg.OpenKey(hkey, subkey) as key_handle:
29
+ value, _ = winreg.QueryValueEx(key_handle, name)
30
+ return value
31
+ except FileNotFoundError:
32
+ return default
33
+ except OSError as e:
34
+ if e.winerror == 2: # 注册表项不存在
35
+ return default
36
+ else:
37
+ raise # 其他 OSError 异常,例如权限问题,重新抛出
kotonebot/kaa/common.py CHANGED
@@ -2,7 +2,7 @@ import os
2
2
  import json
3
3
  import shutil
4
4
  from importlib import resources
5
- from typing import Literal, TypeVar, Any
5
+ from typing import Literal, TypeVar, Any, Sequence
6
6
  from typing_extensions import assert_never
7
7
  from enum import IntEnum, Enum
8
8
 
@@ -235,7 +235,7 @@ class PurchaseConfig(ConfigBaseModel):
235
235
  """
236
236
  ap_enabled: bool = False
237
237
  """是否启用AP购买"""
238
- ap_items: list[Literal[0, 1, 2, 3]] = []
238
+ ap_items: Sequence[Literal[0, 1, 2, 3]] = []
239
239
  """AP商店要购买的物品"""
240
240
 
241
241
 
@@ -523,6 +523,11 @@ def upgrade_config() -> str | None:
523
523
  user_config, msg = upgrade_v3_to_v4(user_config['options'])
524
524
  messages.append(msg)
525
525
  version = 4
526
+ case 4:
527
+ logger.info('Upgrading config: v4 -> v5')
528
+ user_config, msg = upgrade_v4_to_v5(user_config, user_config['options'])
529
+ messages.append(msg)
530
+ version = 5
526
531
  case _:
527
532
  logger.info('No config upgrade needed.')
528
533
  return version
@@ -929,5 +934,16 @@ def upgrade_v3_to_v4(options: dict[str, Any]) -> tuple[dict[str, Any], str]:
929
934
  logger.info('Corrected game package name to com.bandainamcoent.idolmaster_gakuen')
930
935
  return options, ''
931
936
 
937
+ def upgrade_v4_to_v5(user_config: dict[str, Any], options: dict[str, Any]) -> tuple[dict[str, Any], str]:
938
+ """
939
+ v4 -> v5 变更:
940
+ 为 windows 和 windows_remote 截图方式的 type 设置为 dmm
941
+ """
942
+ shutil.copy('config.json', 'config.v4.json')
943
+ if user_config['backend']['screenshot_impl'] in ['windows', 'remote_windows']:
944
+ logger.info('Set backend type to dmm.')
945
+ user_config['backend']['type'] = 'dmm'
946
+ return options, ''
947
+
932
948
  if __name__ == '__main__':
933
949
  print(PurchaseConfig.model_fields['money_refresh_on'].description)
@@ -4,7 +4,7 @@ badge 模块,用于关联带附加徽章的 UI。
4
4
  """
5
5
  from typing import Literal, NamedTuple
6
6
 
7
- from kotonebot.util import Rect
7
+ from kotonebot.primitives import Rect, RectTuple, PointTuple
8
8
 
9
9
  BadgeCorner = Literal['lt', 'lm', 'lb', 'rt', 'rm', 'rb', 'mt', 'm', 'mb']
10
10
  """
@@ -33,11 +33,11 @@ def match(
33
33
  :return: 匹配结果列表
34
34
  """
35
35
  # 将 rect 转换为中心点
36
- def center(rect: Rect) -> tuple[int, int]:
36
+ def center(rect: RectTuple) -> PointTuple:
37
37
  return rect[0] + rect[2] // 2, rect[1] + rect[3] // 2
38
38
 
39
39
  # 判断 badge 是否在 object 的指定角落位置
40
- def is_in_corner(obj_rect: Rect, badge_center: tuple[int, int]) -> bool:
40
+ def is_in_corner(obj_rect: RectTuple, badge_center: PointTuple) -> bool:
41
41
  obj_center = center(obj_rect)
42
42
  x_obj, y_obj = obj_center
43
43
  x_badge, y_badge = badge_center
@@ -72,15 +72,15 @@ def match(
72
72
  available_badges = badges.copy()
73
73
 
74
74
  for obj_rect in objects:
75
- obj_center = center(obj_rect)
75
+ obj_center = center(obj_rect.xywh)
76
76
  target_badge = None
77
77
  min_dist = float('inf')
78
78
  target_index = -1
79
79
 
80
80
  # 查找最近的符合条件的徽章
81
81
  for i, badge_rect in enumerate(available_badges):
82
- badge_center = center(badge_rect)
83
- if is_in_corner(obj_rect, badge_center):
82
+ badge_center = center(badge_rect.xywh)
83
+ if is_in_corner(obj_rect.xywh, badge_center):
84
84
  dist = ((badge_center[0] - obj_center[0]) ** 2 + (badge_center[1] - obj_center[1]) ** 2) ** 0.5
85
85
  if dist < min_dist and dist <= threshold_distance:
86
86
  min_dist = dist
@@ -4,7 +4,7 @@ from cv2.typing import MatLike
4
4
 
5
5
  from kotonebot import action, color, image
6
6
  from kotonebot.backend.color import HsvColor
7
- from kotonebot.util import Rect
7
+ from kotonebot.primitives import RectTuple, Rect
8
8
  from kotonebot.backend.core import Image
9
9
  from kotonebot.backend.preprocessor import HsvColorFilter
10
10
 
@@ -20,16 +20,16 @@ def filter_rectangles(
20
20
  过滤出指定颜色,并执行轮廓查找,返回符合要求的轮廓的 bound box。
21
21
  返回结果按照 y 坐标排序。
22
22
  """
23
- img_hsv =cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
23
+ img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
24
24
 
25
25
  white_mask = cv2.inRange(img_hsv, np.array(color_ranges[0]), np.array(color_ranges[1]))
26
26
  contours, _ = cv2.findContours(white_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
27
- result_rects = []
27
+ result_rects: list[Rect] = []
28
28
  for contour in contours:
29
29
  x, y, w, h = cv2.boundingRect(contour)
30
30
  # 如果不在指定范围内,跳过
31
31
  if rect is not None:
32
- rect_x1, rect_y1, rect_w, rect_h = rect
32
+ rect_x1, rect_y1, rect_w, rect_h = rect.xywh
33
33
  rect_x2 = rect_x1 + rect_w
34
34
  rect_y2 = rect_y1 + rect_h
35
35
  if not (
@@ -42,8 +42,8 @@ def filter_rectangles(
42
42
  aspect_ratio = w / h
43
43
  area = cv2.contourArea(contour)
44
44
  if aspect_ratio >= aspect_ratio_threshold and area >= area_threshold:
45
- result_rects.append((x, y, w, h))
46
- result_rects.sort(key=lambda x: x[1])
45
+ result_rects.append(Rect(x, y, w, h))
46
+ result_rects.sort(key=lambda x: x.y1)
47
47
  return result_rects
48
48
 
49
49
  @action('按钮是否禁用', screenshot_mode='manual-inherit')
@@ -2,9 +2,10 @@ from dataclasses import dataclass
2
2
  from typing import Sequence
3
3
 
4
4
  from ..tasks import R
5
+ from kotonebot.primitives import Rect, RectTuple
5
6
  from kotonebot.backend.core import HintBox
6
7
  from kotonebot.backend.color import HsvColor
7
- from kotonebot import action, device, ocr, sleep, Rect
8
+ from kotonebot import action, device, ocr, sleep
8
9
  from .common import filter_rectangles, WHITE_LOW, WHITE_HIGH
9
10
 
10
11
  @dataclass
@@ -103,7 +104,7 @@ class CommuEventButtonUI:
103
104
  if selected is not None:
104
105
  result.append(selected)
105
106
  selected.selected = False
106
- result.sort(key=lambda x: x.rect[1])
107
+ result.sort(key=lambda x: x.rect.y1)
107
108
  return result
108
109
 
109
110
  @action('交流事件按钮.识别描述', screenshot_mode='manual-inherit')
@@ -116,7 +117,7 @@ class CommuEventButtonUI:
116
117
  """
117
118
  img = device.screenshot()
118
119
  rects = filter_rectangles(img, (WHITE_LOW, WHITE_HIGH), 3, 1000, rect=self.rect)
119
- rects.sort(key=lambda x: x[1])
120
+ rects.sort(key=lambda x: x.y1)
120
121
  # TODO: 这里 rects 可能为空,需要加入判断重试
121
122
  ocr_result = ocr.raw().ocr(img, rect=rects[0])
122
123
  return ocr_result.squash().text
@@ -7,7 +7,7 @@ from cv2.typing import MatLike
7
7
 
8
8
  from kotonebot.kaa.tasks import R
9
9
  from kotonebot.kaa.util import paths
10
- from kotonebot.util import Rect
10
+ from kotonebot.primitives import RectTuple, Rect
11
11
  from kotonebot.kaa.game_ui import Scrollable
12
12
  from kotonebot import device, action
13
13
  from kotonebot.kaa.image_db import ImageDatabase, HistDescriptor, FileDataSource
@@ -21,7 +21,7 @@ RED_DOT = ((157, 205, 255), (179, 255, 255)) # 红点
21
21
  ORANGE_SELECT_BORDER = ((9, 50, 106), (19, 255, 255)) # 当前选中的偶像的橙色边框
22
22
  WHITE_BACKGROUND = ((0, 0, 234), (179, 40, 255)) # 白色背景
23
23
 
24
- def extract_idols(img: MatLike) -> list[Rect]:
24
+ def extract_idols(img: MatLike) -> list[RectTuple]:
25
25
  """
26
26
  寻找给定图像中的所有偶像。
27
27
 
@@ -48,7 +48,7 @@ def extract_idols(img: MatLike) -> list[Rect]:
48
48
  rects.append((x, y, w, h))
49
49
  return rects
50
50
 
51
- def display_rects(img: MatLike, rects: list[Rect]) -> MatLike:
51
+ def display_rects(img: MatLike, rects: list[RectTuple]) -> MatLike:
52
52
  """Draw rectangles on the image and display them."""
53
53
  result = img.copy()
54
54
  for rect in rects:
@@ -60,7 +60,7 @@ def display_rects(img: MatLike, rects: list[Rect]) -> MatLike:
60
60
  cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
61
61
  return result
62
62
 
63
- def draw_idol_preview(img: MatLike, rects: list[Rect], db: ImageDatabase, idol_path: str) -> MatLike:
63
+ def draw_idol_preview(img: MatLike, rects: list[RectTuple], db: ImageDatabase, idol_path: str) -> MatLike:
64
64
  """
65
65
  在预览图上绘制所有匹配到的偶像。
66
66
 
@@ -110,7 +110,7 @@ def idols_db() -> ImageDatabase:
110
110
  def locate_idol(skin_id: str):
111
111
  device.screenshot()
112
112
  logger.info('Locating idol %s', skin_id)
113
- x, y, w, h = R.Produce.BoxIdolOverviewIdols
113
+ x, y, w, h = R.Produce.BoxIdolOverviewIdols.xywh
114
114
  db = idols_db()
115
115
  sc = Scrollable(color_schema='light')
116
116
 
@@ -140,7 +140,7 @@ def locate_idol(skin_id: str):
140
140
  # 同一张卡升级前后图片不一样,index 分别为 0 和 1
141
141
  if match and match.key.startswith(skin_id):
142
142
  logger.info('Found idol %s', skin_id)
143
- return rx, ry, rw, rh
143
+ return Rect(rx, ry, rw, rh)
144
144
  return None
145
145
  # cv2.imshow('Detected Idols', cv2.resize(display_rects(img, rects), (0, 0), fx=0.5, fy=0.5))
146
146
 
@@ -0,0 +1,162 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+
4
+ from cv2.typing import MatLike
5
+
6
+ from kotonebot.primitives import Rect
7
+ from kotonebot import ocr, device, image, action
8
+ from kotonebot.backend.core import HintBox
9
+ from kotonebot.kaa.common import ProduceAction
10
+ from kotonebot.kaa.tasks import R
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # 三属性的当前值与最大值读取范围
15
+ CurVoValue = HintBox(x1=185, y1=680, x2=285, y2=720, source_resolution=(720, 1280))
16
+ CurDaValue = HintBox(x1=330, y1=680, x2=430, y2=720, source_resolution=(720, 1280))
17
+ CurViValue = HintBox(x1=475, y1=680, x2=575, y2=720, source_resolution=(720, 1280))
18
+ MaxDaValue = HintBox(x1=285, y1=720, x2=430, y2=750, source_resolution=(720, 1280))
19
+
20
+
21
+ @dataclass
22
+ class Lesson:
23
+ """
24
+ :param rect: 课程位置
25
+ :param sp: 是否为 SP 课程
26
+ :param act: 课程类型
27
+ :param cur_attr_value: 课程属性的当前值
28
+ :param max_attr_value: 课程属性的最大值
29
+ """
30
+ rect: Rect
31
+ sp: bool
32
+ act: ProduceAction
33
+ cur_attr_value: int
34
+ max_attr_value: int
35
+
36
+
37
+ class Schedule:
38
+ def __init__(self):
39
+ """
40
+ 初始化日程表
41
+ 这里本来想传入本次培育中的全局信息,比如 难度,偶像信息,各个课程sp率
42
+ 这些信息在整局培育中是相同的
43
+ """
44
+ pass
45
+
46
+ @action('检测日程是否有课程', screenshot_mode='manual-inherit')
47
+ def have_lesson(self) -> bool:
48
+ """
49
+ 判断是否有课程,依据是课程的名字的图片
50
+ TODO: NIA 课程的名字的图片没放到这里,后续若要支持NIA需要添加对应的图片
51
+ """
52
+ device.screenshot()
53
+ result = image.find_multi([
54
+ R.InPurodyuusu.TextActionVocal,
55
+ R.InPurodyuusu.TextActionDance,
56
+ R.InPurodyuusu.TextActionVisual,
57
+ ])
58
+ return result is not None
59
+
60
+ @action('识别日程,课程抉择,', screenshot_mode='manual-inherit')
61
+ def select_lesson(self) -> Lesson:
62
+ """
63
+ 选择课程,根据推荐、属性进行抉择
64
+ :return: 课程
65
+ """
66
+ recommended = self.read_sensei_recommended()
67
+ lesson_data = self.read_lesson_data()
68
+ sp_lessons, nor_lessons = [], []
69
+ for l in lesson_data:
70
+ sp_lessons.append(l) if l.sp else nor_lessons.append(l)
71
+ have_sp = len(sp_lessons) > 0
72
+ for lesson in lesson_data:
73
+ if lesson.act == recommended and lesson.sp == have_sp:
74
+ logger.info(f'Recommended: {lesson}')
75
+ return lesson
76
+
77
+ # TODO: 各个课程的属性上限需要根据难度+偶像属性要求进行调整,之后再根据此上限选择课程.目前设置上限为 attr_limit_ratio
78
+ cal_lesson = None
79
+ attr_limit_ratio = 0.8
80
+ if sp_lessons:
81
+ lesson = min(sp_lessons, key=lambda x: x.cur_attr_value)
82
+ if lesson.cur_attr_value < lesson.max_attr_value * attr_limit_ratio:
83
+ cal_lesson = lesson
84
+ if cal_lesson is None and nor_lessons:
85
+ cal_lesson = min(nor_lessons, key=lambda x: x.cur_attr_value)
86
+ if cal_lesson is None:
87
+ logger.warning('Lesson calculate error, select vocal')
88
+ return lesson_data[0]
89
+ logger.info(f'Recommended is not sp, calculate result: {cal_lesson}')
90
+ return cal_lesson
91
+
92
+ @action('读取日程中课程数据', screenshot_mode='manual-inherit')
93
+ def read_lesson_data(self) -> list[Lesson]:
94
+ """
95
+ 读取当前课程数据,包括位置,是否为sp,当前属性对应数值和最大值
96
+ :return: 课程数据列表
97
+ """
98
+ img = device.screenshot()
99
+ sp_list = image.find_all(R.InPurodyuusu.IconSp)
100
+ vo_sp = da_sp = vi_sp = False
101
+ vo = image.expect(R.InPurodyuusu.ButtonPracticeVocal)
102
+ da = image.expect(R.InPurodyuusu.ButtonPracticeDance)
103
+ vi = image.expect(R.InPurodyuusu.ButtonPracticeVisual)
104
+ for cur_sp in sp_list:
105
+ if cur_sp.position[0] < vo.position[0]:
106
+ vo_sp = True
107
+ elif vo.position[0] < cur_sp.position[0] < da.position[0]:
108
+ da_sp = True
109
+ elif da.position[0] < cur_sp.position[0] < vi.position[0]:
110
+ vi_sp = True
111
+ max_value = self.read_number(img, MaxDaValue)
112
+ lesson_data = [
113
+ Lesson(vo.rect, vo_sp, ProduceAction.VOCAL, self.read_number(img, CurVoValue), max_value),
114
+ Lesson(da.rect, da_sp, ProduceAction.DANCE, self.read_number(img, CurDaValue), max_value),
115
+ Lesson(vi.rect, vi_sp, ProduceAction.VISUAL, self.read_number(img, CurViValue), max_value),
116
+ ]
117
+ for lesson in lesson_data:
118
+ logger.info(f'Lesson: {lesson}')
119
+ return lesson_data
120
+
121
+ @action('读取日程中老师的推荐行动', screenshot_mode='manual-inherit')
122
+ def read_sensei_recommended(self) -> ProduceAction:
123
+ """
124
+ 读取老师的推荐行动
125
+ :return: 当前推荐行动,如果没推荐行动,返回 RECOMMENDED
126
+ """
127
+ device.screenshot()
128
+ if image.find(R.InPurodyuusu.IconAsariSenseiAvatar):
129
+ logger.debug('Retrieving recommended lesson...')
130
+ result = image.find_multi([
131
+ R.InPurodyuusu.TextSenseiTipVocal,
132
+ R.InPurodyuusu.TextSenseiTipDance,
133
+ R.InPurodyuusu.TextSenseiTipVisual,
134
+ R.InPurodyuusu.TextSenseiTipRest,
135
+ R.InPurodyuusu.TextSenseiTipConsult,
136
+ ])
137
+ if result:
138
+ match result.index:
139
+ case 0:
140
+ return ProduceAction.VOCAL
141
+ case 1:
142
+ return ProduceAction.DANCE
143
+ case 2:
144
+ return ProduceAction.VISUAL
145
+ case 3:
146
+ return ProduceAction.REST
147
+ case 4:
148
+ return ProduceAction.CONSULT
149
+ return ProduceAction.RECOMMENDED
150
+
151
+ def read_number(self, img: MatLike, box: HintBox) -> int:
152
+ """
153
+ :param img: MatLike 图像
154
+ :param box: HintBox 需要读取数值的范围
155
+ :return: int 数值,读取失败返回0
156
+ """
157
+ all_number = ocr.raw().ocr(img, rect=box)
158
+ all_number = all_number.squash().numbers()
159
+ if all_number:
160
+ return int(all_number[0])
161
+ else:
162
+ return 0
@@ -4,11 +4,12 @@ from typing import Literal
4
4
 
5
5
  import cv2
6
6
  import numpy as np
7
- from cv2.typing import MatLike, Rect
7
+ from cv2.typing import MatLike
8
8
 
9
- from kotonebot import device, color, action
9
+ from kotonebot import device, action
10
+ from kotonebot.primitives import Rect
10
11
  from kotonebot.backend.core import HintBox
11
- from kotonebot.backend.preprocessor import HsvColorFilter
12
+ from kotonebot.primitives.geometry import RectTuple
12
13
 
13
14
  logger = logging.getLogger(__name__)
14
15
 
@@ -77,7 +78,7 @@ def find_scroll_bar2(img: MatLike) -> Rect | None:
77
78
  contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
78
79
  # 找出最可能是滚动条的轮廓:
79
80
  # 宽高比 < 0.5,且形似矩形,且最长
80
- rects = []
81
+ rects: list[RectTuple] = []
81
82
  for contour in contours:
82
83
  x, y, w, h = cv2.boundingRect(contour)
83
84
  contour_area = cv2.contourArea(contour)
@@ -86,7 +87,7 @@ def find_scroll_bar2(img: MatLike) -> Rect | None:
86
87
  rects.append((x, y, w, h))
87
88
  if rects:
88
89
  longest_rect = max(rects, key=lambda r: r[2] * r[3])
89
- return longest_rect
90
+ return Rect(xywh=longest_rect)
90
91
  return None
91
92
 
92
93
  class ScrollableIterator:
@@ -190,7 +191,7 @@ class Scrollable:
190
191
  return False
191
192
  logger.debug('Scrollbar rect found.')
192
193
 
193
- x, y, w, h = self.scrollbar_rect
194
+ x, y, w, h = self.scrollbar_rect.xywh
194
195
  scroll_img = img[y:y+h, x:x+w]
195
196
  # 灰度、二值化
196
197
  gray = cv2.cvtColor(scroll_img, cv2.COLOR_BGR2GRAY)
@@ -0,0 +1,8 @@
1
+ from kotonebot.backend.context import vars
2
+ from kotonebot.client.host import Instance
3
+
4
+ def _set_instance(new_instance: Instance) -> None:
5
+ vars.set('instance', new_instance)
6
+
7
+ def instance() -> Instance:
8
+ return vars.get('instance')