feat: 新增话费券兑换功能与优享权益领取任务

This commit is contained in:
unknown committed 2026-01-26 14:19:07 +08:00
1 parent ffeccfe85a
commit 418f4cbbd7
3 files changed
+486 -4

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
.claude/
.vscode/
__pycache__/
+17 -2
View File
@@ -33,7 +33,9 @@
│ ├── sign_day_sign ............... 执行签到
│ ├── sign_task_center ............ 任务中心(自动完成任务并领取奖励)
│ ├── sign_do_task_from_list ...... 执行任务中心任务
│ └── sign_get_task_reward ........ 领取任务奖励
│ ├── sign_get_task_reward ........ 领取任务奖励
│ ├── sign_month_reward ........... 月签到连签礼奖励
│ └── sign_coupon_exchange ........ 话费红包兑换话费券
│ │
│ ├── [天天领现金 TTLXJ]
│ ├── ttlxj_task .................. 领现金任务入口
@@ -58,7 +60,8 @@
│ ├── market_watering ............. 执行浇花
│ ├── market_raffle_task .......... 抽奖任务(可选展示奖池信息)
│ ├── market_validate_captcha ..... 人机验证处理
│ └── market_raffle ............... 执行抽奖
│ ├── market_raffle ............... 执行抽奖
│ └── market_privilege_task ....... 优享权益每日领取
│ │
│ ├── [新疆专区 Xinjiang]
│ ├── xj_task ..................... 新疆任务入口(含归属地校验)
@@ -118,6 +121,7 @@
- **奖池信息展示**: 抽奖完成后展示今日奖池信息(显示任意中奖率>0的奖品)
- **人机验证处理**: 抽奖触发人机验证时自动处理并继续抽奖
- **浇花任务**: 支持多次浇花直到完成目标次数
- **优享权益**: 每日自动领取一款优享权益(优先惊喜权益)
- **全局奖池查询**: 多账号只查询一次奖池,共享结果
### 云手机活动
@@ -128,6 +132,8 @@
### 签到区-任务中心
- **任务自动完成**: 自动浏览任务页面并完成任务
- **奖励自动领取**: 任务完成后自动领取奖励
- **月签到奖励**: 自动检查并领取每月连签礼奖励
- **话费券兑换**: 支持使用话费红包兑换话费券(可配置兑换面额)
### 联通安全管家
- **每日签到**: 自动完成安全管家签到任务
@@ -186,6 +192,15 @@ export chinaUnicomCookie="token1@token2@token3"
```python
SHOW_PRIZE_POOL = True # 是否显示权益超市奖品池信息
# 话费券兑换配置 (需要在抢兑时间段内: 10:00-14:00, 18:00-22:00)
EXCHANGE_COUPON_CONFIG = {
"1元话费券": False, # 100积分, 满20元可用
"3元话费券": False, # 300积分, 满30元可用
"5元话费券": True, # 500积分, 满50元可用
"10元话费券": True, # 1000积分, 满100元可用
"18元话费券": False, # 1800积分, 满200元可用
}
```
### 4. 运行脚本
+466 -2
View File
@@ -41,6 +41,35 @@ WOCARE_SIGN_KEY = "f4cd4ffeb5554586acf65ba7110534f5"
WOCARE_CHANNEL_TYPE = "wocareMBHServiceLife1"
WOCARE_VERSION = "1"
# ==================== Coupon Exchange Config (话费券兑换配置) ====================
# True=开启兑换, False=关闭兑换
# 注意: 需要在抢兑时间段内才能兑换 (通常为 10:00-14:00, 18:00-22:00)
EXCHANGE_COUPON_CONFIG = {
"1元话费券": False, # 100积分, 满20元可用
"3元话费券": False, # 300积分, 满30元可用
"5元话费券": True, # 500积分, 满50元可用
"10元话费券": True, # 1000积分, 满100元可用
"18元话费券": False, # 1800积分, 满200元可用
}
# 话费券产品映射表 (名称 -> product_id)
COUPON_PRODUCT_MAP = {
"1元话费券": "25122309441216995",
"3元话费券": "25122309482612026",
"5元话费券": "25122309512816188",
"10元话费券": "25122309543215732",
"18元话费券": "25122310293512803",
}
# 话费券积分需求表 (名称 -> 需要积分数, 积分=话费红包*100)
COUPON_POINTS_REQUIRED = {
"1元话费券": 100, # 需要1元话费红包
"3元话费券": 300, # 需要3元话费红包
"5元话费券": 500, # 需要5元话费红包
"10元话费券": 1000, # 需要10元话费红包
"18元话费券": 1800, # 需要18元话费红包
}
# ==================== Global Market Raffle State ====================
# 全局奖池状态,多账号共享,只查询一次
@@ -149,6 +178,10 @@ market_raffle_state = MarketRaffleState()
# ==================== Utils ====================
# 全局打印锁,防止异步并发时日志输出混乱
_print_lock = asyncio.Lock()
class Logger:
def __init__(self, prefix=""):
self.prefix = prefix
@@ -156,7 +189,13 @@ class Logger:
def log(self, message, notify=False):
timestamp = datetime.now().strftime("%H:%M:%S")
prefix_str = f"[{self.prefix}] " if self.prefix else ""
print(f"[{timestamp}] {prefix_str}{message}")
# 使用 print 的 flush 参数确保立即输出并换行
print(f"[{timestamp}] {prefix_str}{message}", flush=True)
async def log_async(self, message, notify=False):
"""异步日志,使用锁防止并发输出混乱"""
async with _print_lock:
self.log(message, notify)
class HttpClient:
@@ -402,6 +441,7 @@ class CustomUserService:
await self.sign_get_telephone(is_initial=True)
await self.sign_task_center()
await self.sign_get_telephone(is_initial=False)
await self.sign_coupon_exchange()
async def sign_get_telephone(self, is_initial=False):
try:
@@ -906,6 +946,7 @@ class CustomUserService:
await self.market_share_task() # 分享小红书任务,获取额外抽奖机会
await self.market_watering_task()
await self.market_raffle_task()
await self.market_privilege_task() # 优享权益领取
async def market_login(self):
target_url = "https://contact.bol.wo.cn/"
@@ -1118,6 +1159,203 @@ class CustomUserService:
self.logger.log(f"权益超市抽奖异常: {str(e)}")
return False
async def market_privilege_task(self):
"""优享权益: 每日领取一款权益"""
try:
if not self.market_token:
return
# 获取当前日期
now = datetime.now()
current_time = f"{now.year}-{now.month}-{now.day}"
# 获取活动详情
res = await self.http.request(
"POST",
"https://backward.bol.wo.cn/prod-api/promotion/activity/roll/getActivitiesDetail",
headers={
"Authorization": f"Bearer {self.market_token}",
"Content-Type": "application/json",
"Referer": "https://contact.bol.wo.cn/",
},
json={
"majorId": 3,
"subCodeList": ["YOUCHOICEONE"],
"currentTime": current_time,
"withUserStatus": 1,
},
)
result = res["result"]
if not result or result.get("code") != 200:
self.logger.log(f"优享权益: 获取活动详情失败 {result.get('msg', '')}")
return
data_list = result.get("data", [])
if not data_list:
return
activity = data_list[0]
user_available_times = activity.get("userAvailableTimes", 0)
if user_available_times <= 0:
self.logger.log("优享权益: 今日已领取")
return
detail_list = activity.get("detailList", [])
if not detail_list:
return
# 筛选有库存的权益
available_items = [
item for item in detail_list if int(item.get("leftQuantity", 0)) > 0
]
if not available_items:
self.logger.log("优享权益: 所有权益均无库存")
return
# 分类: 惊喜权益 vs 普通权益
surprise_items = [
item for item in available_items if item.get("isSurprise") == 1
]
normal_items = [
item for item in available_items if item.get("isSurprise") != 1
]
# 按sort降序排列 (sort越大可能价值越高)
surprise_items.sort(key=lambda x: int(x.get("sort", 0)), reverse=True)
normal_items.sort(key=lambda x: int(x.get("sort", 0)), reverse=True)
# 从活动中获取 activityId 和 activityCode
activity_id = activity.get("activityId")
activity_code = activity.get("activityCode", "YOUCHOICEONE")
# 优先尝试惊喜权益
for item in surprise_items:
product_name = item.get("productName", "")
product_id = item.get("id")
product_code = item.get("productCode", "")
is_unlock = item.get("isUnlock")
channel_id = item.get("channelId")
account_type = item.get("accountType", "4")
# 惊喜权益需要先解锁
if is_unlock == 0:
unlock_success = await self._unlock_surprise_privilege(
product_id, activity_code
)
if not unlock_success:
self.logger.log(f"优享权益: [{product_name}] 解锁失败")
continue
# 尝试领取
receive_success = await self._receive_privilege(
activity_id,
product_id,
product_code,
channel_id,
account_type,
current_time,
)
if receive_success:
self.logger.log(
f"优享权益: [{product_name}] 领取成功!", notify=True
)
return
else:
self.logger.log(f"优享权益: [{product_name}] 领取失败")
# 如果惊喜权益都失败了,尝试普通权益
for item in normal_items:
product_name = item.get("productName", "")
product_id = item.get("id")
product_code = item.get("productCode", "")
channel_id = item.get("channelId")
account_type = item.get("accountType", "4")
receive_success = await self._receive_privilege(
activity_id,
product_id,
product_code,
channel_id,
account_type,
current_time,
)
if receive_success:
self.logger.log(
f"优享权益: [{product_name}] 领取成功!", notify=True
)
return
else:
self.logger.log(f"优享权益: [{product_name}] 领取失败")
self.logger.log("优享权益: 所有权益领取失败")
except Exception as e:
self.logger.log(f"优享权益异常: {str(e)}")
async def _unlock_surprise_privilege(self, product_id, activity_code):
"""解锁惊喜权益"""
try:
timestamp = int(time.time() * 1000)
res = await self.http.request(
"POST",
"https://backward.bol.wo.cn/prod-api/promotion/activity/roll/unlock/surpriseInterest",
headers={
"Authorization": f"Bearer {self.market_token}",
"Content-Type": "application/json",
"Referer": "https://contact.bol.wo.cn/",
},
json={
"timeVerRan": timestamp,
"mobile": self.mobile,
"id": product_id,
"activityId": activity_code,
},
)
result = res["result"]
return result and result.get("code") == 200
except Exception:
return False
async def _receive_privilege(
self,
activity_id,
product_id,
product_code,
channel_id,
account_type,
current_time,
):
"""领取权益"""
try:
res = await self.http.request(
"POST",
"https://backward.bol.wo.cn/prod-api/promotion/activity/roll/receiveRights",
headers={
"Authorization": f"Bearer {self.market_token}",
"Content-Type": "application/json",
"Referer": "https://contact.bol.wo.cn/",
},
json={
"channelId": channel_id,
"activityId": activity_id,
"productId": product_id,
"productCode": product_code,
"currentTime": current_time,
"accountType": account_type,
},
)
result = res["result"]
return result and result.get("code") == 200
except Exception:
return False
# ==================== Xinjiang (xj) ====================
async def xj_task(self):
if "新疆" not in self.province:
@@ -1452,7 +1690,7 @@ class CustomUserService:
async def sign_task_center(self):
"""签到区-任务中心: 获取任务列表、执行任务、领取奖励"""
try:
for i in range(30): # 最多循环30次,防止死循环
for i in range(20): # 最多循环20次,防止死循环
res = await self.http.request(
"GET",
"https://activity.10010.com/sixPalaceGridTurntableLottery/task/taskList",
@@ -1505,9 +1743,235 @@ class CustomUserService:
break
# 检查月签到奖励
await self.sign_month_reward()
except Exception as e:
self.logger.log(f"签到区-任务中心异常: {str(e)}")
async def sign_month_reward(self):
"""签到区-月签到奖励: 检查并领取每月连签礼奖励"""
try:
res = await self.http.request(
"GET",
"https://activity.10010.com/sixPalaceGridTurntableLottery/floor/getMonthSign",
headers={"Referer": "https://img.client.10010.com/"},
)
result = res["result"]
code = str(result.get("code", "")) if result else ""
if code != "0000":
return
task_list = result.get("data", {}).get("taskList", [])
for task in task_list:
task_status = str(task.get("taskStatus", ""))
# taskStatus: 2=已领取, 1=待领取, 0=不可领取
if task_status == "1":
task_id = task.get("taskId", "")
task_name = task.get("taskName", "")
id_ = task.get("id", "")
if task_id and id_:
reward_res = await self.http.request(
"GET",
"https://activity.10010.com/sixPalaceGridTurntableLottery/task/getTaskReward",
params={
"taskId": task_id,
"taskType": "30",
"id": id_,
},
headers={"Referer": "https://img.client.10010.com/"},
)
reward_result = reward_res["result"]
reward_code = (
str(reward_result.get("code", "")) if reward_result else ""
)
if reward_code == "0000":
data = reward_result.get("data", {})
if str(data.get("code", "")) == "0000":
prize_name = data.get("prizeName", "")
prize_name_red = data.get("prizeNameRed", "")
self.logger.log(
f"签到区-月签奖励: [{task_name}] {prize_name}{prize_name_red}",
notify=True,
)
await asyncio.sleep(1)
except Exception:
pass
async def sign_coupon_exchange(self):
"""签到区-话费券兑换: 根据配置尝试兑换话费券"""
try:
# 检查是否有任何启用的兑换配置
enabled_coupons = [
name for name, enabled in EXCHANGE_COUPON_CONFIG.items() if enabled
]
if not enabled_coupons:
return
# 获取奖品列表
prize_list = await self._get_coupon_prize_list()
if not prize_list:
return
# 获取当前话费红包积分 (积分 = 话费红包 * 100)
current_points = int(self.initial_telephone_amount * 100)
# 遍历启用的话费券配置
for coupon_name in enabled_coupons:
product_id = COUPON_PRODUCT_MAP.get(coupon_name)
if not product_id:
continue
# 在奖品列表中查找对应商品
prize_info = self._find_prize_in_list(prize_list, product_id)
if not prize_info:
continue
# 检查是否已兑换过 (buttonDTO.name = "面额已参与兑换")
button_dto = prize_info.get("buttonDTO")
if button_dto and button_dto.get("name") == "面额已参与兑换":
self.logger.log(f"签到区-话费券: [{coupon_name}] 今日已兑换")
continue
# 检查库存 (无库存直接略过,不显示)
stock_surplus = int(prize_info.get("stockSurplus", 0))
if stock_surplus <= 0:
continue
# 检查积分是否达到兑换门槛
required_points = COUPON_POINTS_REQUIRED.get(coupon_name, 0)
if current_points < required_points:
self.logger.log(
f"签到区-话费券: [{coupon_name}] 积分不足 (需要{required_points}, 当前{current_points})"
)
continue
# 执行兑换 (连续尝试3次)
for attempt in range(3):
await self._do_coupon_exchange(
product_id, prize_info.get("type_code", "21003_01")
)
await asyncio.sleep(0.5)
# 等待并验证兑换结果
await asyncio.sleep(1)
new_prize_list = await self._get_coupon_prize_list()
if new_prize_list:
new_prize_info = self._find_prize_in_list(
new_prize_list, product_id
)
if new_prize_info:
# 检查是否出现 buttonDTO 字段 (表示已兑换成功)
new_button_dto = new_prize_info.get("buttonDTO")
if (
new_button_dto
and new_button_dto.get("name") == "面额已参与兑换"
):
self.logger.log(
f"签到区-话费券: [{coupon_name}] 兑换成功!", notify=True
)
else:
self.logger.log(f"签到区-话费券: [{coupon_name}] 兑换失败")
else:
self.logger.log(f"签到区-话费券: [{coupon_name}] 验证失败")
await asyncio.sleep(1)
except Exception as e:
self.logger.log(f"签到区-话费券兑换异常: {str(e)}")
async def _get_coupon_prize_list(self):
"""获取话费券奖品列表"""
try:
res = await self.http.request(
"POST",
"https://act.10010.com/SigninApp/new_convert/prizeList",
headers={
"Referer": "https://img.client.10010.com/",
"Content-Type": "application/x-www-form-urlencoded",
},
data="",
)
result = res["result"]
status = str(result.get("status", "")) if result else ""
if status != "0000":
return None
# 提取当前有效时段的奖品列表
tab_items = result.get("data", {}).get("datails", {}).get("tabItems", [])
for tab in tab_items:
# 找到当前有效的时段 (defaultShowList=true 且 state="抢兑中")
if tab.get("defaultShowList") and tab.get("state") == "抢兑中":
return tab.get("timeLimitQuanListData", [])
return None
except Exception:
return None
def _find_prize_in_list(self, prize_list, product_id):
"""在奖品列表中查找指定商品"""
for prize in prize_list:
if prize.get("product_id") == product_id:
return prize
return None
async def _do_coupon_exchange(self, product_id, type_code):
"""执行话费券兑换"""
try:
# Step 1: 获取兑换 UUID
uuid_res = await self.http.request(
"POST",
"https://act.10010.com/SigninApp/convert/prizeConvert",
headers={
"Referer": "https://img.client.10010.com/",
"Content-Type": "application/x-www-form-urlencoded",
},
data=f"product_id={product_id}&typeCode={type_code}",
)
uuid_result = uuid_res["result"]
uuid_status = str(uuid_result.get("status", "")) if uuid_result else ""
if uuid_status != "0000":
return False
uuid = uuid_result.get("data", {}).get("uuid", "")
if not uuid:
return False
# Step 2: 执行兑换
exchange_res = await self.http.request(
"POST",
"https://act.10010.com/SigninApp/convert/prizeConvertResult",
headers={
"Referer": "https://img.client.10010.com/",
"Content-Type": "application/x-www-form-urlencoded",
},
data=f"uuid={uuid}",
)
exchange_result = exchange_res["result"]
exchange_status = (
str(exchange_result.get("status", "")) if exchange_result else ""
)
# 注意: 即使返回失败也可能实际成功,需要通过库存验证
return exchange_status == "0000"
except Exception:
return False
async def sign_do_task_from_list(self, task):
"""执行签到区任务"""
try: