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.
- kotonebot/__init__.py +0 -2
- kotonebot/backend/bot.py +19 -34
- kotonebot/backend/color.py +20 -17
- kotonebot/backend/context/context.py +41 -8
- kotonebot/backend/core.py +7 -30
- kotonebot/backend/dispatch.py +1 -1
- kotonebot/backend/image.py +26 -26
- kotonebot/backend/ocr.py +18 -17
- kotonebot/backend/preprocessor.py +0 -1
- kotonebot/client/__init__.py +8 -1
- kotonebot/client/device.py +7 -20
- kotonebot/client/factory.py +33 -5
- kotonebot/client/host/__init__.py +8 -1
- kotonebot/client/host/custom.py +7 -2
- kotonebot/client/host/leidian_host.py +200 -0
- kotonebot/client/host/mumu12_host.py +177 -0
- kotonebot/client/host/protocol.py +68 -38
- kotonebot/client/implements/adb.py +1 -1
- kotonebot/client/protocol.py +1 -1
- kotonebot/config/base_config.py +8 -3
- kotonebot/interop/win/reg.py +37 -0
- kotonebot/kaa/common.py +18 -2
- kotonebot/kaa/game_ui/badge.py +6 -6
- kotonebot/kaa/game_ui/common.py +6 -6
- kotonebot/kaa/game_ui/commu_event_buttons.py +4 -3
- kotonebot/kaa/game_ui/idols_overview.py +6 -6
- kotonebot/kaa/game_ui/schedule.py +162 -0
- kotonebot/kaa/game_ui/scrollable.py +7 -6
- kotonebot/kaa/kaa_context.py +8 -0
- kotonebot/kaa/main/cli.py +7 -0
- kotonebot/kaa/main/dmm_host.py +53 -0
- kotonebot/kaa/main/gr.py +508 -298
- kotonebot/kaa/main/kaa.py +77 -1
- kotonebot/kaa/metadata.py +26 -0
- kotonebot/kaa/resources/__pycache__/__init__.cpython-310.pyc +0 -0
- kotonebot/kaa/resources/game.db +0 -0
- kotonebot/kaa/resources/game_ver.txt +0 -0
- kotonebot/kaa/sprites/4503db6b-7224-4b81-9971-e7cfa56e10f2.png +0 -0
- kotonebot/kaa/tasks/R.py +137 -133
- kotonebot/kaa/tasks/daily/contest.py +123 -89
- kotonebot/kaa/tasks/daily/mission_reward.py +4 -4
- kotonebot/kaa/tasks/end_game.py +2 -9
- kotonebot/kaa/tasks/produce/cards.py +7 -6
- kotonebot/kaa/tasks/produce/common.py +16 -12
- kotonebot/kaa/tasks/produce/in_purodyuusu.py +5 -5
- kotonebot/kaa/tasks/produce/p_drink.py +4 -3
- kotonebot/kaa/tasks/produce/produce.py +5 -3
- kotonebot/kaa/tasks/start_game.py +2 -2
- kotonebot/primitives/__init__.py +17 -0
- kotonebot/primitives/geometry.py +290 -0
- kotonebot/primitives/visual.py +63 -0
- kotonebot/util.py +20 -20
- {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/METADATA +1 -1
- {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/RECORD +191 -183
- {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/WHEEL +1 -1
- /kotonebot/{client/host/mumu_host.py → interop/win/__init__.py} +0 -0
- /kotonebot/kaa/sprites/{7971b595-44d3-4ce7-91ae-a8e5a25c543b.png → 02ff2b86-db5a-4850-ac98-4288a3f71ed8.png} +0 -0
- /kotonebot/kaa/sprites/{bc64dd37-48ba-45a5-af6d-457a7fb2c9fd.png → 04422a5d-2499-40fd-a08f-095ecbd051a8.png} +0 -0
- /kotonebot/kaa/sprites/{17da97eb-93c5-438f-8688-0be5b0756019.png → 0672e1c6-6abc-452c-82b3-af6eb7454cfa.png} +0 -0
- /kotonebot/kaa/sprites/{5089ea64-3f32-4520-a252-4ee278815cdd.png → 07960be4-b575-4f7f-be64-7e344dc118e0.png} +0 -0
- /kotonebot/kaa/sprites/{0f67c0a6-45d0-4953-b1a9-b5eb2c916173.png → 09cd4336-7d2a-4bce-9d08-8ac64a2bdcc3.png} +0 -0
- /kotonebot/kaa/sprites/{5ed9bef4-ab11-4186-9b21-a479eec687ce.png → 09fda5a8-c546-48f0-ae29-ebe4f4208d8b.png} +0 -0
- /kotonebot/kaa/sprites/{7dec3bd9-f8ee-430f-af54-0c03c4c91546.png → 0b5c2d3f-28b1-456f-a452-05ca006f8b33.png} +0 -0
- /kotonebot/kaa/sprites/{76b0e3b3-3438-4e88-aca8-da18c8e50ddd.png → 0c359f2a-72d4-4203-9ca3-968a1948eee8.png} +0 -0
- /kotonebot/kaa/sprites/{9e110778-1b66-4700-b462-64c1dea368da.png → 0ecc3e37-189c-40ad-9ee6-fb576b72a13b.png} +0 -0
- /kotonebot/kaa/sprites/{bbcb97e2-89b7-4de2-b480-a0442fc33b1e.png → 11c2ff97-3303-4d6d-8e4f-6da2148bbe20.png} +0 -0
- /kotonebot/kaa/sprites/{36d712ce-9227-4aef-86ee-4f78415b0c80.png → 128886a7-7629-4158-9538-3619542f775b.png} +0 -0
- /kotonebot/kaa/sprites/{ba07fa5e-3451-4520-8b10-0b240e5decec.png → 13a85dbd-e226-47cc-b6f5-c0d3b40654f5.png} +0 -0
- /kotonebot/kaa/sprites/{f71df091-fc71-48d4-8124-b3c46f4c4436.png → 13c8fa4a-0ae5-4ee1-a433-20f6c724466e.png} +0 -0
- /kotonebot/kaa/sprites/{02e69560-ec42-4453-b771-8600b5726b93.png → 18042dda-0222-4333-9d79-3afc3dc75220.png} +0 -0
- /kotonebot/kaa/sprites/{420e4ff4-82a3-4ea3-98a7-63efe65003a6.png → 18670c18-07e3-4378-a8fc-4f9b331db00f.png} +0 -0
- /kotonebot/kaa/sprites/{027dd54f-09d8-452d-8eff-66b06aecd264.png → 1959c932-1bbf-4fee-b3a1-2ecfbc6d4209.png} +0 -0
- /kotonebot/kaa/sprites/{b673cc06-14ef-4a22-b484-13a17b01b0d4.png → 1a8ec83b-ebba-4ff7-9737-104d3aa2ff98.png} +0 -0
- /kotonebot/kaa/sprites/{1e6d734e-7bc5-4f3f-a717-3931f44a7709.png → 1b6bc9ae-3f4e-4722-ae6f-d76bd65b071e.png} +0 -0
- /kotonebot/kaa/sprites/{0bd326fd-fad0-4e6c-957c-da08583ef231.png → 1e4671aa-2cdc-4432-9d83-c635e0a3d09d.png} +0 -0
- /kotonebot/kaa/sprites/{e941ae91-59bf-4a69-945b-1bcd7cf241c6.png → 2490bd58-bcf7-4c94-ab24-04e752137939.png} +0 -0
- /kotonebot/kaa/sprites/{f790aed5-b9f3-4c93-a14f-47629f53052d.png → 25cfb896-fed1-4361-ae64-fc45fc8a98dd.png} +0 -0
- /kotonebot/kaa/sprites/{6e81e54e-b69b-434a-8bbf-6e2551bddeb8.png → 2730beb1-a6c5-463d-9748-393764bc8e34.png} +0 -0
- /kotonebot/kaa/sprites/{3f0b91e1-898a-4f44-b116-38483bc30461.png → 2766af5c-4ad1-44e2-ab84-e8a6ac7bb172.png} +0 -0
- /kotonebot/kaa/sprites/{4fca090a-b94a-433a-8438-832a54ff65e8.png → 2cc68858-7af0-4a12-8c78-c4e4dcf9fff1.png} +0 -0
- /kotonebot/kaa/sprites/{04b4ccde-a374-4df3-b0fa-552ae1518348.png → 2e68066d-9a56-48cc-8711-1d4c080ee320.png} +0 -0
- /kotonebot/kaa/sprites/{4a9e612b-9e52-4eb5-a851-3e5ad68030e9.png → 32d50687-b3a2-4b34-a2f9-4418b5e14fff.png} +0 -0
- /kotonebot/kaa/sprites/{2179ff5c-7964-42b1-9a95-c0bd8af1b692.png → 344bedf1-fcca-4025-9ff6-cea99c91ddb4.png} +0 -0
- /kotonebot/kaa/sprites/{99e95c59-d5da-448d-b839-51428b2686f0.png → 346da82f-bfca-4a0f-bae4-ca9c8e4b794e.png} +0 -0
- /kotonebot/kaa/sprites/{be7504d5-48e5-404c-a74d-f7fa86acf276.png → 379a8811-932c-4c75-854f-943631c921d5.png} +0 -0
- /kotonebot/kaa/sprites/{d11d8839-90ef-433c-8555-fad5478009a0.png → 38f8f55f-e9dc-4c10-b97a-b7254d871ae9.png} +0 -0
- /kotonebot/kaa/sprites/{03c9182c-b5ba-4404-b6f9-50b06a86e102.png → 39b1738f-af9a-4c85-9315-2858f0b1d1ab.png} +0 -0
- /kotonebot/kaa/sprites/{fb529219-fc53-43e1-83ca-2313d1d33636.png → 3a58e019-6328-412a-9d2f-85a73900371e.png} +0 -0
- /kotonebot/kaa/sprites/{d4673e38-cf76-4062-9a71-0f31936fe49b.png → 3c2f70b5-9095-48d6-a012-4d68a6b0a975.png} +0 -0
- /kotonebot/kaa/sprites/{e753b92e-99bf-4d81-9673-92b5f67d129b.png → 3d016208-f8ed-45be-bfb1-ed15b231d0b8.png} +0 -0
- /kotonebot/kaa/sprites/{0b0e76ce-60cc-45cc-b204-227e59d39c26.png → 3e2af23e-041f-4128-8a78-cbd1297538cf.png} +0 -0
- /kotonebot/kaa/sprites/{edb144e1-7c82-4fcf-b426-c6294ad2112d.png → 429c30b5-7d3a-40a1-853a-ec51c8f238e7.png} +0 -0
- /kotonebot/kaa/sprites/{de619141-10a4-4447-89b3-6cabcbf52d8a.png → 440eeea3-b710-485e-ab40-80e2b6ce66f0.png} +0 -0
- /kotonebot/kaa/sprites/{1607c23d-3ab4-418b-8114-1a3bb782d246.png → 46f24175-8657-497d-97e6-1c1ee356e4cd.png} +0 -0
- /kotonebot/kaa/sprites/{3eb037db-4cd8-4c00-a655-fa73f612adfd.png → 4a128979-bf11-42fe-a966-a8f5ee143128.png} +0 -0
- /kotonebot/kaa/sprites/{7b07a31c-9e2b-4ce0-8998-ccc0592de5e0.png → 4a5ec641-38ec-46b0-83c4-d7edacb754b0.png} +0 -0
- /kotonebot/kaa/sprites/{e97e58cb-3412-4a8c-9a9e-dd45daa5ae02.png → 4a9c1ac7-0a61-4d40-b749-e8896322581f.png} +0 -0
- /kotonebot/kaa/sprites/{9fd268df-96b7-4b48-a6d1-b5da2161da8e.png → 4af7631b-3674-4491-a3fb-a514bfd6b204.png} +0 -0
- /kotonebot/kaa/sprites/{32a1072b-c359-4dad-80f6-c9ca558e5f99.png → 4c2c5378-d946-41c2-980e-afb8493f71ee.png} +0 -0
- /kotonebot/kaa/sprites/{1a668ad4-d6b0-4aed-bb99-8b1ac85659d3.png → 4c4dd8e5-5370-4381-b6d4-c260b32015cf.png} +0 -0
- /kotonebot/kaa/sprites/{32793e3b-613b-4b6d-bc2e-25f9993f6274.png → 4c5b8c40-3dfa-4091-95c3-257a36e9a6d3.png} +0 -0
- /kotonebot/kaa/sprites/{d3fa5988-1184-4478-abb9-1c9799bd4100.png → 4d0b8533-b3e2-4ea1-8204-a8d5e67e1f64.png} +0 -0
- /kotonebot/kaa/sprites/{0de838fb-24e9-4fe8-8e2a-cc26bce6d7d8.png → 50e462d5-ca14-4934-b35a-da3ebb8fb33f.png} +0 -0
- /kotonebot/kaa/sprites/{d2830bf7-e0c4-4d00-868a-6df22bdf6fcd.png → 557389eb-d0da-4c5a-a322-de3e32a81909.png} +0 -0
- /kotonebot/kaa/sprites/{979c7468-d9ef-4cc1-9449-2aa716caabb0.png → 57b7006a-ada9-4d9d-9655-71765251882e.png} +0 -0
- /kotonebot/kaa/sprites/{8f39322e-92d2-4cc6-80ef-341884b0a598.png → 59ef821b-4803-4fe3-83b8-305902f8459a.png} +0 -0
- /kotonebot/kaa/sprites/{a0b83694-4945-4476-aa2e-f268841ac372.png → 5af71b93-310c-48ab-b438-dba296fb9e52.png} +0 -0
- /kotonebot/kaa/sprites/{a03e79b3-7f44-4211-b625-2510ba8031bc.png → 5b6b5bf8-cd03-4360-9550-e15d8db6241a.png} +0 -0
- /kotonebot/kaa/sprites/{2fd33c51-b3a7-4ffb-9555-d25961186eee.png → 5d963589-623d-4a49-9c72-af91134720b0.png} +0 -0
- /kotonebot/kaa/sprites/{f4c8a757-674e-45ca-af2c-0bc409b7bca8.png → 5f01b811-9aad-4439-a70b-c099a42c36fa.png} +0 -0
- /kotonebot/kaa/sprites/{a1c29e77-138c-4580-a777-666815039524.png → 601d0743-7143-4c04-989b-b80fc620e678.png} +0 -0
- /kotonebot/kaa/sprites/{4aed7e90-76b1-4837-a6ad-e2ed2e3240ec.png → 63e71ec7-6290-4efa-89cf-69c53d805a58.png} +0 -0
- /kotonebot/kaa/sprites/{c15ad104-3814-467c-a211-1e54be35fc66.png → 66f5fab8-274c-4ab9-983d-ca2f8d90ea94.png} +0 -0
- /kotonebot/kaa/sprites/{8031a52b-afe1-4ebd-9599-1d123bc0e48b.png → 6abb63c1-025a-425e-8f33-29f83f504d75.png} +0 -0
- /kotonebot/kaa/sprites/{ad26884a-8371-420f-be89-7fe82ddaf1e8.png → 6d6550a6-f270-474e-b55c-b2017ef87ab2.png} +0 -0
- /kotonebot/kaa/sprites/{bf158b17-4d93-40f1-b9cf-4ae9226bcfc1.png → 7057e165-9ed4-485e-9609-94854ef269dd.png} +0 -0
- /kotonebot/kaa/sprites/{fa64ed03-4277-4ae2-a958-adb93c393457.png → 78c2c39e-6621-41d1-9a1e-7e171b001795.png} +0 -0
- /kotonebot/kaa/sprites/{f32e04e8-2e9a-49c2-9734-d0a02ed2d892.png → 798a9587-3f8c-424e-90af-89a08ed62ced.png} +0 -0
- /kotonebot/kaa/sprites/{b677c2ba-62da-4afd-8ba7-a9b40362b5bb.png → 7bbac9cd-417f-4bd5-975a-a8a25c7bd922.png} +0 -0
- /kotonebot/kaa/sprites/{d1a8f72b-813b-4d8a-a596-5576758d7fe2.png → 7c2f9216-56f2-4eec-8a45-1bd864316dad.png} +0 -0
- /kotonebot/kaa/sprites/{fe7e4f2b-b795-43b6-bf0a-4e65c9133a40.png → 7c992bae-5ded-473b-8e60-4dede8434e5d.png} +0 -0
- /kotonebot/kaa/sprites/{e73cacc9-08aa-4b7a-acd3-3dbe95d9f51c.png → 7e7e8772-b31a-4bbe-919e-90373a4ad5a7.png} +0 -0
- /kotonebot/kaa/sprites/{39151ffc-365e-45b2-a2f4-9d4c76d41524.png → 80171520-d132-4395-b2ed-e6451335b3d5.png} +0 -0
- /kotonebot/kaa/sprites/{04180b4a-1532-42dd-9280-0f47c1e68959.png → 82cf688e-ac85-4425-917d-10b6db9b6cc3.png} +0 -0
- /kotonebot/kaa/sprites/{ebcaaba7-57f8-4bcb-93da-a288a0fcc95e.png → 87292951-43c1-4039-9b92-c59c1b25b5f7.png} +0 -0
- /kotonebot/kaa/sprites/{4dd7db2d-3107-4fc5-8f30-cfa70bc28de1.png → 893a6bba-905e-4123-9e35-f829fd6c8b2a.png} +0 -0
- /kotonebot/kaa/sprites/{f9e0be54-237b-4092-8784-1a4d2eb43097.png → 8a605d31-3160-49d1-9098-45d8ee18e8d6.png} +0 -0
- /kotonebot/kaa/sprites/{334d4915-4600-4c81-bbb0-c52291b7d089.png → 8cb3b6c2-7fae-41cf-8f33-a0e3ea668f25.png} +0 -0
- /kotonebot/kaa/sprites/{6abe81b7-1a04-44e9-b390-affe47799c46.png → 8e1ca316-39e6-4b9f-b497-84ad8848a3e1.png} +0 -0
- /kotonebot/kaa/sprites/{f14cc536-f38c-4568-9812-d3ed95e639d3.png → 8f675963-122a-4932-9f33-07dcdcb3a51a.png} +0 -0
- /kotonebot/kaa/sprites/{0df088e0-737f-44ec-b39f-76239d087382.png → 907dc373-e2f5-4959-84cb-d7971a7fefd8.png} +0 -0
- /kotonebot/kaa/sprites/{7d5b4d44-cb46-49fb-96be-edaab5396836.png → 9359526e-c5db-4d63-adc4-08b7d704b422.png} +0 -0
- /kotonebot/kaa/sprites/{2860421f-2db8-4850-9f0c-0aca8bbf16d0.png → 96178cef-4868-4378-9f9c-c08780def3d6.png} +0 -0
- /kotonebot/kaa/sprites/{a477c873-98bd-45f0-a9a6-324e714c69cd.png → 985ec867-bc7c-47c5-8fcd-75f736f27507.png} +0 -0
- /kotonebot/kaa/sprites/{669f9e7c-b665-47a8-b4f5-fb2748bf88af.png → 9c98c4e6-5335-4c8b-92fa-24ec440b055e.png} +0 -0
- /kotonebot/kaa/sprites/{1373ec63-fd98-4be9-a925-08144f1d5ba5.png → a015cea0-d2c7-439a-b942-d4d2dad276e5.png} +0 -0
- /kotonebot/kaa/sprites/{6fec0efa-336f-4726-b1d9-dc83c23b297d.png → a05abc9d-9f0e-4d83-94bb-4f0b1e061035.png} +0 -0
- /kotonebot/kaa/sprites/{957dbdc4-8367-4a59-9bfc-d35ea11af9b8.png → a2e41d98-dd91-4e51-ba57-4f8ddfd07cfa.png} +0 -0
- /kotonebot/kaa/sprites/{8e78e1f1-ea2f-4779-a8cd-56336f936c81.png → a402b549-a4dd-4ff3-ad51-2aa5f8e620af.png} +0 -0
- /kotonebot/kaa/sprites/{ff493975-0f30-468f-a3ea-17505a2b0385.png → a5efb332-8452-49d8-ade3-882bbd445553.png} +0 -0
- /kotonebot/kaa/sprites/{dfe2fc89-eaf7-4027-bb53-dd17a9741e01.png → a9f47d53-fbef-4cb0-9d2d-845ef3d1f719.png} +0 -0
- /kotonebot/kaa/sprites/{45ae4d17-1887-4f2e-8cce-e5a4cc42e10b.png → b10596fe-22fa-4284-ab4b-1d653d4f0f07.png} +0 -0
- /kotonebot/kaa/sprites/{9cc7997f-3aeb-4b20-ae42-468ebfbc851e.png → b326c50f-c88c-4b99-80a7-046053423a19.png} +0 -0
- /kotonebot/kaa/sprites/{6d706642-441f-4bcc-b377-0fce3484be22.png → b44085c7-9777-415a-aae1-aae47c2f7321.png} +0 -0
- /kotonebot/kaa/sprites/{54d57991-e5d0-4346-b1f8-63e429d8a3fe.png → b629f0a7-df4f-4407-908b-a031c70e8b6f.png} +0 -0
- /kotonebot/kaa/sprites/{63f28640-766f-4698-b556-f0a0cbac6a1a.png → b8165178-a9ce-491a-ba57-d25c7af8533f.png} +0 -0
- /kotonebot/kaa/sprites/{0c3cc689-90f5-4c29-be80-04e2f2ab799a.png → b8eeacac-5679-42b9-a5b9-138598c8c6b6.png} +0 -0
- /kotonebot/kaa/sprites/{f89cafb7-657b-4ab4-8917-1964b242ff2e.png → b9ad5832-889a-4a5f-adc0-5dfd55126d6a.png} +0 -0
- /kotonebot/kaa/sprites/{16576157-f99b-4a64-b41b-3ee712c2304e.png → bcb95d32-32e1-4251-83de-ef5164d22a48.png} +0 -0
- /kotonebot/kaa/sprites/{7e64561b-0dab-420a-8f79-45ed6801939b.png → c0ddf15a-a6f6-42f6-86b7-e17cde60b437.png} +0 -0
- /kotonebot/kaa/sprites/{6d4f414d-ccd8-4a25-aa32-1b77ce0035d6.png → c24e874e-6a3e-472c-8745-edf7cac88954.png} +0 -0
- /kotonebot/kaa/sprites/{2b87ed7c-b01b-4ea7-ab34-ec4e37e036c0.png → c362b54d-d535-4227-8612-9bfe11fd7891.png} +0 -0
- /kotonebot/kaa/sprites/{1e7924dc-9dd4-4d54-add8-b394c75af1c7.png → c51132e9-f45c-40c9-8026-7d70b13dca8b.png} +0 -0
- /kotonebot/kaa/sprites/{5663551f-5abc-4ef8-a3fd-ba57b20cb35c.png → c60bec93-231f-498d-b3c6-0a78c9b8f37f.png} +0 -0
- /kotonebot/kaa/sprites/{89d6407a-982c-4dbc-8dd7-89cf79429eec.png → c64ff0a8-9f96-4ca7-902b-4f7298c1c431.png} +0 -0
- /kotonebot/kaa/sprites/{bda40c0d-55a9-41ad-ba8a-e8aafc52608b.png → c819d999-fef6-47bb-a94f-055d616033c7.png} +0 -0
- /kotonebot/kaa/sprites/{1b8b2a66-fb45-41d7-8441-04425a47bdeb.png → c85e58d3-ec5a-43ea-aa2a-04ee28e9ea21.png} +0 -0
- /kotonebot/kaa/sprites/{b332eb23-6653-4d17-b779-2451a3b261ab.png → c883049b-187c-4685-a63a-cff516851a95.png} +0 -0
- /kotonebot/kaa/sprites/{60f292d1-6c2d-4cfb-af76-568610817722.png → c8f0cc49-5df8-4d7a-9ef6-2f021c2380a5.png} +0 -0
- /kotonebot/kaa/sprites/{7383d7d0-8ee2-4413-b590-51cff541cab3.png → c9a8d73f-f487-45ff-aa05-a61393d27f66.png} +0 -0
- /kotonebot/kaa/sprites/{817c22a3-ecfe-4949-a784-59342e61a880.png → cb4c357f-b01b-4b95-b928-5affdf4ae684.png} +0 -0
- /kotonebot/kaa/sprites/{dff3509c-f0ec-44e2-8e0b-3c6b465d45f3.png → cc0e38b2-c2ae-4fe1-adb0-6ea73a602563.png} +0 -0
- /kotonebot/kaa/sprites/{c7a33e9e-2f1c-4171-b1f7-7d7dcc1a7bd3.png → cdd40676-b49d-42ad-9972-7149aa13d9bb.png} +0 -0
- /kotonebot/kaa/sprites/{5d912f4e-c319-4d45-85e6-de6e684a6278.png → cf76eb98-44ff-406e-913a-503a0233b4cd.png} +0 -0
- /kotonebot/kaa/sprites/{7b195535-7362-492f-8d87-b8eed15c6fc0.png → d21117c2-3679-4bb0-a5b3-5af5d12d8d3e.png} +0 -0
- /kotonebot/kaa/sprites/{7d778f82-f908-4edb-9b12-d8460589ab11.png → d37bc673-9668-4b7e-bc9d-54495129dcc0.png} +0 -0
- /kotonebot/kaa/sprites/{08750a02-0881-443a-9c69-c90a2ccf2a63.png → dac24ef9-e53d-4d95-aa09-fa694606c3ab.png} +0 -0
- /kotonebot/kaa/sprites/{31271771-5149-447c-8fae-ccc308c0a838.png → dd084696-be33-4a00-b6df-b25131b78b40.png} +0 -0
- /kotonebot/kaa/sprites/{a35aed53-ff78-41f0-9a19-c69fda40aeef.png → de1e3c4a-f6ed-467a-889c-25c01a613477.png} +0 -0
- /kotonebot/kaa/sprites/{639465a0-3c63-4307-a72c-4f9e543d2095.png → e1eb4596-73a1-471e-892b-c83cfb706bb3.png} +0 -0
- /kotonebot/kaa/sprites/{91940a9e-2cfc-423a-86d6-49429c051ea0.png → e1ec3ee5-e73c-44b6-b173-ab1c9382cba4.png} +0 -0
- /kotonebot/kaa/sprites/{096d3df5-2c95-41dd-bcc3-2e3afab47420.png → e4d076bd-7824-4496-a498-d6dc01ad90ba.png} +0 -0
- /kotonebot/kaa/sprites/{50a9e2a0-1166-4f1d-ad15-d8493ce0e49e.png → e551148d-1b0d-4518-8711-4161863c93ef.png} +0 -0
- /kotonebot/kaa/sprites/{45671af1-d731-4bb1-83f7-c9d23348308f.png → e61d855e-84d3-4a0e-b996-43fce262c2c3.png} +0 -0
- /kotonebot/kaa/sprites/{608217f6-b880-42ab-9be9-b4d56df87be2.png → e795f9c5-fdda-4fbc-8d38-a92512a590f4.png} +0 -0
- /kotonebot/kaa/sprites/{7d4d708f-c629-4f52-a0af-11aee7fcb878.png → eb446c0a-1dd2-4693-838e-98a76c02eac5.png} +0 -0
- /kotonebot/kaa/sprites/{35e6ad3a-8020-4619-b8ec-6506a8aa51fc.png → eb4bde42-de6e-4c62-8dc4-8885efc834a8.png} +0 -0
- /kotonebot/kaa/sprites/{9a5e7bc9-830f-4b01-8f94-6d4f589754c0.png → ec547e11-4952-4089-9744-1e89bd44389c.png} +0 -0
- /kotonebot/kaa/sprites/{40e3dac1-74fb-4501-a20f-18422d070554.png → ed4a8cff-b3df-4d7a-bfc4-8da776a8e548.png} +0 -0
- /kotonebot/kaa/sprites/{39d57097-6839-4030-bb51-24d6cd880897.png → efc08f1c-e78f-4df8-bff9-b45cde37f9dd.png} +0 -0
- /kotonebot/kaa/sprites/{9f56889b-0251-4ec1-8ccb-5cfd3bddd396.png → f02d997a-a3a8-4e18-96f1-e4176c14a5e0.png} +0 -0
- /kotonebot/kaa/sprites/{97bbc57d-780a-40f4-8fe1-bcaef697b601.png → f5e2e4e1-40be-481d-9293-9d47a5bce7cc.png} +0 -0
- /kotonebot/kaa/sprites/{bee4fbe2-365f-4145-b8f4-87c2c460cd71.png → f62dd2a2-bac6-4dc8-9a19-3e995ab80a3b.png} +0 -0
- /kotonebot/kaa/sprites/{666b1e8c-19fb-49e9-a761-1069f635f450.png → f64cfd95-8c87-47e6-96c3-8b7df2624ae3.png} +0 -0
- /kotonebot/kaa/sprites/{847cb208-a71c-42e3-97f1-18d4aa886b41.png → f8bcbc2c-9831-4e8d-9ffd-e532aa3193c3.png} +0 -0
- /kotonebot/kaa/sprites/{9e5c5ffc-450d-4ddc-a4ae-27c33267b056.png → fb50c7d1-b983-4e36-a998-ea17a1bdb18b.png} +0 -0
- /kotonebot/kaa/sprites/{f1a60528-1f35-451f-92e3-b1d5407e0f4e.png → fb97d804-42e4-45e2-8b22-01d16b5d0b6b.png} +0 -0
- /kotonebot/kaa/sprites/{a3140a53-ffb4-4094-a39c-240e9fe42308.png → fe337541-a6a3-4325-97b4-2bf27f51f39f.png} +0 -0
- /kotonebot/kaa/sprites/{1a0f2a10-c23b-47c7-aa7e-89b606f4d8ae.png → ffd3c6b7-cac2-4d45-ae37-792404ea2b40.png} +0 -0
- {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/entry_points.txt +0 -0
- {ksaa-2025.5.16.1.dist-info → ksaa-2025.5.23.1.dist-info}/top_level.txt +0 -0
kotonebot/__init__.py
CHANGED
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.
|
|
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
|
|
133
|
+
except Exception:
|
|
134
134
|
logger.error(f'Failed to load sub-module: {name}')
|
|
135
|
-
logger.exception(
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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.
|
|
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:
|
kotonebot/backend/color.py
CHANGED
|
@@ -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
|
|
171
|
-
x, y, w, h =
|
|
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
|
|
203
|
-
x, y, w, h =
|
|
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:
|
|
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:
|
|
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
|
|
323
|
-
x, y, w, h =
|
|
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
|
|
374
|
-
intersection = _rect_intersection(contour_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
|
|
437
|
-
x, y, w, h =
|
|
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
|
|
472
|
-
x, y, w, h =
|
|
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
|
|
501
|
-
x, y, w, h =
|
|
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.
|
|
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__(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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) ->
|
|
94
|
+
def rect(self) -> RectTuple:
|
|
113
95
|
return self.x1, self.y1, self.width, self.height
|
|
114
96
|
|
|
115
|
-
class HintPoint(
|
|
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
|
-
|
|
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:
|
kotonebot/backend/dispatch.py
CHANGED
|
@@ -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.
|
|
14
|
+
from kotonebot.primitives import Rect, is_rect
|
|
15
15
|
|
|
16
16
|
from .core import Image
|
|
17
17
|
|
kotonebot/backend/image.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from logging import getLogger
|
|
3
|
-
from typing import NamedTuple, Protocol,
|
|
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,
|
|
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) ->
|
|
27
|
+
def rect(self) -> KbRect:
|
|
28
28
|
"""结果区域。左上角坐标和宽高。"""
|
|
29
29
|
...
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class TemplateMatchResult(NamedTuple):
|
|
33
33
|
score: float
|
|
34
|
-
position:
|
|
34
|
+
position: KbPoint
|
|
35
35
|
"""结果位置。左上角坐标。"""
|
|
36
|
-
size:
|
|
36
|
+
size: KbSize
|
|
37
37
|
"""输入模板的大小。宽高。"""
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
|
-
def rect(self) ->
|
|
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) ->
|
|
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:
|
|
51
|
+
position: KbPoint
|
|
52
52
|
"""结果位置。左上角坐标。"""
|
|
53
|
-
size:
|
|
53
|
+
size: KbSize
|
|
54
54
|
"""命中模板的大小。宽高。"""
|
|
55
55
|
index: int
|
|
56
56
|
"""命中模板在列表中的索引。"""
|
|
57
57
|
|
|
58
58
|
@property
|
|
59
|
-
def rect(self) ->
|
|
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) ->
|
|
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:
|
|
80
|
-
size:
|
|
79
|
+
position: KbPoint
|
|
80
|
+
size: KbSize
|
|
81
81
|
image: MatLike
|
|
82
82
|
|
|
83
83
|
@property
|
|
84
|
-
def rect(self) ->
|
|
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:
|
|
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
|
|
74
|
-
points.append((r.rect
|
|
75
|
-
points.append((r.rect
|
|
76
|
-
points.append((r.rect
|
|
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
|
|
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
|
|
261
|
-
text_y = r.rect
|
|
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
|
|
342
|
-
result_rect[1] + rect
|
|
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
|
|