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
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import socket
|
|
3
|
-
from
|
|
4
|
-
from
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
54
|
-
emulator_name = self.
|
|
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
|
|
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
|
-
|
|
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, _
|
|
35
|
+
_, activity, *_ = result_text.split(' ')
|
|
36
36
|
package = activity.split('/')[0]
|
|
37
37
|
return package
|
|
38
38
|
|
kotonebot/client/protocol.py
CHANGED
kotonebot/config/base_config.py
CHANGED
|
@@ -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:
|
|
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:
|
|
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 =
|
|
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:
|
|
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)
|
kotonebot/kaa/game_ui/badge.py
CHANGED
|
@@ -4,7 +4,7 @@ badge 模块,用于关联带附加徽章的 UI。
|
|
|
4
4
|
"""
|
|
5
5
|
from typing import Literal, NamedTuple
|
|
6
6
|
|
|
7
|
-
from kotonebot.
|
|
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:
|
|
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:
|
|
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
|
kotonebot/kaa/game_ui/common.py
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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[
|
|
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[
|
|
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[
|
|
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
|
|
7
|
+
from cv2.typing import MatLike
|
|
8
8
|
|
|
9
|
-
from kotonebot import device,
|
|
9
|
+
from kotonebot import device, action
|
|
10
|
+
from kotonebot.primitives import Rect
|
|
10
11
|
from kotonebot.backend.core import HintBox
|
|
11
|
-
from kotonebot.
|
|
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)
|