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
kotonebot/__init__.py CHANGED
@@ -17,13 +17,11 @@ from .backend.context import (
17
17
  wait
18
18
  )
19
19
  from .util import (
20
- Rect,
21
20
  cropped,
22
21
  AdaptiveWait,
23
22
  Countdown,
24
23
  Interval,
25
24
  until,
26
- crop_rect,
27
25
  )
28
26
  from .backend.color import (
29
27
  hsv_cv2web,
kotonebot/backend/bot.py CHANGED
@@ -8,7 +8,7 @@ from typing_extensions import Self
8
8
  from dataclasses import dataclass, field
9
9
  from typing import Any, Literal, Callable, Generic, TypeVar, ParamSpec
10
10
 
11
- from kotonebot.ui import user
11
+ from kotonebot.client import Device
12
12
  from kotonebot.client.host.protocol import Instance
13
13
  from kotonebot.backend.context import init_context, vars
14
14
  from kotonebot.backend.context import task_registry, action_registry, Task, Action
@@ -130,49 +130,34 @@ class KotoneBot:
130
130
  logger.debug(f'Loading sub-module: {name}')
131
131
  try:
132
132
  importlib.import_module(name)
133
- except Exception as e:
133
+ except Exception:
134
134
  logger.error(f'Failed to load sub-module: {name}')
135
- logger.exception(f'Error: ')
135
+ logger.exception('Error: ')
136
136
 
137
137
  logger.info('Tasks and actions initialized.')
138
138
  logger.info(f'{len(task_registry)} task(s) and {len(action_registry)} action(s) loaded.')
139
139
 
140
- def check_backend(self):
141
- from kotonebot.client.host import create_custom
142
- from kotonebot.config.manager import load_config
143
- # HACK: 硬编码
144
- config = load_config(self.config_path, type=self.config_type)
145
- config = config.user_configs[0]
146
- logger.info('Checking backend...')
147
- if config.backend.type == 'custom' and config.backend.check_emulator:
148
- exe = config.backend.emulator_path
149
- if exe is None:
150
- user.error('「检查并启动模拟器」已开启但未配置「模拟器 exe 文件路径」。')
151
- raise ValueError('Emulator executable path is not set.')
152
- if not os.path.exists(exe):
153
- user.error('「模拟器 exe 文件路径」对应的文件不存在!请检查路径是否正确。')
154
- raise FileNotFoundError(f'Emulator executable not found: {exe}')
155
- self.backend_instance = create_custom(
156
- adb_ip=config.backend.adb_ip,
157
- adb_port=config.backend.adb_port,
158
- adb_emulator_name=config.backend.adb_emulator_name,
159
- exe_path=exe,
160
- emulator_args=config.backend.emulator_args
161
- )
162
- if not self.backend_instance.running():
163
- logger.info('Starting custom backend...')
164
- self.backend_instance.start()
165
- logger.info('Waiting for custom backend to be available...')
166
- self.backend_instance.wait_available()
167
- else:
168
- logger.info('Custom backend "%s" already running.', self.backend_instance)
140
+ def _on_create_device(self) -> Device:
141
+ """
142
+ 抽象方法,用于创建 Device 类,在 `run()` 方法执行前会被调用。
143
+
144
+ 所有子类都需要重写该方法。
145
+ """
146
+ raise NotImplementedError('Implement `_create_device` before using Kotonebot.')
147
+
148
+ def _on_after_init_context(self):
149
+ """
150
+ 抽象方法,在 init_context() 被调用后立即执行。
151
+ """
152
+ pass
169
153
 
170
154
  def run(self, tasks: list[Task], *, by_priority: bool = True):
171
155
  """
172
156
  按优先级顺序运行所有任务。
173
157
  """
174
- self.check_backend()
175
- init_context(config_path=self.config_path, config_type=self.config_type)
158
+ d = self._on_create_device()
159
+ init_context(config_path=self.config_path, config_type=self.config_type, target_device=d)
160
+ self._on_after_init_context()
176
161
  vars.interrupted.clear()
177
162
 
178
163
  if by_priority:
@@ -7,8 +7,8 @@ import cv2
7
7
  from cv2.typing import MatLike
8
8
 
9
9
  from .core import unify_image
10
+ from ..primitives import RectTuple, Rect
10
11
  from .debug import result as debug_result, debug, color as debug_color
11
- from ..util import Rect
12
12
 
13
13
  RgbColorTuple = tuple[int, int, int]
14
14
  RgbColorStr = str
@@ -131,6 +131,7 @@ def find(
131
131
  * rgb_dist:
132
132
  计算图片中每个点的颜色到目标颜色的欧氏距离,并以 442 为最大值归一化到 0-1 之间。
133
133
  """
134
+ _rect = rect.xywh if rect else None
134
135
  ret = None
135
136
  ret_similarity = 0
136
137
  found_color = None
@@ -167,8 +168,8 @@ def find(
167
168
  # 寻找结果
168
169
  matches: np.ndarray = dist <= (1 - threshold)
169
170
  # 只在rect范围内搜索
170
- if rect is not None:
171
- x, y, w, h = rect
171
+ if _rect is not None:
172
+ x, y, w, h = _rect
172
173
  search_area = matches[y:y+h, x:x+w]
173
174
  if search_area.any():
174
175
  # 在裁剪区域中找到最小距离的点
@@ -199,8 +200,8 @@ def find(
199
200
  (min(result_image.shape[1], x+20), min(result_image.shape[0], y+20)),
200
201
  (255, 0, 0), 2)
201
202
  # 绘制搜索范围
202
- if rect is not None:
203
- x, y, w, h = rect
203
+ if _rect is not None:
204
+ x, y, w, h = _rect
204
205
  # 红色圈出rect
205
206
  cv2.rectangle(result_image, (x, y), (x+w, y+h), (0, 0, 255), 2)
206
207
  debug_result(
@@ -219,7 +220,7 @@ def color_distance_map(
219
220
  image: MatLike | str,
220
221
  color: RgbColor,
221
222
  *,
222
- rect: Rect | None = None,
223
+ rect: RectTuple | None = None,
223
224
  ) -> np.ndarray:
224
225
  """
225
226
  计算图像中每个像素点到目标颜色的HSL距离,并返回归一化后的距离矩阵。
@@ -266,7 +267,7 @@ def color_distance_map(
266
267
  dist = np.sqrt((h_diff * 2)**2 + l_diff**2 + s_diff**2) / np.sqrt(6)
267
268
  return dist
268
269
 
269
- def _rect_intersection(rect1: Rect, rect2: Rect) -> Rect | None:
270
+ def _rect_intersection(rect1: RectTuple, rect2: RectTuple) -> RectTuple | None:
270
271
  """
271
272
  计算两个矩形的交集区域。
272
273
 
@@ -311,6 +312,7 @@ def find_all(
311
312
  :param max_results: 最大返回结果数量。如果为 None,则返回所有结果。
312
313
  :return: 结果列表。
313
314
  """
315
+ _rect: RectTuple | None = rect.xywh if rect is not None else None
314
316
  # 计算距离矩阵
315
317
  dist = color_distance_map(image, color)
316
318
  # 筛选满足要求的点,二值化
@@ -319,8 +321,8 @@ def find_all(
319
321
  def filter_by_point(binary: np.ndarray, target_color: RgbColor, dist: np.ndarray, rect: Rect | None = None, max_results: int | None = None) -> list[FindColorPointResult]:
320
322
  results = []
321
323
 
322
- if rect is not None:
323
- x, y, w, h = rect
324
+ if _rect is not None:
325
+ x, y, w, h = _rect
324
326
  search_area = binary[y:y+h, x:x+w]
325
327
  local_dist = dist[y:y+h, x:x+w]
326
328
 
@@ -370,8 +372,8 @@ def find_all(
370
372
  assert len(contour_rect) == 4
371
373
 
372
374
  # 如果指定了rect,计算轮廓外接矩形与rect的交集
373
- if rect is not None:
374
- intersection = _rect_intersection(contour_rect, rect)
375
+ if _rect is not None:
376
+ intersection = _rect_intersection(contour_rect, _rect)
375
377
  if intersection is None:
376
378
  continue
377
379
 
@@ -433,8 +435,8 @@ def find_all(
433
435
  (min(result_image.shape[1], x+10), min(result_image.shape[0], y+10)),
434
436
  (255, 0, 0), 1)
435
437
  # 绘制搜索范围
436
- if rect is not None:
437
- x, y, w, h = rect
438
+ if _rect is not None:
439
+ x, y, w, h = _rect
438
440
  cv2.rectangle(result_image, (x, y), (x+w, y+h), (0, 0, 255), 2)
439
441
 
440
442
  debug_result(
@@ -466,10 +468,11 @@ def dominant_color(
466
468
  :param count: 提取的颜色数量。默认为 1。
467
469
  :param rect: 提取范围。如果为 None,则在整个图像中提取。
468
470
  """
471
+ _rect: RectTuple | None = rect.xywh if rect is not None else None
469
472
  # 载入/裁剪图像
470
473
  img = unify_image(image)
471
- if rect is not None:
472
- x, y, w, h = rect
474
+ if _rect is not None:
475
+ x, y, w, h = _rect
473
476
  img = img[y:y+h, x:x+w]
474
477
 
475
478
  pixels = np.float32(img.reshape(-1, 3))
@@ -497,8 +500,8 @@ def dominant_color(
497
500
  if debug.enabled:
498
501
  origin_image = unify_image(image)
499
502
  result_image = origin_image.copy()
500
- if rect is not None:
501
- x, y, w, h = rect
503
+ if _rect is not None:
504
+ x, y, w, h = _rect
502
505
  cv2.rectangle(result_image, (x, y), (x + w, y + h), (0, 0, 255), 2)
503
506
  debug_result(
504
507
  'color.dominant_color',
@@ -25,7 +25,6 @@ import cv2
25
25
  from cv2.typing import MatLike
26
26
 
27
27
  from kotonebot.client.device import Device
28
- from kotonebot.util import Rect
29
28
  import kotonebot.backend.image as raw_image
30
29
  from kotonebot.backend.image import (
31
30
  TemplateMatchResult,
@@ -52,6 +51,7 @@ from kotonebot.backend.core import Image, HintBox
52
51
  from kotonebot.errors import KotonebotWarning
53
52
  from kotonebot.client.factory import DeviceImpl
54
53
  from kotonebot.backend.preprocessor import PreprocessorProtocol
54
+ from kotonebot.primitives import Rect
55
55
 
56
56
  OcrLanguage = Literal['jp', 'en']
57
57
  ScreenshotMode = Literal['auto', 'manual', 'manual-inherit']
@@ -174,11 +174,30 @@ def is_manual_screenshot_mode() -> bool:
174
174
 
175
175
  class ContextGlobalVars:
176
176
  def __init__(self):
177
- self.auto_collect: bool = False
178
- """遇到未知P饮料/卡片时,是否自动截图并收集"""
177
+ self.__vars = dict[str, Any]()
179
178
  self.interrupted: Event = Event()
180
179
  """用户请求中断事件"""
181
180
 
181
+ def __getitem__(self, key: str) -> Any:
182
+ return self.__vars[key]
183
+
184
+ def __setitem__(self, key: str, value: Any) -> None:
185
+ self.__vars[key] = value
186
+
187
+ def __delitem__(self, key: str) -> None:
188
+ del self.__vars[key]
189
+
190
+ def __contains__(self, key: str) -> bool:
191
+ return key in self.__vars
192
+
193
+ def get(self, key: str, default: Any = None) -> Any:
194
+ return self.__vars.get(key, default)
195
+
196
+ def set(self, key: str, value: Any) -> None:
197
+ self.__vars[key] = value
198
+
199
+ def clear(self):
200
+ self.__vars.clear()
182
201
 
183
202
  class ContextStackVars:
184
203
  stack: list['ContextStackVars'] = []
@@ -730,7 +749,13 @@ class ContextDevice(Device):
730
749
 
731
750
 
732
751
  class Context(Generic[T]):
733
- def __init__(self, config_path: str, config_type: Type[T], screenshot_impl: Optional[DeviceImpl] = None):
752
+ def __init__(
753
+ self,
754
+ config_path: str,
755
+ config_type: Type[T],
756
+ screenshot_impl: Optional[DeviceImpl] = None,
757
+ device: Optional[Device] = None
758
+ ):
734
759
  self.__ocr = ContextOcr(self)
735
760
  self.__image = ContextImage(self)
736
761
  self.__color = ContextColor(self)
@@ -744,7 +769,7 @@ class Context(Generic[T]):
744
769
  if screenshot_impl is None:
745
770
  screenshot_impl = self.config.current.backend.screenshot_impl
746
771
  logger.info(f'Using "{screenshot_impl}" as screenshot implementation')
747
- self.__device = ContextDevice(create_device(f'{ip}:{port}', screenshot_impl))
772
+ self.__device = ContextDevice(device or create_device(f'{ip}:{port}', screenshot_impl))
748
773
 
749
774
  def inject(
750
775
  self,
@@ -803,11 +828,12 @@ class Context(Generic[T]):
803
828
  def config(self) -> 'ContextConfig[T]':
804
829
  return self.__config
805
830
 
831
+ @deprecated('使用 Rect 类的实例方法代替')
806
832
  def rect_expand(rect: Rect, left: int = 0, top: int = 0, right: int = 0, bottom: int = 0) -> Rect:
807
833
  """
808
834
  向四个方向扩展矩形区域。
809
835
  """
810
- return (rect[0] - left, rect[1] - top, rect[2] + right + left, rect[3] + bottom + top)
836
+ return Rect(rect.x1 - left, rect.y1 - top, rect.w + right + left, rect.h + bottom + top)
811
837
 
812
838
  def use_screenshot(*args: MatLike | None) -> MatLike:
813
839
  for img in args:
@@ -854,7 +880,8 @@ def init_context(
854
880
  config_path: str = 'config.json',
855
881
  config_type: Type[T] = dict[str, Any],
856
882
  force: bool = False,
857
- screenshot_impl: Optional[DeviceImpl] = None
883
+ screenshot_impl: Optional[DeviceImpl] = None,
884
+ target_device: Device | None = None,
858
885
  ):
859
886
  """
860
887
  初始化 Context 模块。
@@ -867,11 +894,17 @@ def init_context(
867
894
  若为 `True`,则忽略已存在的 Context 实例,并重新创建一个新的实例。
868
895
  :param screenshot_impl: 截图实现。
869
896
  若为 `None`,则使用默认配置文件中指定的截图实现。
897
+ :param target_device: 目标设备
870
898
  """
871
899
  global _c, device, ocr, image, color, vars, debug, config
872
900
  if _c is not None and not force:
873
901
  return
874
- _c = Context(config_path=config_path, config_type=config_type, screenshot_impl=screenshot_impl)
902
+ _c = Context(
903
+ config_path=config_path,
904
+ config_type=config_type,
905
+ screenshot_impl=screenshot_impl,
906
+ device=target_device
907
+ )
875
908
  device._FORWARD_getter = lambda: _c.device # type: ignore
876
909
  ocr._FORWARD_getter = lambda: _c.ocr # type: ignore
877
910
  image._FORWARD_getter = lambda: _c.image # type: ignore
kotonebot/backend/core.py CHANGED
@@ -1,14 +1,13 @@
1
1
  import logging
2
2
  from functools import cache
3
- from typing import Callable, overload, TYPE_CHECKING
3
+ from typing import Callable
4
4
 
5
5
  import cv2
6
6
  from cv2.typing import MatLike
7
7
 
8
8
  from kotonebot.util import cv2_imread
9
+ from kotonebot.primitives import RectTuple, Rect, Point
9
10
  from kotonebot.errors import ResourceFileMissingError
10
- if TYPE_CHECKING:
11
- from kotonebot.util import Rect
12
11
 
13
12
  class Ocr:
14
13
  def __init__(
@@ -67,20 +66,7 @@ class Image:
67
66
  return f'<Image: "{self.name}" at {self.path}>'
68
67
 
69
68
 
70
- class HintBox(tuple[int, int, int, int]):
71
- def __new__(
72
- cls,
73
- x1: int,
74
- y1: int,
75
- x2: int,
76
- y2: int,
77
- *,
78
- source_resolution: tuple[int, int],
79
- ):
80
- w = x2 - x1
81
- h = y2 - y1
82
- return super().__new__(cls, [x1, y1, w, h])
83
-
69
+ class HintBox(Rect):
84
70
  def __init__(
85
71
  self,
86
72
  x1: int,
@@ -92,11 +78,7 @@ class HintBox(tuple[int, int, int, int]):
92
78
  description: str | None = None,
93
79
  source_resolution: tuple[int, int],
94
80
  ):
95
- self.x1 = x1
96
- self.y1 = y1
97
- self.x2 = x2
98
- self.y2 = y2
99
- self.name = name
81
+ super().__init__(x1, y1, x2 - x1, y2 - y1, name=name)
100
82
  self.description = description
101
83
  self.source_resolution = source_resolution
102
84
 
@@ -109,17 +91,12 @@ class HintBox(tuple[int, int, int, int]):
109
91
  return self.y2 - self.y1
110
92
 
111
93
  @property
112
- def rect(self) -> 'Rect':
94
+ def rect(self) -> RectTuple:
113
95
  return self.x1, self.y1, self.width, self.height
114
96
 
115
- class HintPoint(tuple[int, int]):
116
- def __new__(cls, x: int, y: int):
117
- return super().__new__(cls, (x, y))
118
-
97
+ class HintPoint(Point):
119
98
  def __init__(self, x: int, y: int, *, name: str | None = None, description: str | None = None):
120
- self.x = x
121
- self.y = y
122
- self.name = name
99
+ super().__init__(x, y, name=name)
123
100
  self.description = description
124
101
 
125
102
  def __repr__(self) -> str:
@@ -11,7 +11,7 @@ from typing_extensions import deprecated
11
11
  from dataclasses import dataclass
12
12
 
13
13
  from kotonebot.backend.ocr import StringMatchFunction
14
- from kotonebot.util import Rect, is_rect
14
+ from kotonebot.primitives import Rect, is_rect
15
15
 
16
16
  from .core import Image
17
17
 
@@ -1,16 +1,16 @@
1
1
  import os
2
2
  from logging import getLogger
3
- from typing import NamedTuple, Protocol, TypeVar, Sequence, runtime_checkable
3
+ from typing import NamedTuple, Protocol, Sequence, runtime_checkable
4
4
 
5
5
  import cv2
6
6
  import numpy as np
7
- from cv2.typing import MatLike, Size
7
+ from cv2.typing import MatLike, Rect as CvRect
8
8
  from skimage.metrics import structural_similarity
9
9
 
10
10
  from .core import Image, unify_image
11
- from ..util import Rect, Point
12
- from .debug import result as debug_result, debug, img
13
11
  from .preprocessor import PreprocessorProtocol
12
+ from kotonebot.primitives import Point as KbPoint, Rect as KbRect, Size as KbSize
13
+ from .debug import result as debug_result, debug, img
14
14
 
15
15
  logger = getLogger(__name__)
16
16
 
@@ -24,46 +24,46 @@ class TemplateNoMatchError(Exception):
24
24
  @runtime_checkable
25
25
  class ResultProtocol(Protocol):
26
26
  @property
27
- def rect(self) -> Rect:
27
+ def rect(self) -> KbRect:
28
28
  """结果区域。左上角坐标和宽高。"""
29
29
  ...
30
30
 
31
31
 
32
32
  class TemplateMatchResult(NamedTuple):
33
33
  score: float
34
- position: Point
34
+ position: KbPoint
35
35
  """结果位置。左上角坐标。"""
36
- size: Size
36
+ size: KbSize
37
37
  """输入模板的大小。宽高。"""
38
38
 
39
39
  @property
40
- def rect(self) -> Rect:
41
- """结果区域。左上角坐标和宽高。"""
42
- return (self.position[0], self.position[1], self.size[0], self.size[1])
40
+ def rect(self) -> KbRect:
41
+ """结果区域。"""
42
+ return KbRect(self.position[0], self.position[1], self.size[0], self.size[1])
43
43
 
44
44
  @property
45
- def right_bottom(self) -> Point:
45
+ def right_bottom(self) -> KbPoint:
46
46
  """结果右下角坐标。"""
47
- return (self.position[0] + self.size[0], self.position[1] + self.size[1])
47
+ return KbPoint(self.position[0] + self.size[0], self.position[1] + self.size[1])
48
48
 
49
49
  class MultipleTemplateMatchResult(NamedTuple):
50
50
  score: float
51
- position: Point
51
+ position: KbPoint
52
52
  """结果位置。左上角坐标。"""
53
- size: Size
53
+ size: KbSize
54
54
  """命中模板的大小。宽高。"""
55
55
  index: int
56
56
  """命中模板在列表中的索引。"""
57
57
 
58
58
  @property
59
- def rect(self) -> Rect:
59
+ def rect(self) -> KbRect:
60
60
  """结果区域。左上角坐标和宽高。"""
61
- return (self.position[0], self.position[1], self.size[0], self.size[1])
61
+ return KbRect(self.position[0], self.position[1], self.size[0], self.size[1])
62
62
 
63
63
  @property
64
- def right_bottom(self) -> Point:
64
+ def right_bottom(self) -> KbPoint:
65
65
  """结果右下角坐标。"""
66
- return (self.position[0] + self.size[0], self.position[1] + self.size[1])
66
+ return KbPoint(self.position[0] + self.size[0], self.position[1] + self.size[1])
67
67
 
68
68
  @classmethod
69
69
  def from_template_match_result(cls, result: TemplateMatchResult, index: int):
@@ -76,13 +76,13 @@ class MultipleTemplateMatchResult(NamedTuple):
76
76
 
77
77
  class CropResult(NamedTuple):
78
78
  score: float
79
- position: Point
80
- size: Size
79
+ position: KbPoint
80
+ size: KbSize
81
81
  image: MatLike
82
82
 
83
83
  @property
84
- def rect(self) -> Rect:
85
- return (self.position[0], self.position[1], self.size[0], self.size[1])
84
+ def rect(self) -> KbRect:
85
+ return KbRect(self.position[0], self.position[1], self.size[0], self.size[1])
86
86
 
87
87
  def _draw_result(image: MatLike, matches: Sequence[ResultProtocol] | ResultProtocol | None) -> MatLike:
88
88
  """在图像上绘制匹配结果的矩形框。"""
@@ -92,7 +92,7 @@ def _draw_result(image: MatLike, matches: Sequence[ResultProtocol] | ResultProto
92
92
  matches = [matches]
93
93
  result_image = image.copy()
94
94
  for match in matches:
95
- cv2.rectangle(result_image, match.rect, (0, 0, 255), 2)
95
+ cv2.rectangle(result_image, match.rect.xywh, (0, 0, 255), 2)
96
96
  return result_image
97
97
 
98
98
  def _img2str(image: MatLike | str | Image | None) -> str:
@@ -229,8 +229,8 @@ def template_match(
229
229
 
230
230
  matches.append(TemplateMatchResult(
231
231
  score=score,
232
- position=(int(x), int(y)),
233
- size=(int(w), int(h))
232
+ position=KbPoint(int(x), int(y)),
233
+ size=KbSize(int(w), int(h))
234
234
  ))
235
235
 
236
236
  # 如果达到最大结果数,提前结束
@@ -242,7 +242,7 @@ def template_match(
242
242
  def hist_match(
243
243
  image: MatLike | str,
244
244
  template: MatLike | str,
245
- rect: Rect | None = None,
245
+ rect: CvRect | None = None,
246
246
  threshold: float = 0.9,
247
247
  ) -> bool:
248
248
  """
kotonebot/backend/ocr.py CHANGED
@@ -15,8 +15,9 @@ from thefuzz import fuzz as _fuzz
15
15
  from rapidocr_onnxruntime import RapidOCR
16
16
 
17
17
 
18
+ from ..util import lf_path
19
+ from ..primitives import Rect, Point
18
20
  from .core import HintBox, Image, unify_image
19
- from ..util import Rect, lf_path
20
21
  from .debug import result as debug_result, debug
21
22
 
22
23
  logger = logging.getLogger(__name__)
@@ -65,16 +66,16 @@ class OcrResultList(list[OcrResult]):
65
66
  将所有识别结果合并为一个大结果。
66
67
  """
67
68
  if not self:
68
- return OcrResult('', (0, 0, 0, 0), 0, (0, 0, 0, 0))
69
+ return OcrResult('', Rect(0, 0, 0, 0), 0, Rect(0, 0, 0, 0))
69
70
  text = [r.text for r in self]
70
71
  confidence = sum(r.confidence for r in self) / len(self)
71
72
  points = []
72
73
  for r in self:
73
- points.append((r.rect[0], r.rect[1]))
74
- points.append((r.rect[0] + r.rect[2], r.rect[1]))
75
- points.append((r.rect[0], r.rect[1] + r.rect[3]))
76
- points.append((r.rect[0] + r.rect[2], r.rect[1] + r.rect[3]))
77
- rect = bounding_box(points)
74
+ points.append(Point(r.rect.x1, r.rect.y1))
75
+ points.append(Point(r.rect.x1 + r.rect.w, r.rect.y1))
76
+ points.append(Point(r.rect.x1, r.rect.y1 + r.rect.h))
77
+ points.append(Point(r.rect.x1 + r.rect.w, r.rect.y1 + r.rect.h))
78
+ rect = Rect(xywh=bounding_box(points))
78
79
  text = '\n'.join(text)
79
80
  if remove_newlines:
80
81
  text = text.replace('\n', '')
@@ -245,7 +246,7 @@ def _draw_result(image: 'MatLike', result: list[OcrResult]) -> 'MatLike':
245
246
  for r in result:
246
247
  # 画矩形框
247
248
  draw.rectangle(
248
- [r.rect[0], r.rect[1], r.rect[0] + r.rect[2], r.rect[1] + r.rect[3]],
249
+ [r.rect.x1, r.rect.y1, r.rect.x1 + r.rect.w, r.rect.y1 + r.rect.h],
249
250
  outline=(255, 0, 0),
250
251
  width=2
251
252
  )
@@ -257,8 +258,8 @@ def _draw_result(image: 'MatLike', result: list[OcrResult]) -> 'MatLike':
257
258
  text_height = text_bbox[3] - text_bbox[1]
258
259
 
259
260
  # 计算文本位置
260
- text_x = r.rect[0]
261
- text_y = r.rect[1] - text_height - 5 if r.rect[1] > text_height + 5 else r.rect[1] + r.rect[3] + 5
261
+ text_x = r.rect.x1
262
+ text_y = r.rect.y1 - text_height - 5 if r.rect.y1 > text_height + 5 else r.rect.y1 + r.rect.h + 5
262
263
 
263
264
  # 添加padding
264
265
  padding = 4
@@ -313,7 +314,7 @@ class Ocr:
313
314
  :return: 所有识别结果
314
315
  """
315
316
  if rect is not None:
316
- x, y, w, h = rect
317
+ x, y, w, h = rect.xywh
317
318
  img = img[y:y+h, x:x+w]
318
319
  original_img = img
319
320
  if pad:
@@ -338,8 +339,8 @@ class Ocr:
338
339
  # result_rect (x, y, w, h)
339
340
  if rect is not None:
340
341
  original_rect = (
341
- result_rect[0] + rect[0] - pos_in_padded_img[0],
342
- result_rect[1] + rect[1] - pos_in_padded_img[1],
342
+ result_rect[0] + rect.x1 - pos_in_padded_img[0],
343
+ result_rect[1] + rect.y1 - pos_in_padded_img[1],
343
344
  result_rect[2],
344
345
  result_rect[3]
345
346
  )
@@ -352,8 +353,8 @@ class Ocr:
352
353
  confidence = float(r[2])
353
354
  ret.append(OcrResult(
354
355
  text=text,
355
- rect=result_rect,
356
- original_rect=original_rect,
356
+ rect=Rect(xywh=result_rect),
357
+ original_rect=Rect(xywh=original_rect),
357
358
  confidence=confidence
358
359
  ))
359
360
  ret = OcrResultList(ret)
@@ -392,7 +393,7 @@ class Ocr:
392
393
  """
393
394
  if hint is not None:
394
395
  warnings.warn("使用 `rect` 参数代替")
395
- if ret := self.find(img, text, rect=hint):
396
+ if ret := self.find(img, text, rect=Rect(xywh=hint.rect)):
396
397
  logger.debug(f"find: {text} SUCCESS [hint={hint}]")
397
398
  return ret
398
399
  logger.debug(f"find: {text} FAILED [hint={hint}]")
@@ -430,7 +431,7 @@ class Ocr:
430
431
  # HintBox 处理
431
432
  if hint is not None:
432
433
  warnings.warn("使用 `rect` 参数代替")
433
- result = self.find_all(img, texts, rect=hint, pad=pad)
434
+ result = self.find_all(img, texts, rect=Rect(xywh=hint.rect), pad=pad)
434
435
  if all(result):
435
436
  return result
436
437
 
@@ -1,4 +1,3 @@
1
-
2
1
  from typing import Protocol, Literal
3
2
 
4
3
  import cv2
@@ -1 +1,8 @@
1
- from .device import Device
1
+ from .device import Device
2
+ from .factory import create_device, DeviceImpl
3
+
4
+ __all__ = [
5
+ 'Device',
6
+ 'create_device',
7
+ 'DeviceImpl',
8
+ ]