1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/Bg6shY若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/Bg6shY
若失效,可用地址
https://www.aliyun.com/daily-act/ecs/activity_selection?source=5176.29345612&userCode=49hts92d
腾讯云:
https://curl.qcloud.com/wJpWmSfU
若失效,可用地址
https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console
华为云
https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=201905
2.部署教程
3.代码如下
import asyncio
import time
from datetime import datetime
import os
import re
from typing import *
import httpx
# ==================== Bark 推送配置 ====================
# Bark 推送地址(环境变量读取,不变)
BARK_PUSH = os.getenv("BARK_PUSH")
# 你可以在这里写死参数,也可以留空
CUSTOM_BARK_ICON = "Travel.png" # 自定义图标
CUSTOM_BARK_GROUP = "同程旅行" # 自定义分组
PUSH_SWITCH = "0" #推送开关,1开启,0关闭
# 定义全局变量,保证不会报未定义错误
BARK_ICON = CUSTOM_BARK_ICON or os.getenv("BARK_ICON", "")
BARK_GROUP = CUSTOM_BARK_GROUP or os.getenv("BARK_GROUP", "")
# 覆盖环境变量,让 notify.py 能读到
os.environ["BARK_ICON"] = BARK_ICON
os.environ["BARK_GROUP"] = BARK_GROUP
os.environ["PUSH_SWITCH"] = PUSH_SWITCH
# =====================================================
all_print_list = []
push_summary_list = [] # 存储精简的推送内容
def fn_print(*args, sep=' ', end='\n', **kwargs):
global all_print_list
output = ""
# 构建输出字符串
for index, arg in enumerate(args):
if index == len(args) - 1:
output += str(arg)
continue
output += str(arg) + sep
output = output + end
all_print_list.append(output)
# 调用内置的 print 函数打印字符串
print(*args, sep=sep, end=end, **kwargs)
def get_env(env_var, separator):
if env_var in os.environ:
return re.split(separator, os.environ.get(env_var))
else:
try:
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
if env_var in os.environ:
return re.split(separator, os.environ.get(env_var))
else:
fn_print(f"未找到{env_var}变量.")
return []
except ImportError:
fn_print(f"未找到{env_var}变量且无法加载dotenv.")
return []
try:
from notify import send as notify_send
except ImportError:
fn_print("无法导入青龙面板的notify模块,将使用简单的打印通知")
def notify_send(title, content):
fn_print(f"【{title}】\n{content}")
tc_cookies = get_env("tc_cookie", "@")
push_switch = os.environ.get('PUSH_SWITCH', '1')
bark_key = os.environ.get('BARK_KEY', '')
bark_icon = os.environ.get('BARK_ICON', '')
bark_group = os.environ.get('BARK_GROUP', '')
class Tclx:
def __init__(self, cookie):
self.client = httpx.AsyncClient(base_url="https://app.17u.cn/welfarecenter",
verify=False,
timeout=60)
self.phone = cookie.split("#")[0]
self.apptoken = cookie.split("#")[1]
self.device = cookie.split("#")[2]
self.headers = {
'accept': 'application/json, text/plain, */*',
'phone': self.phone,
'channel': '1',
'apptoken': self.apptoken,
'sec-fetch-site': 'same-site',
'accept-language': 'zh-CN,zh-Hans;q=0.9',
'accept-encoding': 'gzip, deflate, br',
'sec-fetch-mode': 'cors',
'origin': 'https://m.17u.cn',
'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 TcTravel/11.0.0 tctype/wk',
'referer': 'https://m.17u.cn/',
# 'content-length': str(len(payload_str.encode('utf-8'))),
'device': self.device,
'sec-fetch-dest': 'empty'
}
self.summary_info = {} # 存储精简的推送信息
@staticmethod
async def get_today_date():
return datetime.now().strftime('%Y-%m-%d')
async def sign_in(self):
try:
response = await self.client.post(
url="/index/signIndex",
headers=self.headers,
json={}
)
data = response.json()
if data['code'] != 2200:
fn_print(f"用户【{self.phone}】 - token失效了,请更新")
self.summary_info['status'] = "token失效"
return None
else:
today_sign = data['data']['todaySign']
mileage = data['data']['mileageBalance']['mileage']
fn_print(f"用户【{self.phone}】 - 今日{'已' if today_sign else '未'}签到,当前剩余里程{mileage}!")
return today_sign
except Exception as e:
fn_print(f"用户【{self.phone}】 - 签到请求异常!{e}")
fn_print(response.text)
self.summary_info['status'] = "签到异常"
return None
async def do_sign_in(self):
today_date = await self.get_today_date()
try:
response = await self.client.post(
url="/index/sign",
headers=self.headers,
json={"type": 1, "day": today_date}
)
data = response.json()
if data['code'] != 2200:
fn_print(f"用户【{self.phone}】 - 签到失败了,尝试获取任务列表")
self.summary_info['status'] = "签到失败"
return False
else:
fn_print(f"用户【{self.phone}】 - 签到成功!开始获取任务列表")
self.summary_info['status'] = "签到成功"
return True
except Exception as e:
fn_print(f"用户【{self.phone}】 - 执行签到请求异常!{e}")
fn_print(response.text)
self.summary_info['status'] = "签到异常"
return False
async def get_task_list(self):
try:
response = await self.client.post(
url="/task/taskList?version=11.0.7",
headers=self.headers,
json={}
)
data = response.json()
if data['code'] != 2200:
fn_print(f"用户【{self.phone}】 - 获取任务列表失败了")
return None
else:
tasks = []
for task in data['data']:
if task['state'] == 1 and task['browserTime'] != 0:
tasks.append(
{
'taskCode': task['taskCode'],
'title': task['title'],
'browserTime': task['browserTime']
}
)
return tasks
except Exception as e:
fn_print(f"用户【{self.phone}】 - 获取任务列表请求异常!{e}")
fn_print(response.text)
return None
async def perform_tasks(self, task_code):
try:
response = await self.client.post(
url="/task/start",
headers=self.headers,
json={"taskCode": task_code}
)
data = response.json()
if data['code'] != 2200:
fn_print(f"用户【{self.phone}】 - 执行任务【{task_code}】失败了,跳过当前任务")
return None
else:
task_id = data['data']
return task_id
except Exception as e:
fn_print(f"用户【{self.phone}】 - 执行任务【{task_code}】请求异常!{e}")
fn_print(response.text)
return None
async def finsh_task(self, task_id):
max_retry = 3 # 最大重试次数
retry_delay = 2 # 重试间隔时间(秒)
for attempt in range(max_retry):
try:
response = await self.client.post(
url="/task/finish",
headers=self.headers,
json={"id": task_id}
)
data = response.json()
if data['code'] == 2200:
fn_print(f"用户【{self.phone}】 - 完成任务【{task_id}】成功!开始领取奖励")
return True
if attempt < max_retry - 1:
fn_print(f"用户【{self.phone}】 - 完成任务【{task_id}】失败了,尝试重新提交(第{attempt + 1}次重试。。)")
await asyncio.sleep(retry_delay * (attempt + 1))
continue
fn_print(f"用户【{self.phone}】 - 完成任务【{task_id}】最终失败,跳过当前任务")
return False
except Exception as e:
error_msg = f"用户【{self.phone}】 - 完成任务【{task_id}】请求异常!{e}"
if 'response' in locals():
error_msg += f"\n{response.text}"
fn_print(error_msg)
if attempt == max_retry - 1:
return False
await asyncio.sleep(retry_delay * (attempt + 1))
async def receive_reward(self, task_id):
try:
response = await self.client.post(
url="/task/receive",
headers=self.headers,
json={"id": task_id}
)
data = response.json()
if data['code'] != 2200:
fn_print(f"用户【{self.phone}】 - 领取签到奖励失败了, 请尝试手动领取")
else:
fn_print(f"用户【{self.phone}】 - 领取签到奖励成功!开始下一个任务")
except Exception as e:
fn_print(f"用户【{self.phone}】 - 领取签到奖励请求异常!{e}")
fn_print(response.text)
async def get_mileage_info(self):
try:
response = await self.client.post(
url="/index/signIndex",
headers=self.headers,
json={}
)
data = response.json()
if data['code'] != 2200:
fn_print(f"用户【{self.phone}】 - 获取积分信息失败了")
return None
else:
cycle_sign_num = data['data']['cycleSighNum']
continuous_history = data['data']['continuousHistory']
mileage = data['data']['mileageBalance']['mileage']
today_mileage = data['data']['mileageBalance']['todayMileage']
# 存储精简信息
self.summary_info['cycle_sign_num'] = cycle_sign_num
self.summary_info['continuous_history'] = continuous_history
self.summary_info['mileage'] = mileage
self.summary_info['today_mileage'] = today_mileage
fn_print(
f"用户【{self.phone}】 - 本月签到{cycle_sign_num}天,连续签到{continuous_history}天,今日共获取{today_mileage}里程,当前剩余里程{mileage}")
return True
except Exception as e:
fn_print(f"用户【{self.phone}】 - 获取积分信息请求异常!{e}")
fn_print(response.text)
return None
async def run(self):
# 初始化摘要信息
self.summary_info = {
'phone': self.phone,
'status': '未签到',
'cycle_sign_num': 0,
'continuous_history': 0,
'mileage': 0,
'today_mileage': 0
}
today_sign = await self.sign_in()
if today_sign is None:
return self.summary_info
if today_sign:
fn_print(f"用户【{self.phone}】 - 今日已签到,开始获取任务列表")
self.summary_info['status'] = "签到成功"
else:
if await self.do_sign_in():
fn_print(f"用户【{self.phone}】 - 签到成功,开始获取任务列表")
tasks = await self.get_task_list()
if tasks:
for task in tasks:
task_code = task['taskCode']
title = task['title']
browser_time = task['browserTime']
fn_print(f"用户【{self.phone}】 - 开始做任务【{title}】,需要浏览{browser_time}秒")
task_id = await self.perform_tasks(task_code)
if task_id:
await asyncio.sleep(browser_time)
if await self.finsh_task(task_id):
await self.receive_reward(task_id)
await self.get_mileage_info()
# 添加到推送摘要列表
summary = f" {self.phone}\n • {self.summary_info['status']}本月签到{self.summary_info['cycle_sign_num']}天\n • 当前里程: {self.summary_info['mileage']}(+{self.summary_info['today_mileage']})"
push_summary_list.append(summary)
return self.summary_info
async def main():
tasks = []
for cookie in tc_cookies:
tclx = Tclx(cookie)
tasks.append(tclx.run())
results = await asyncio.gather(*tasks)
return results
if __name__ == '__main__':
results = asyncio.run(main())
# 构建精简推送内容
title = f"同程旅行签到 - {datetime.now().strftime('%m/%d')}"
push_content = ""
for summary in push_summary_list:
push_content += f"\n\n{summary}"
# 添加统计信息
success_count = sum(1 for r in results if r and r.get('status') in ['签到成功', '今日已签到'])
push_content += f""
push_content = push_content.strip()
# 根据推送开关决定是否推送
if push_switch == '1':
if bark_key:
bark_send(title, push_content, bark_key, bark_icon, bark_group)
else:
notify_send(title, push_content)
else:
fn_print("推送开关已关闭,不发送推送通知")
# 输出详细日志
fn_print("\n" + "="*50)
fn_print("详细执行日志:")
fn_print(''.join(all_print_list))
解析
该脚本为面向同程旅行领福利的自动签到与做任务脚本,支持多账号并发。它会:
调用同程旅行福利中心接口判断今天是否已签到,必要时执行签到;
拉取任务列表,逐个开始任务→等待浏览时长→提交完成→领取奖励;
查询当日/当月积分(里程)等信息;
汇总为简短推送(Bark 或青龙
notify
),并打印详细日志。
主要方法
get_env(env_var, separator)
从环境变量(或.env
)中取配置,按分隔符拆分。用于拿tc_cookie
多账号串。fn_print(*args, ...)
对print
的包装:既打印到控制台,也把文本追加到全局all_print_list
,用于最后"详细日志"输出。Bark/通知设置(顶端常量 &
notify_send
)读取
BARK_*
、PUSH_SWITCH
等,覆写到os.environ
以兼容青龙的notify.py
;尝试
from notify import send
,失败则降级为简单打印。( 警示)主流程结尾调用了
bark_send(...)
,但脚本里没有定义该函数——要么实现它,要么统一用notify_send
。类
Tclx
(一个账号一实例)code==2200
认为 token 有效;解析todaySign
(今日是否已签)和剩余里程;其他 code 视为 token 失效。
构造:创建
httpx.AsyncClient
(base_url=https://app.17u.cn/welfarecenter
),拼请求头,把phone/apptoken/device
注入到 header;并准备summary_info
(用于推送摘要)。sign_in()
调/index/signIndex
:do_sign_in()
调/index/sign
提交签到(type:1, day:YYYY-MM-DD
),成功则标记"签到成功"。get_task_list()
调/task/taskList?version=11.0.7
,筛出需浏览的进行中任务(state==1 且 browserTime>0
),返回[{taskCode,title,browserTime}, ...]
。perform_tasks(task_code)
调/task/start
启动任务,返回task_id
(后续提交/领奖用)。finsh_task(task_id)
(原文拼写如此)
调/task/finish
提交完成,内置最大 3 次重试(指数型延时 2s、4s …),成功返回 True。receive_reward(task_id)
调/task/receive
领取任务奖励。get_mileage_info()
再调一次/index/signIndex
,拿本月签到天数、连续签到、今日里程、当前里程,写入summary_info
。run()
(单账号完整流程)sign_in()
判断今日是否已签;未签则do_sign_in()
;拉任务 →
start
→await browserTime
→finish
→receive
;查询里程信息;
生成一条精简摘要,附加到全局
push_summary_list
供统一推送。main()
为每个
tc_cookie
账号 new 一个Tclx
,异步并发run()
(asyncio.gather
),最终返回各账号结果。__main__
区执行
asyncio.run(main())
;拼接推送标题与摘要内容;
若
PUSH_SWITCH=='1'
则推送( 目前引用了未定义的bark_send
);打印累积的"详细执行日志"。
接口一览(相对路径)
POST /index/signIndex
:查询签到与里程信息(用两次:前置判断 & 结尾汇总)POST /index/sign
:执行签到POST /task/taskList?version=11.0.7
:取任务列表POST /task/start
:开始任务(返回id
)POST /task/finish
:完成任务(带重试)POST /task/receive
:领取奖励
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论