feat: add recruit sign-in automation

This commit is contained in:
admin committed 2026-10-05 04:18:02 +08:00
1 parent 19c502f659
commit 8b7c5d45da
12 files changed
+288

No files matched your search

+168
View File
@@ -0,0 +1,168 @@
"""商城英雄招募的限时 UP 神灯任务与免费神灯领取。"""
import time
from .engine import WorkflowError
class RecruitRunner:
def __init__(self, desktop, matcher, flow, region_provider, log=print, timeout=12):
self.desktop = desktop
self.matcher = matcher
self.flow = flow
self.region_provider = region_provider
self.log = log
self.timeout = timeout
def wait(self, *names):
end = time.monotonic() + self.timeout
previous, stable = None, 0
while time.monotonic() < end:
shot = self.desktop.screenshot()
region = self.region_provider(shot)
frame = self.matcher.frame(shot, region)
found = next((name for name in names
if self.matcher.matches(frame, self.flow['states'][name])), None)
stable = stable + 1 if found and found == previous else (1 if found else 0)
previous = found
if stable >= 2:
return found, shot, region, frame
time.sleep(.25)
raise WorkflowError('未识别到预期页面 ' + '/'.join(names) + ',已停止。')
def observe(self):
shot = self.desktop.screenshot()
region = self.region_provider(shot)
return shot, region, self.matcher.frame(shot, region)
def click(self, name, observation, hold=.8):
shot, region, _ = observation[-3:]
if self.region_provider(shot) != region:
raise WorkflowError('识别后游戏窗口移动,已停止,请重新运行。')
self.desktop.click(self.flow['clicks'][name], region, shot.size, hold=hold)
self.log('已执行:' + name)
def click_location(self, location, observation):
shot, region, _ = observation[-3:]
x, y, width, height = location
self.desktop.click(((x + width / 2) / 562, (y + height / 2) / 1002),
region, shot.size, hold=.8)
def swipe(self, name, observation):
shot, region, _ = observation[-3:]
start, end = self.flow['swipes'][name]
self.desktop.drag(start, end, region, shot.size)
time.sleep(self.flow.get('swipe_settle_seconds', .5))
def has_dot(self, frame, spec_name, search=None):
spec = dict(self.flow[spec_name])
if search:
spec['search'] = search
score, _ = self.matcher.locate(frame, spec)
return score >= spec.get('threshold', .86)
def task_buttons_with_dot(self, frame):
buttons = self.matcher.locate_all(frame, self.flow['task_button'], limit=8)
for _, location in buttons:
x, y, width, _ = location
# The red dot sits just above the task-button's top-right corner.
dot_search = [x + width - 32, max(0, y - 31), x + width + 8, y + 5]
if self.has_dot(frame, 'card_task_dot', dot_search):
yield location
def dismiss_reward(self):
reward = self.wait('reward')
self.click('dismiss_reward', reward, hold=.2)
self.wait('lamp')
def claim_visible_tasks(self):
claimed = 0
empty_scrolls = 0
while claimed < self.flow['limits']['task_claims']:
lamp = self.wait('lamp')
spec = self.flow['task_claim']
matches = self.matcher.locate_all(lamp[3], spec, limit=8)
if matches:
_, location = matches[0]
self.click_location(location, lamp)
self.log('已领取神灯任务奖励。')
self.dismiss_reward()
claimed += 1
empty_scrolls = 0
continue
if empty_scrolls >= self.flow['limits']['task_scrolls']:
break
self.swipe('task_list_up', lamp)
empty_scrolls += 1
return claimed
def process_lamp(self):
lamp = self.wait('lamp')
if self.has_dot(lamp[3], 'task_tab_dot'):
self.click('task_tab', lamp)
self.wait('lamp')
self.claim_visible_tasks()
lamp = self.wait('lamp')
if self.has_dot(lamp[3], 'gift_tab_dot'):
self.click('gift_tab', lamp)
gift = self.wait('gift')
score, location = self.matcher.locate(gift[3], self.flow['free_button'])
if score >= self.flow['free_button'].get('threshold', .86):
self.click_location(location, gift)
self.log('已领取免费神灯。')
self.dismiss_reward()
else:
self.log('神灯礼包没有可领取的免费神灯,跳过。')
lamp = self.wait('lamp')
self.click('close_lamp', lamp, hold=.2)
time.sleep(.5)
def return_home(self):
observation = self.observe()
self.click('shop_back', observation, hold=.2)
self.wait('home')
self.log('已确认返回首页。')
def run(self, dry_run=False):
home = self.wait('home')
if dry_run:
self.log('首页识别通过。计划:商城 → 英雄招募限时 UP → 神灯任务/礼包 → 返回首页。未点击。')
return 'preview'
self.click('shop', home, hold=.8)
shop = self.wait('shop')
for _ in range(self.flow['limits']['scroll_to_recruit']):
self.swipe('shop_down', shop)
shop = self.observe()
if self.matcher.locate_all(shop[2], self.flow['task_button'], limit=1):
break
handled, blank_pages = 0, 0
while handled < self.flow['limits']['cards']:
_, _, frame = self.observe()
task_button = next(self.task_buttons_with_dot(frame), None)
if task_button:
# Click the button itself, rather than its red dot.
x, y, width, height = task_button
shot, region, current = self.observe()
self.desktop.click(((x + width / 2) / 562, (y + height / 2) / 1002),
region, shot.size, hold=.8)
self.log('已打开限时 UP 卡片的神灯召唤。')
self.process_lamp()
handled += 1
blank_pages = 0
continue
if blank_pages >= self.flow['limits']['card_pages']:
break
# The card stack precedes 图腾寻宝. Pulling the list down shows
# earlier UP cards without ever touching the treasure buttons.
self.swipe('shop_down', self.observe())
blank_pages += 1
self.return_home()
if handled:
self.log('已处理 %s 张限时 UP 卡片。' % handled)
return 'claimed'
self.log('没有带红点的限时 UP 任务卡片,未领取奖励。')
return 'no_card_dot'