mirror of
https://github.com/TheFunny/ArisuAutoSweeper
synced 2026-06-09 20:04:52 +00:00
Added Tasks, Shop, MomoTalk (#11)
* feat: tasks Added module tasks for EN * refactor: gui added tree view Farm and Reward. * feat: shop * feat: momotalk --------- Co-authored-by: YoursFunny <admin@yoursfunny.top>
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
from module.base.button import Button, ButtonWrapper
|
||||
|
||||
# This file was auto-generated, do not modify it manually. To generate:
|
||||
# ``` python -m dev_tools.button_extract ```
|
||||
|
||||
BEGIN_STORY = ButtonWrapper(
|
||||
name='BEGIN_STORY',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/BEGIN_STORY.png',
|
||||
area=(796, 540, 1059, 591),
|
||||
search=(776, 520, 1079, 611),
|
||||
color=(107, 197, 230),
|
||||
button=(796, 540, 1059, 591),
|
||||
),
|
||||
)
|
||||
CHAT_AREA = ButtonWrapper(
|
||||
name='CHAT_AREA',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/CHAT_AREA.png',
|
||||
area=(760, 149, 1149, 628),
|
||||
search=(740, 129, 1169, 648),
|
||||
color=(206, 212, 217),
|
||||
button=(760, 149, 1149, 628),
|
||||
),
|
||||
)
|
||||
CONFIRM_SKIP = ButtonWrapper(
|
||||
name='CONFIRM_SKIP',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/CONFIRM_SKIP.png',
|
||||
area=(674, 486, 871, 555),
|
||||
search=(654, 466, 891, 575),
|
||||
color=(112, 207, 241),
|
||||
button=(674, 486, 871, 555),
|
||||
),
|
||||
)
|
||||
CONFIRM_SORT = ButtonWrapper(
|
||||
name='CONFIRM_SORT',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/CONFIRM_SORT.png',
|
||||
area=(239, 407, 657, 439),
|
||||
search=(219, 387, 677, 459),
|
||||
color=(248, 249, 249),
|
||||
button=(239, 407, 657, 439),
|
||||
),
|
||||
)
|
||||
FIRST_UNREAD = ButtonWrapper(
|
||||
name='FIRST_UNREAD',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/FIRST_UNREAD.png',
|
||||
area=(636, 239, 661, 265),
|
||||
search=(616, 219, 681, 285),
|
||||
color=(251, 86, 45),
|
||||
button=(636, 239, 661, 265),
|
||||
),
|
||||
)
|
||||
MENU = ButtonWrapper(
|
||||
name='MENU',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/MENU.png',
|
||||
area=(1156, 15, 1251, 63),
|
||||
search=(1136, 0, 1271, 83),
|
||||
color=(215, 219, 222),
|
||||
button=(1156, 15, 1251, 63),
|
||||
),
|
||||
)
|
||||
MESSAGE_OFF = ButtonWrapper(
|
||||
name='MESSAGE_OFF',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/MESSAGE_OFF.png',
|
||||
area=(143, 273, 203, 298),
|
||||
search=(123, 253, 223, 318),
|
||||
color=(93, 105, 122),
|
||||
button=(143, 273, 203, 298),
|
||||
),
|
||||
)
|
||||
MESSAGE_ON = ButtonWrapper(
|
||||
name='MESSAGE_ON',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/MESSAGE_ON.png',
|
||||
area=(143, 271, 203, 301),
|
||||
search=(123, 251, 223, 321),
|
||||
color=(160, 168, 182),
|
||||
button=(143, 271, 203, 301),
|
||||
),
|
||||
)
|
||||
NOTIFICATION_BADGE = ButtonWrapper(
|
||||
name='NOTIFICATION_BADGE',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/NOTIFICATION_BADGE.png',
|
||||
area=(171, 109, 200, 128),
|
||||
search=(151, 89, 220, 148),
|
||||
color=(242, 101, 47),
|
||||
button=(171, 109, 200, 128),
|
||||
),
|
||||
)
|
||||
REPLY = ButtonWrapper(
|
||||
name='REPLY',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/REPLY.png',
|
||||
area=(791, 431, 855, 462),
|
||||
search=(771, 411, 875, 482),
|
||||
color=(198, 210, 216),
|
||||
button=(791, 431, 855, 462),
|
||||
),
|
||||
)
|
||||
SELECT_STUDENT = ButtonWrapper(
|
||||
name='SELECT_STUDENT',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/SELECT_STUDENT.png',
|
||||
area=(839, 369, 998, 403),
|
||||
search=(819, 349, 1018, 423),
|
||||
color=(241, 242, 242),
|
||||
button=(839, 369, 998, 403),
|
||||
),
|
||||
)
|
||||
SKIP = ButtonWrapper(
|
||||
name='SKIP',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/SKIP.png',
|
||||
area=(1192, 103, 1229, 141),
|
||||
search=(1172, 83, 1249, 161),
|
||||
color=(108, 125, 145),
|
||||
button=(1192, 103, 1229, 141),
|
||||
),
|
||||
)
|
||||
SORT_OFF = ButtonWrapper(
|
||||
name='SORT_OFF',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/SORT_OFF.png',
|
||||
area=(591, 158, 662, 199),
|
||||
search=(571, 138, 682, 219),
|
||||
color=(235, 237, 238),
|
||||
button=(591, 158, 662, 199),
|
||||
),
|
||||
)
|
||||
SORT_ON = ButtonWrapper(
|
||||
name='SORT_ON',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/SORT_ON.png',
|
||||
area=(594, 159, 658, 196),
|
||||
search=(574, 139, 678, 216),
|
||||
color=(233, 235, 236),
|
||||
button=(594, 159, 658, 196),
|
||||
),
|
||||
)
|
||||
STORY = ButtonWrapper(
|
||||
name='STORY',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/STORY.png',
|
||||
area=(790, 529, 979, 557),
|
||||
search=(770, 509, 999, 577),
|
||||
color=(220, 208, 214),
|
||||
button=(790, 529, 979, 557),
|
||||
),
|
||||
)
|
||||
UNREAD = ButtonWrapper(
|
||||
name='UNREAD',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/UNREAD.png',
|
||||
area=(454, 160, 568, 193),
|
||||
search=(434, 140, 588, 213),
|
||||
color=(241, 242, 243),
|
||||
button=(454, 160, 568, 193),
|
||||
),
|
||||
)
|
||||
UNREAD_OFF = ButtonWrapper(
|
||||
name='UNREAD_OFF',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/UNREAD_OFF.png',
|
||||
area=(456, 273, 658, 316),
|
||||
search=(436, 253, 678, 336),
|
||||
color=(252, 252, 251),
|
||||
button=(456, 273, 658, 316),
|
||||
),
|
||||
)
|
||||
UNREAD_ON = ButtonWrapper(
|
||||
name='UNREAD_ON',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/momotalk/UNREAD_ON.png',
|
||||
area=(456, 272, 658, 314),
|
||||
search=(436, 252, 678, 334),
|
||||
color=(245, 120, 144),
|
||||
button=(456, 272, 658, 314),
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
from enum import Enum
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from tasks.tactical_challenge.assets.assets_tactical_challenge import *
|
||||
from tasks.momotalk.ui import MomoTalkUI
|
||||
|
||||
class MomoTalkStatus(Enum):
|
||||
OPEN = 0
|
||||
SORT = 1
|
||||
CHECK = 2
|
||||
CHAT = 3
|
||||
STORY = 4
|
||||
FINISHED = -1
|
||||
|
||||
class MomoTalk(MomoTalkUI):
|
||||
def handle_momotalk(self, status):
|
||||
match status:
|
||||
case MomoTalkStatus.OPEN:
|
||||
if self.open_momotalk():
|
||||
return MomoTalkStatus.SORT
|
||||
return MomoTalkStatus.FINISHED
|
||||
case MomoTalkStatus.SORT:
|
||||
if self.sort_messages():
|
||||
return MomoTalkStatus.CHECK
|
||||
case MomoTalkStatus.CHECK:
|
||||
if self.check_first_student():
|
||||
return MomoTalkStatus.CHAT
|
||||
return MomoTalkStatus.FINISHED
|
||||
case MomoTalkStatus.CHAT:
|
||||
if self.chat():
|
||||
return MomoTalkStatus.STORY
|
||||
return MomoTalkStatus.OPEN
|
||||
case MomoTalkStatus.STORY:
|
||||
if self.skip_story():
|
||||
return MomoTalkStatus.OPEN
|
||||
case MomoTalkStatus.FINISHED:
|
||||
return status
|
||||
case _:
|
||||
logger.warning(f'Invalid status: {status}')
|
||||
return status
|
||||
|
||||
def run(self):
|
||||
action_timer = Timer(0.5, 1)
|
||||
status = MomoTalkStatus.OPEN
|
||||
|
||||
while 1:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.ui_additional():
|
||||
continue
|
||||
|
||||
if action_timer.reached_and_reset():
|
||||
logger.attr('Status', status)
|
||||
status = self.handle_momotalk(status)
|
||||
|
||||
if status == MomoTalkStatus.FINISHED:
|
||||
break
|
||||
|
||||
self.config.task_delay(server_update=True)
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
from module.base.timer import Timer
|
||||
from module.base.base import ModuleBase
|
||||
from module.logger import logger
|
||||
from module.ui.switch import Switch
|
||||
from module.base.utils import point_in_area, area_size
|
||||
from tasks.base.ui import UI
|
||||
from tasks.base.page import page_main, page_momo_talk
|
||||
from tasks.momotalk.assets.assets_momotalk import *
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
"""None of the switches works"""
|
||||
SWITCH_MESSAGE = Switch("Message_switch")
|
||||
SWITCH_MESSAGE.add_state("on", MESSAGE_ON)
|
||||
SWITCH_MESSAGE.add_state("off", MESSAGE_OFF)
|
||||
|
||||
SWITCH_UNREAD = Switch("Unread_switch")
|
||||
SWITCH_UNREAD.add_state("on", UNREAD_ON)
|
||||
SWITCH_UNREAD.add_state("off", UNREAD_OFF)
|
||||
|
||||
SWITCH_SORT = Switch("Sort_switch")
|
||||
SWITCH_SORT.add_state("on", SORT_ON)
|
||||
SWITCH_SORT.add_state("off", SORT_OFF)
|
||||
|
||||
"""Required for template matching as reply and story
|
||||
button can be found in different locations"""
|
||||
REPLY_TEMPLATE = REPLY.matched_button.image
|
||||
STORY_TEMPLATE = STORY.matched_button.image
|
||||
|
||||
class MomoTalkUI(UI):
|
||||
def __init__(self, config, device):
|
||||
super().__init__(config, device)
|
||||
self.swipe_vector_range = (0.65, 0.85)
|
||||
self.list = CHAT_AREA
|
||||
|
||||
def swipe_page(self, direction: str, main: ModuleBase, vector_range=None, reverse=False):
|
||||
"""
|
||||
Args:
|
||||
direction: up, down
|
||||
main:
|
||||
vector_range (tuple[float, float]):
|
||||
reverse (bool):
|
||||
"""
|
||||
if vector_range is None:
|
||||
vector_range = self.swipe_vector_range
|
||||
vector = np.random.uniform(*vector_range)
|
||||
width, height = area_size(self.list.button)
|
||||
if direction == 'up':
|
||||
vector = (0, vector * height)
|
||||
elif direction == 'down':
|
||||
vector = (0, -vector * height)
|
||||
else:
|
||||
logger.warning(f'Unknown swipe direction: {direction}')
|
||||
return
|
||||
|
||||
if reverse:
|
||||
vector = (-vector[0], -vector[1])
|
||||
main.device.swipe_vector(vector, self.list.button)
|
||||
|
||||
def select_then_check(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper, similarity=0.85):
|
||||
timer = Timer(5, 10).start()
|
||||
while 1:
|
||||
self.device.screenshot()
|
||||
self.appear_then_click(dest_enter, interval=1, similarity=similarity)
|
||||
if self.appear(dest_check, similarity=similarity):
|
||||
return True
|
||||
if timer.reached():
|
||||
return False
|
||||
|
||||
def select_then_disappear(self, dest_enter: ButtonWrapper, dest_check: ButtonWrapper, force_select=False):
|
||||
timer = Timer(5, 10).start()
|
||||
while 1:
|
||||
self.device.screenshot()
|
||||
if force_select or self.appear(dest_enter):
|
||||
self.click_with_interval(dest_enter, interval=1)
|
||||
if not self.appear(dest_check):
|
||||
return True
|
||||
if timer.reached():
|
||||
return False
|
||||
|
||||
def set_switch(self, switch):
|
||||
"""
|
||||
Set switch to on. However, unsure why is inaccurate in momotalk.
|
||||
Returns:
|
||||
True if switch is set, False if switch not found
|
||||
"""
|
||||
if not switch.appear(main=self):
|
||||
logger.info(f'{switch.name} not found')
|
||||
return False
|
||||
switch.set('on', main=self)
|
||||
|
||||
return True
|
||||
|
||||
def click_all(self, template, x_add=0, y_add=0):
|
||||
"""
|
||||
Find the all the locations of the template adding an offset if specified and click them.
|
||||
TODO: filter coords that are not inside the chat area as otherwise it will close momotalk.
|
||||
If after filter, no coords then swipe.
|
||||
"""
|
||||
click_coords = self.device.click_methods.get(self.config.Emulator_ControlMethod, self.device.click_adb)
|
||||
image = self.device.screenshot()
|
||||
result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
|
||||
threshold = 0.8
|
||||
locations = np.where(result >= threshold)
|
||||
seen = set()
|
||||
for pt in zip(*locations[::-1]):
|
||||
center_pt = (int(pt[0] + template.shape[1] / 2 + x_add), int(pt[1] + template.shape[0] / 2 + y_add))
|
||||
seen.add(center_pt)
|
||||
if seen:
|
||||
seen = filter(lambda x: point_in_area(x, CHAT_AREA.area), seen)
|
||||
[click_coords(coords[0], coords[1]) for coords in seen]
|
||||
self.swipe_page("down", self)
|
||||
return True
|
||||
return False
|
||||
|
||||
def open_momotalk(self):
|
||||
"""
|
||||
Go to main and check if there are any red notifications in momotalk.
|
||||
If yes, open it otherwise it means no student available for interaction.
|
||||
"""
|
||||
self.ui_ensure(page_main)
|
||||
if self.match_color(NOTIFICATION_BADGE, threshold=80):
|
||||
self.ui_ensure(page_momo_talk)
|
||||
while not self.select_then_check(MESSAGE_OFF, MESSAGE_ON):
|
||||
pass
|
||||
return True
|
||||
logger.warn("No students available for interaction")
|
||||
return False
|
||||
|
||||
def sort_messages(self):
|
||||
"""
|
||||
Switch from newest to unread and sort the messages in descending order
|
||||
"""
|
||||
logger.info("Sorting messages...")
|
||||
steps = [UNREAD, CONFIRM_SORT, UNREAD_OFF, UNREAD_ON]
|
||||
for i in range(len(steps)-2):
|
||||
self.select_then_check(steps[i], steps[i+1], similarity=0.95)
|
||||
return not self.appear(CONFIRM_SORT) and self.appear(UNREAD) and self.appear(SORT_ON)
|
||||
|
||||
def check_first_student(self):
|
||||
"""
|
||||
If the first student has a red notification return True and start chat.
|
||||
Otherwise it means no students are available for interaction.
|
||||
"""
|
||||
if self.match_color(FIRST_UNREAD, threshold=80) and self.select_then_disappear(FIRST_UNREAD, SELECT_STUDENT, force_select=True):
|
||||
return True
|
||||
logger.warn("No students available for interaction")
|
||||
return False
|
||||
|
||||
def chat(self):
|
||||
"""
|
||||
Waits for the chat area to be stable and then
|
||||
check if a reply or story button is found and click them.
|
||||
If the begin story button is found skip story.
|
||||
"""
|
||||
logger.info("Chatting with student...")
|
||||
stability_counter = 0
|
||||
while 1:
|
||||
self.wait_until_stable(CHAT_AREA, timer=Timer(10, 10))
|
||||
if self.appear(BEGIN_STORY):
|
||||
logger.info("Begin Story detected")
|
||||
return True
|
||||
if self.click_all(REPLY_TEMPLATE, y_add=62):
|
||||
logger.info("Clicked on reply")
|
||||
stability_counter = 0
|
||||
continue
|
||||
if self.click_all(STORY_TEMPLATE, y_add=62):
|
||||
logger.info("Clicked on story")
|
||||
stability_counter = 0
|
||||
continue
|
||||
logger.info("No new message detected")
|
||||
stability_counter += 1
|
||||
if stability_counter > 3:
|
||||
return False
|
||||
|
||||
def skip_story(self):
|
||||
"""
|
||||
Skip story by executing a series of steps. Returns True if the confirm skip
|
||||
button is clicked and disappears
|
||||
"""
|
||||
logger.info("Attempting to skip story...")
|
||||
steps = [BEGIN_STORY, MENU, SKIP]
|
||||
for step in steps:
|
||||
self.appear_then_click(step)
|
||||
if self.appear_then_click(CONFIRM_SKIP) and not self.appear(CONFIRM_SKIP, interval=5):
|
||||
logger.info("Skipped story successfully")
|
||||
return True
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user