Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

567 lines
26 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Payment specialized task definitions.
覆盖支付、绑卡、转账、充值、购票支付、退款等付款链路。
"""
# -- Task Index (auto-generated, do not edit) --
# 7 tasks | L3×2 L4×5
#
# [L4] AlipayContinuousPaymentsToContactsRecordBalances 我的密码是123456。帮我用支付宝连续给{contact1}、{contact2}、{contact3}、{contact4}、{contact5}转账,金额依次是{amount1}、{amount2}、{amount3}、{amount4}、{amount5},备注都写“发工资”,并把每次转账后的余额记到标题为{noteTitle}的备忘录里
# [L4] AlipayBindMultipleCardsTransferAndRecordSuccessfulCards 我的支付宝密码是123456。帮我在支付宝绑定工商银行储蓄卡和农业银行储蓄卡,再按银行卡绑定顺序用三张银行卡分别向{targetAccount}转账{amount1}、{amount2}、{amount3},并把转账成功的银行卡记到标题为{noteTitle}的备忘录里
# [L4] AlipayChangePaymentPasswordThenPay 我现在的密码是000000。帮我把支付宝支付密码改成{newPassword},然后马上转账{amount}元给{contact}
# [L3] TransferToContactWithNote 我的密码是123456。请你用支付宝给{name}转{amount}元,备注写“{note}”
# [L4] SubscribeMembershipAutoRenewThenCancelInWechat 帮我使用微信支付开通哔哩哔哩大会员连月自动续费,然后到微信把这项自动续费关闭
# [L3] AlipayTransferAndNotify 我的支付宝密码是123456。请你用支付宝给{alipay_contact}转{amount}元,备注写“{note}”,然后在微信里把这笔转账告知{contact}
# [L4] WechatExtractAmountTransfer 看看微信里{contact}最近发来的消息,对方让你转多少钱,你就用支付宝转给{alipay_contact}多少,然后回复{reply}
# -- End Task Index --
from __future__ import annotations
import random
from typing import Any
from bench_env.task.base import BaseTask
from bench_env.task.alipay.app import Alipay, TxMatch as _TxMatch
from bench_env.task.bilibili.app import Bilibili
from bench_env.task.judge import JudgeInput
from bench_env.task.notes.app import Notes
from bench_env.task.utils import extract_numbers, now_ms
from bench_env.task.wechat.app import Wechat
def _quantize_money2(params: dict[str, Any], keys: tuple[str, ...]) -> None:
"""将转账类金额统一为最多两位小数(用户入参与采样结果均适用)。"""
for k in keys:
if k in params:
params[k] = round(float(params[k]), 2)
def _distinct_alipay_contact_names(env_state: dict[str, Any]) -> list[str]:
contacts = (env_state.get("apps") or {}).get("alipay", {}).get("contacts") or []
out: list[str] = []
seen: set[str] = set()
for c in contacts:
if isinstance(c, dict) and c.get("name"):
n = str(c["name"]).strip()
if n and n not in seen:
seen.add(n)
out.append(n)
return out
def _reassign_five_distinct_contacts(
params: dict[str, Any],
user_param_keys: set[str],
pool: list[str],
seed: int | None,
) -> None:
"""保证 contact1..5 互不相同;优先保留用户显式传入且不重复的联系人。"""
keys = [f"contact{i}" for i in range(1, 6)]
if len(pool) < 5:
return
rng = random.Random(int(seed) if seed is not None else 0)
slots: list[str | None] = [None] * 5
used: set[str] = set()
for i, k in enumerate(keys):
if k not in user_param_keys:
continue
n = str(params.get(k, "")).strip()
if n and n not in used:
slots[i] = n
used.add(n)
avail = [n for n in pool if n not in used]
rng.shuffle(avail)
ai = 0
for i in range(5):
if slots[i] is not None:
continue
while ai < len(avail):
n = avail[ai]
ai += 1
if n not in used:
slots[i] = n
used.add(n)
break
for i in range(5):
if slots[i] is None:
for n in pool:
if n not in used:
slots[i] = n
used.add(n)
break
for i, k in enumerate(keys):
if slots[i] is not None:
params[k] = slots[i]
class AlipayContinuousPaymentsToContactsRecordBalances(BaseTask):
"""判定:5 笔转账都命中,最终余额正确,且目标备忘录按顺序记录每次转账后的余额。
五个收款联系人必须互不相同;若参数采样出现重复,会在 post_sample 阶段从支付宝通讯录内重选补齐。
备忘录标题由模板固定提供,judge 可据此稳定定位唯一产物,不依赖具体写法。
"""
templates = [
"我的密码是123456。帮我用支付宝连续给{contact1}{contact2}{contact3}{contact4}{contact5}转账,金额依次是{amount1}{amount2}{amount3}{amount4}{amount5},备注都写“发工资”,并把每次转账后的余额记到标题为{noteTitle}的备忘录里",
'My password is 123456. Use Alipay to transfer money to {contact1}, {contact2}, {contact3}, {contact4}, and {contact5} in order, with amounts {amount1}, {amount2}, {amount3}, {amount4}, and {amount5} respectively. Write "发工资" as the note for each transfer, and record the balance after each transfer in a note titled {noteTitle}',
]
scope = "S2"
objective = "operate"
composition = "transfer"
difficulty = "L4"
capabilities = ["finance", "handoff", "create", "reasoning"]
apps = ["alipay", "notes"]
expected_changes = [
"apps.alipay.transferRecords",
"apps.alipay.balance",
"apps.alipay.lastPaymentHint",
"apps.alipay.transferDraft",
"apps.alipay.transferReceipt",
"apps.notes.notes",
]
parameters = {
"amount1": {"type": "float", "default": 88.0, "min": 1, "max": 9999, "round": 2, "description": "第1笔金额"},
"amount2": {"type": "float", "default": 120.0, "min": 1, "max": 9999, "round": 2, "description": "第2笔金额"},
"amount3": {"type": "float", "default": 96.0, "min": 1, "max": 9999, "round": 2, "description": "第3笔金额"},
"amount4": {"type": "float", "default": 156.0, "min": 1, "max": 9999, "round": 2, "description": "第4笔金额"},
"amount5": {"type": "float", "default": 110.0, "min": 1, "max": 9999, "round": 2, "description": "第5笔金额"},
"contact1": {"type": "string", "source": "apps.alipay.contacts[name]", "default": "锐(郭锐)", "description": "第1个联系人显示名"},
"contact2": {"type": "string", "source": "apps.alipay.contacts[name]", "default": "于奶奶(于桂兰)", "description": "第2个联系人显示名"},
"contact3": {"type": "string", "source": "apps.alipay.contacts[name]", "default": "浩杰(李浩杰)", "description": "第3个联系人显示名"},
"contact4": {"type": "string", "source": "apps.alipay.contacts[name]", "default": "老王(王建国)", "description": "第4个联系人显示名"},
"contact5": {"type": "string", "source": "apps.alipay.contacts[name]", "default": "阿明(张明)", "description": "第5个联系人显示名"},
"noteTitle": {"type": "string", "default": "工资支付记录", "description": "备忘录标题"},
}
async def _post_sample(self, env: Any) -> None:
_quantize_money2(
self.params,
("amount1", "amount2", "amount3", "amount4", "amount5"),
)
contact_keys = tuple(f"contact{i}" for i in range(1, 6))
if len({str(self.params[k]) for k in contact_keys}) < 5:
state = await env.get_state()
_reassign_five_distinct_contacts(
self.params,
self._user_params,
_distinct_alipay_contact_names(state),
self._seed,
)
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
ali_init = Alipay(input.apps_init["alipay"])
ali = Alipay(input.apps["alipay"])
notes = Notes(input.apps["notes"])
amounts = [
round(float(self.p.amount1), 2),
round(float(self.p.amount2), 2),
round(float(self.p.amount3), 2),
round(float(self.p.amount4), 2),
round(float(self.p.amount5), 2),
]
start_balance = float(ali_init.total_balance)
expected_balance = max(0.0, start_balance - float(sum(amounts)))
since_ms = now_ms(input.os_init)
contacts = [
str(self.p.contact1),
str(self.p.contact2),
str(self.p.contact3),
str(self.p.contact4),
str(self.p.contact5),
]
matches = [
_TxMatch(counterparty=contact, amount=amount, note="发工资")
for contact, amount in zip(contacts, amounts, strict=True)
]
expected_balances_seq = []
running = start_balance
for amt in amounts:
running = max(0.0, running - float(amt))
expected_balances_seq.append(running)
return [
ali.check_matching_transfers(matches, since_ms=since_ms),
ali.check_total_balance(expected_balance),
notes.check_note_title_exists(str(self.p.noteTitle)),
notes.check_note_with_title_has_number_sequence(
str(self.p.noteTitle),
expected_balances_seq,
field="notes.balances.sequence",
),
]
class AlipayBindMultipleCardsTransferAndRecordSuccessfulCards(BaseTask):
"""判定:两张新卡已绑定,向目标账户发起了 3 次银行卡转账尝试,其中成功的卡被记入指定备忘录。
初始额度:建行/农行足够覆盖采样金额(≤9999);工行额度低于最小第二笔金额,第二笔按「绑定顺序」用工行时会余额不足,
从而形成「第 1、3 笔成功、第 2 笔失败」等符合 max_count=2 的 Human 可完成路径。judge 仍只要求成功笔数 1~2 笔且三卡均有尝试。
"""
templates = [
"我的支付宝密码是123456。帮我在支付宝绑定工商银行储蓄卡和农业银行储蓄卡,再按银行卡绑定顺序用三张银行卡分别向{targetAccount}转账{amount1}{amount2}{amount3},并把转账成功的银行卡记到标题为{noteTitle}的备忘录里",
"My Alipay password is 123456. Bind the ICBC debit card and ABC debit card in Alipay, then use the three bank cards in binding order to transfer {amount1}, {amount2}, and {amount3} to {targetAccount}, and record the successfully transferred bank cards in a note titled {noteTitle}",
]
scope = "S2"
objective = "operate"
composition = "transfer"
difficulty = "L4"
capabilities = ["finance", "handoff", "create"]
apps = ["alipay", "notes"]
expected_changes = [
"apps.alipay.transferRecords",
"apps.alipay.balance",
"apps.alipay.bankCards",
"apps.alipay.lastPaymentHint",
"apps.alipay.transferDraft",
"apps.alipay.transferReceipt",
"apps.notes.notes",
# 绑卡短信验证会写入系统短信与通知(Human Mode 常见)
"os.notifications",
"os.providers.sms",
]
parameters = {
"targetAccount": {"type": "string", "default": "13856785678", "description": "收款账户"},
"amount1": {"type": "int", "default": 3500, "min": 1000, "max": 9999, "description": "第1笔转账金额(建行,额度充足)"},
"amount2": {"type": "int", "default": 4500, "min": 1000, "max": 9999, "description": "第2笔转账金额(工行额度低于此范围最小值时会失败)"},
"amount3": {"type": "int", "default": 6500, "min": 1000, "max": 9999, "description": "第3笔转账金额(农行,额度充足)"},
"noteTitle": {"type": "string", "default": "银行卡转账结果", "description": "备忘录标题"},
}
async def _prepare(self, env: Any) -> None:
await env.set_state(
{
"apps": {
"alipay": {
"balance": {"total": 200.0},
"bankCards": [
{
"id": "ccb",
"bankCode": "ccb",
"bankName": "建设银行储蓄卡",
"cardNumber": "6227000000000005445",
"last4": "5445",
"bound": True,
"available": 20000,
},
{
"id": "icbc",
"bankCode": "icbc",
"bankName": "工商银行储蓄卡",
"cardNumber": "6222000000000001234",
"last4": "1234",
"bound": False,
"available": 500,
},
{
"id": "abc",
"bankCode": "abc",
"bankName": "农业银行储蓄卡",
"cardNumber": "6228480000000006789",
"last4": "6789",
"bound": False,
"available": 20000,
},
],
"transferRecords": [],
"lastPaymentHint": "",
}
}
},
deep=True,
reload=False,
)
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
ali = Alipay(input.apps["alipay"], init=input.apps_init["alipay"])
notes = Notes(input.apps["notes"])
amounts = [round(float(self.p.amount1), 2), round(float(self.p.amount2), 2), round(float(self.p.amount3), 2)]
since_ms = now_ms(input.os_init)
target_account = str(self.p.targetAccount)
return [
ali.check_successful_transfer_count_to_target(
target_account,
since_ms=since_ms,
min_count=1,
max_count=2,
),
ali.check_successful_transfer_amounts_to_target(
target_account,
amounts,
since_ms=since_ms,
),
ali.check_bound_card_count_at_least(3),
ali.check_transfer_attempted_card_count_to_target(
target_account,
since_ms=since_ms,
expected_count=3,
),
notes.check_note_with_title_mentions_groups(
str(self.p.noteTitle),
ali.successful_transfer_card_marker_groups_to_target(
target_account,
since_ms=since_ms,
),
field="notes.success.bank_cards.recorded",
),
notes.check_note_title_exists(str(self.p.noteTitle)),
]
class AlipayChangePaymentPasswordThenPay(BaseTask):
"""判定:支付密码已改为新值,且本次任务后新增了一笔对应金额的扣款记录。
初始密码固定注入为旧值,避免“本来就是目标密码”导致空操作误过。
"""
templates = [
"我现在的密码是000000。帮我把支付宝支付密码改成{newPassword},然后马上转账{amount}元给{contact}",
"My current password is 000000. Change the Alipay payment password to {newPassword}, then immediately transfer {amount} yuan to {contact}",
]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L4"
capabilities = ["finance", "settings"]
apps = ["alipay"]
expected_changes = [
"apps.alipay.userInfo",
"apps.alipay.transferRecords",
"apps.alipay.balance",
"apps.alipay.lastPaymentHint",
"apps.alipay.transferDraft",
"apps.alipay.transferReceipt",
]
parameters = {
"newPassword": {"type": "string", "default": "123456", "pattern": r"\d{6}", "description": "新支付密码(6位数字)"},
"contact": {
"type": "string",
"source": "apps.alipay.contacts[name]",
"default": "浩杰(李浩杰)",
"description": "收款方(须为通讯录已有联系人)",
},
"amount": {"type": "float", "default": 19.9, "min": 1, "max": 9999, "round": 2, "description": "支付金额"},
}
async def _prepare(self, env: Any) -> None:
await env.set_state(
{"apps": {"alipay": {"userInfo": {"paymentPassword": "000000"}}}},
deep=True,
reload=False,
)
async def _post_sample(self, env: Any) -> None:
_quantize_money2(self.params, ("amount",))
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
ali = Alipay(input.apps["alipay"], init=input.apps_init["alipay"])
return [
ali.check_password_changed(str(self.p.newPassword), "000000"),
ali.check_new_negative_transaction_amount(round(float(self.p.amount), 2)),
]
class TransferToContactWithNote(BaseTask):
"""判定:支付宝里新增了一笔联系人、金额、备注都匹配的转账记录。"""
templates = [
"我的密码是123456。请你用支付宝给{name}{amount}元,备注写“{note}”",
"我的密码是123456。帮我在支付宝向{name}转账{amount}元,并添加备注“{note}”",
'My password is 123456. Use Alipay to transfer {amount} yuan to {name} with the note "{note}"',
'My password is 123456. Send {amount} yuan to {name} in Alipay and add the note "{note}"',
]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
capabilities = ["finance", "handoff"]
apps = ["alipay"]
expected_changes = [
"apps.alipay.transferRecords",
"apps.alipay.balance",
"apps.alipay.transferDraft",
"apps.alipay.transferReceipt",
"apps.alipay.lastPaymentHint",
]
parameters = {
"name": {
"type": "string",
"source": "apps.alipay.contacts[name]",
"default": "浩杰(李浩杰)",
"description": "联系人显示名(从通讯录采样)",
},
"amount": {
"type": "float",
"default": 150.0,
"round": 2,
"description": "转账金额",
},
"note": {
"type": "string",
"default": "书本费",
"description": "短备注文本",
},
}
async def _post_sample(self, env: Any) -> None:
_quantize_money2(self.params, ("amount",))
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
ali = Alipay(input.apps["alipay"], init=input.apps_init["alipay"])
return [
ali.check_new_transfer_to_counterparty(
str(self.p.name),
amount=round(float(self.p.amount), 2),
note=str(self.p.note),
field="alipay.transfer.with_note",
)
]
class SubscribeMembershipAutoRenewThenCancelInWechat(BaseTask):
"""判定:B 站会员已开通,且微信里新增的该项自动续费记录最终处于关闭状态。"""
templates = [
"帮我使用微信支付开通哔哩哔哩大会员连月自动续费,然后到微信把这项自动续费关闭",
"Subscribe to Bilibili Premium monthly auto-renewal using WeChat Pay, then go to WeChat and cancel this auto-renewal",
]
scope = "S2"
objective = "operate"
composition = "transfer"
difficulty = "L4"
capabilities = ["finance", "settings", "handoff"]
apps = ["bilibili", "wechat"]
expected_changes = ["apps.wechat.subscriptions", "apps.bilibili.user"]
parameters = {
"membershipType": {"type": "string", "default": "哔哩哔哩大会员", "description": "会员类型"},
"price": {"type": "float", "default": 15.0, "min": 1, "max": 9999, "description": "订阅价格"},
"billingCycle": {"type": "string", "default": "月", "description": "计费周期"},
}
async def _prepare(self, env: Any) -> None:
await env.set_state({"apps": {"wechat": {"subscriptions": []}}}, deep=True, reload=False)
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
bili = Bilibili(input.apps["bilibili"])
wechat = Wechat(input.apps["wechat"], init=input.apps_init["wechat"])
membership_name = str(self.p.membershipType)
return [
bili.check_vip_opened(),
wechat.check_new_subscription_created(membership_name),
wechat.check_new_subscription_auto_renew(
membership_name,
expected=False,
field="wechat.subscription.cancelled",
),
]
class AlipayTransferAndNotify(BaseTask):
"""判定:支付宝确有目标转账,且微信新消息明确提到已转账并包含对应金额。"""
templates = [
"我的支付宝密码是123456。请你用支付宝给{alipay_contact}{amount}元,备注写“{note}”,然后在微信里把这笔转账告知{contact}",
'My Alipay password is 123456. Use Alipay to transfer {amount} yuan to {alipay_contact} with the note "{note}", then notify {contact} about this transfer on WeChat',
]
apps = ["alipay", "wechat"]
scope = "S2"
objective = "operate"
composition = "sequential"
difficulty = "L3"
capabilities = ["finance", "handoff"]
expected_changes = [
"apps.alipay.transferRecords",
"apps.alipay.balance",
"apps.alipay.lastPaymentHint",
"apps.alipay.transferDraft",
"apps.alipay.transferReceipt",
"apps.wechat.chats",
]
parameters = {
"alipay_contact": {"type": "string", "default": "浩杰(李浩杰)", "description": "支付宝联系人"},
"contact": {"type": "string", "default": "张伟", "description": "微信联系人"},
"amount": {"type": "float", "default": 66.0, "min": 1, "max": 9999, "round": 2, "description": "转账金额"},
"note": {"type": "string", "default": "午饭AA", "description": "转账备注"},
}
async def _post_sample(self, env: Any) -> None:
_quantize_money2(self.params, ("amount",))
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
ali = Alipay(input.apps["alipay"], init=input.apps_init["alipay"])
wechat = Wechat(input.apps["wechat"], init=input.apps_init["wechat"])
amount = round(float(self.p.amount), 2)
return [
ali.check_new_transfer_to_counterparty(
str(self.p.alipay_contact),
amount=amount,
note=str(self.p.note),
),
wechat.check_new_transfer_notification(str(self.p.contact), amount),
]
class WechatExtractAmountTransfer(BaseTask):
"""判定:从对方来信中提取「要你转的金额」,再核对支付宝新转账金额一致;若对方后来又发了不含金额的回复,仍向上追溯含金额的那条。
注入:_post_sample 预置一条含金额的请求消息。
"""
templates = [
"看看微信里{contact}最近发来的消息,对方让你转多少钱,你就用支付宝转给{alipay_contact}多少,然后回复{reply}",
"Check the latest message from {contact} on WeChat, figure out how much they want you to transfer, then transfer that amount to {alipay_contact} on Alipay and reply {reply}",
]
apps = ["wechat", "alipay"]
scope = "S2"
objective = "operate"
composition = "deep_dive"
difficulty = "L4"
capabilities = ["extract", "reasoning", "finance"]
expected_changes = [
"apps.alipay.transferRecords",
"apps.alipay.balance",
"apps.alipay.lastPaymentHint",
"apps.alipay.transferDraft",
"apps.alipay.transferReceipt",
"apps.wechat.chats",
]
parameters = {
"contact": {"type": "string", "default": "张伟", "description": "微信联系人"},
"alipay_contact": {"type": "string", "default": "浩杰(李浩杰)", "description": "支付宝联系人"},
"requestAmount": {"type": "float", "default": 66.0, "min": 1, "max": 9999, "round": 2, "description": "微信消息中的目标转账金额(用于任务初始化)"},
"reply": {"type": "string", "default": "已经转了", "description": "回复内容"},
}
async def _post_sample(self, env: Any) -> None:
_quantize_money2(self.params, ("requestAmount",))
state = await env.get_state()
amount = float(self.p.requestAmount)
ts = now_ms(state.get("os") or {})
next_wechat = Wechat(state["apps"]["wechat"]).prepare_state_with_incoming_text(
str(self.p.contact),
f"麻烦你转账给我一个朋友{amount:g}元,转后跟我说一声,我改日还你。",
message_id=f"task_req_{ts}",
timestamp=ts,
)
await env.set_state({"apps": {"wechat": next_wechat}}, deep=True, reload=False)
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wechat = Wechat(input.apps["wechat"], init=input.apps_init["wechat"])
ali = Alipay(input.apps["alipay"], init=input.apps_init["alipay"])
try:
incoming = wechat.last_received_text_with_amount_from(str(self.p.contact))
except ValueError as exc:
raise RuntimeError(f"任务设计错误:{exc}") from exc
nums = extract_numbers(incoming)
expected_amount = round(float(nums[-1]), 2)
return [
ali.check_new_transfer_to_counterparty(
str(self.p.alipay_contact),
amount=expected_amount,
field="alipay.transfer.matches_request",
),
wechat.check_new_sent_to(
str(self.p.contact),
str(self.p.reply),
field="wechat.reply.sent",
),
]