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=2019052.部署教程
3.代码如下
#!/usr/bin/env python3# -*- coding: utf-8 -*-import base64import jsonimport loggingimport osimport randomimport reimport smtplibfrom email.header import Headerfrom email.mime.text import MIMETextfrom email.utils import formataddrfrom smtplib import SMTP_SSLfrom sys import exitfrom time import sleepimport requestsimport tomlfrom requests.utils import cookiejar_from_dict, dict_from_cookiejarUSER_AGENT = ["Mozilla/5.0 (Linux; U; Android 11; zh-cn; PDYM20 Build/RP1A.200720.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 HeyTapBrowser/40.7.24.9","Mozilla/5.0 (Linux; Android 12; Redmi K30 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36"]class Push_messages:class Server_chan:def __init__(self, send_key: str) -> None:self.send_key = send_keydef send_message(self, content: str) -> bool:data = {"title": "联想签到", "desp": content}response = requests.post(f"https://sctapi.ftqq.com/{self.send_key}.send", data=data)res_data = response.json().get("data")pushid = res_data.get("pushid")readkey = res_data.get("readkey")result = requests.get(f"https://sctapi.ftqq.com/push?id={pushid}&readkey={readkey}")return True if result.json().get("code") == 0 else Falseclass Wechat_message:def __init__(self, corpid: str, corpsecret: str, agentid: str) -> None:self.corpid = corpidself.corpsecret = corpsecretself.agentid = agentidself.token = (requests.get(f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={self.corpsecret}").json().get("access_token"))def send_message(self, content: str) -> bool:data = {"touser": "@all","msgtype": "text","agentid": self.agentid,"text": {"content": content},"safe": 0,}response = requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={self.token}",data=json.dumps(data),)return True if response.json().get("errcode") == 0 else Falseclass Dingtalk_message:def __init__(self, ding_accesstoken: str) -> None:self.ding_accesstoken = ding_accesstokendef send_message(self, content: str) -> bool:data = {"msgtype": "text","text": {"content": content},"at": {"isAtAll": True},}response = requests.post(f"https://oapi.dingtalk.com/robot/send?access_token={self.ding_accesstoken}",data=json.dumps(data),)return True if response.json().get("errcode") == 0 else Falseclass Email_message:def __init__(self, sender_email: str, sender_password: str, receiver_email: str, smtp_server: str,smtp_port: int) -> None:self.sender_email = sender_emailself.sender_password = sender_passwordself.receiver_email = receiver_emailself.smtp_server = smtp_serverself.smtp_port = smtp_portdef send_message(self, content: str) -> bool:receiver_email = [self.receiver_email]message = MIMEText(content, 'plain', 'utf-8')message['Subject'] = Header("联想智选定时签到结果", "utf-8")message['From'] = Header("联想智选定时签到程序", "utf-8")message['To'] = receiver_email[0]try:smtp = SMTP_SSL(self.smtp_server, self.smtp_port)smtp.login(self.sender_email, self.sender_password)smtp.sendmail(self.sender_email, receiver_email, message.as_string())smtp.quit()return Trueexcept smtplib.SMTPException as e:print('send email error', e)return Falseclass QQEmail_message:def __init__(self, sender_email: str, sender_password: str, receiver_email: str, smtp_server: str,smtp_port: int) -> None:self.sender_email = sender_emailself.sender_password = sender_passwordself.receiver_email = receiver_emailself.smtp_server = smtp_serverself.smtp_port = smtp_portdef send_message(self, content: str) -> bool:tmp = self.receiver_email.split(",")receiver_email = tmpsubject = "联想智选定时签到结果"from_head = "联想智选定时签到程序"message = MIMEText(content, 'plain', 'utf-8')# 使用 formataddr + Header 自动处理编码message['From'] = formataddr((str(Header(from_head, 'utf-8')), self.sender_email))message['Subject'] = Header(subject, 'utf-8')try:smtp = SMTP_SSL(self.smtp_server, self.smtp_port)smtp.login(self.sender_email, self.sender_password)for i in range(len(receiver_email)):message['To'] = formataddr((str(Header(receiver_email[i], 'utf-8')), self.receiver_email))smtp.sendmail(self.sender_email, receiver_email[i], message.as_string())smtp.quit()return Trueexcept smtplib.SMTPException as e:print('send qq email error', e)return Falsedef set_push_type():for type, key in config.get("message_push").items():key_list = key.values()if "".join(key_list):return getattr(Push_messages(), type)(*key_list).send_messageelse:return loggerdef login(username, password):def get_cookie():session.headers = {"user-agent": ua,"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",}session.get(url="https://reg.lenovo.com.cn/auth/rebuildleid")session.get(url="https://reg.lenovo.com.cn/auth/v1/login?ticket=5e9b6d3d-4500-47fc-b32b-f2b4a1230fd3&ru=https%3A%2F%2Fmclub.lenovo.com.cn%2F")data = f"account={username}&password={base64.b64encode(str(password).encode()).decode()}\&ps=1&ticket=5e9b6d3d-4500-47fc-b32b-f2b4a1230fd3&codeid=&code=&slide=v2&applicationPlatform=2&shopId=\1&os=web&deviceId=BIT%2F8ZTwWmvKpMsz3bQspIZRY9o9hK1Ce3zKIt5js7WSUgGQNnwvYmjcRjVHvJbQ00fe3T2wxgjZAVSd\OYl8rrQ%3D%3D&t=1655187183738&websiteCode=10000001&websiteName=%25E5%2595%2586%25E5%259F%258E%25E\7%25AB%2599&forwardPageUrl=https%253A%252F%252Fmclub.lenovo.com.cn%252F"login_response = session.post(url="https://reg.lenovo.com.cn/auth/v2/doLogin", data=data)if login_response.json().get("ret") == "1":logger(f"{username}账号或密码错误")return Noneck_dict = dict_from_cookiejar(session.cookies)config["cookies"][username] = f"{ck_dict}"toml.dump(config, open(config_file, "w"))session.cookies = cookiejar_from_dict(ck_dict)return sessionsession = requests.Session()session.headers = {"user-agent": ua,"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",}if cookie_dict := config.get("cookies").get(username):session.cookies = cookiejar_from_dict(eval(cookie_dict))ledou = session.post("https://i.lenovo.com.cn/info/uledou.jhtml",data={"sts": "b044d754-bda2-4f56-9fea-dcf3aecfe782"},)try:int(ledou.text)except ValueError:logger(f"{username} ck有错,重新获取ck并保存")session = get_cookie()return sessionlogger(f"{username} ck没有错")return sessionelse:logger(f"{username} ck为空,重新获取ck并保存")session = get_cookie()return sessiondef sign(session):res = session.get(url="https://mclub.lenovo.com.cn/signlist/")token = re.findall('token\s=\s"(.*?)"', res.text)[0]data = f"_token={token}&memberSource=1"headers = {"Host": "mclub.lenovo.com.cn","pragma": "no-cache","cache-control": "no-cache","accept": "application/json, text/javascript, */*; q=0.01","origin": "https://mclub.lenovo.com.cn","x-requested-with": "XMLHttpRequest","user-agent": ua+ "/lenovoofficialapp/16554342219868859_10128085590/newversion/versioncode-1000080/","content-type": "application/x-www-form-urlencoded; charset=UTF-8","referer": "https://mclub.lenovo.com.cn/signlist?pmf_group=in-push&pmf_medium=app&pmf_source=Z00025783T000","accept-language": "zh-CN,en-US;q=0.8",}sign_response = session.post("https://mclub.lenovo.com.cn/signadd", data=data, headers=headers)sign_days = (session.get(url="https://mclub.lenovo.com.cn/getsignincal").json().get("signinCal").get("continueCount"))sign_user_info = session.get("https://mclub.lenovo.com.cn/signuserinfo")try:serviceAmount = sign_user_info.json().get("serviceAmount")ledou = sign_user_info.json().get("ledou")except Exception as e:logger(sign_user_info.headers["content-type"])logger(sign_user_info.status_code)logger(e)serviceAmount, ledou = None, Nonesession.close()if sign_response.json().get("success"):return f"\U00002705账号{username}签到成功, \U0001F4C6连续签到{sign_days}天, \U0001F954共有乐豆{ledou}个, \U0001F4C5共有延保{serviceAmount}天\n"else:return f"\U0001F6AB账号{username}今天已经签到, \U0001F4C6连续签到{sign_days}天, \U0001F954共有乐豆{ledou}个, \U0001F4C5共有延保{serviceAmount}天\n"def main():global logger, config_file, config, ua, usernamelogging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s: %(message)s")logger = logging.getLogger(__name__).info# config_file = r"../private-config/config.toml"# config = toml.load(config_file)# 读取CI指定的配置config_file = os.getenv("CONFIG_FILE", "config.toml")config = toml.load(config_file)account = config.get("account")if not account:exit(1)if not (ua := config.get("browser").get("ua")):ua = random.choice(USER_AGENT)config["browser"]["ua"] = uapush = set_push_type()message = "联想签到: \n"for username, password in account.items():session = login(username, password)if not session:continuemessage += sign(session)sleep(random.randint(0,20))push(message)if __name__ == "__main__":sleep(random.randint(0,120)) # 延时 0-120smain()
这是一个 联想智选自动签到脚本,主要功能包括:
多账号自动登录联想账号
从
config.toml里读取多个账号密码。支持复用历史 Cookie,如果 Cookie 失效则自动重新登录并刷新 Cookie。
自动完成联想俱乐部签到
调用联想俱乐部签到接口(
signadd等),完成每天的签到操作。查询连续签到天数、乐豆数量、延保天数等信息。
汇总所有账号的签到结果并推送通知
根据配置选择一种推送方式(Server酱 / 企业微信 / 钉钉机器人 / 邮件 / QQ 邮箱)。
统一生成一条汇总消息,比如:每个账号是否签到成功、连续签到天数、乐豆数量和延保天数等。
适配青龙面板 / CI 场景
通过环境变量
CONFIG_FILE指定配置文件路径;脚本启动时会随机延时 0–120 秒,再对每个账号之间随机 sleep 0–20 秒,降低被风控的概率。
一句话概括:
"定时批量登录联想账号 → 自动签到 → 统计乐豆/延保/连续天数 → 通过你配置的渠道发一条汇总通知。"
主要函数
1. Push_messages 类及其子类们
Push_messages 是一个"通知发送器的集合",里面嵌套了多种推送方式,每种方式都是一个小类,负责把签到结果发出去:
Push_messages.Server_chan使用 Server酱(sctapi.ftqq.com) 推送签到结果。
发送标题为「联想签到」的消息,并通过接口查询推送是否成功。
Push_messages.Wechat_message使用 企业微信应用消息 推送结果。
持有
corpid、corpsecret、agentid,先获取access_token,再发送文本消息给@all。Push_messages.Dingtalk_message使用 钉钉机器人 Webhook 推送结果。
发送一个
text类型的消息,并 @所有人。Push_messages.Email_message通过 SMTP 发送普通邮件(单收件人版本)。
可配置发件人邮箱、授权码、收件人、SMTP 服务器与端口。
邮件标题是「联想智选定时签到结果」。
Push_messages.QQEmail_message针对 QQ邮箱 / 多收件人 的邮件推送实现。
支持多个收件人(逗号分隔),逐个发送。
用
formataddr + Header解决中文显示和编码问题。
这些子类的共同点:都实现了
send_message(self, content: str) -> bool接口,用来发一段文本内容。
2. set_push_type()
作用:根据配置文件选择推送方式,返回一个"发送函数"。
从
config["message_push"]中读取各类推送配置(Server酱、企业微信、钉钉、邮箱等)。按配置中是否填写了对应字段来判断某种推送方式是否启用:
找到第一个有完整配置的推送方式,就实例化对应的
Push_messages.*类,返回它的send_message方法。如果没有任何推送方式配置,则退而求其次,返回
logger(也就是仅打印日志,不推送)。
后面主流程里会调用:
push = set_push_type(),然后push(message),完全不关心背后具体用的是哪种通知渠道。
3. login(username, password)
作用:负责单个账号的登录与 Cookie 管理,返回一个已登录的 requests.Session 会话。
主要逻辑
先尝试从
config["cookies"]中读取保存过的 Cookie:直接走"重新获取 Cookie"的流程。
将 Cookie 转为
cookiejar并挂到 Session 上;调用一个接口(如
uledou)测试 Cookie 是否有效:如果能正常返回数字,说明 Cookie 有效 → 直接复用。
如果无效,说明 Cookie 过期或错误 → 打日志提示,转为重新获取 Cookie。
如果有:
如果没有 Cookie:
内部的
get_cookie()子函数:如果返回表示账号或密码错误 → 记日志,返回
None。否则认为登录成功:
从 Session 中取出 Cookie,存入
config["cookies"][username],并写回config.toml;返回这个 Session。
设置请求头(UA、Content-Type 等);
访问一些预登陆页面建立会话;
把密码做 base64 之后放入表单中;
构造登录请求:
调用联想的登录接口:
最终,login() 返回一个已经带有有效 Cookie 的 session,供后续签到使用;如果完全失败则返回 None。
4. sign(session)
作用:使用已登录的 Session 执行真正的"联想俱乐部签到",并返回一行描述结果的字符串。
流程概述:
访问签到页面,解析页面中的
_token(表单防 CSRF 的 token)。构造签到请求参数(
_token+ 固定字段),带上移动端 UA 等请求头,向signadd接口发起签到请求。再请求:
当前乐豆数
ledou当前延保天数
serviceAmount签到日历接口,获取连续签到天数;
用户信息接口,获取:
根据签到接口返回的 JSON 中
success字段:若签到成功:返回一段带表情的文案,例如:
账号xxx签到成功, 📆连续签到X天, 🥔共有乐豆Y个, 📅共有延保Z天
若提示已签到:返回相似文案,但文本改成"今天已经签到"。
这个函数不做推送,只返回文字结果;汇总和发送由
main()统一处理。
5. main()
作用:主流程调度,总控逻辑。
主要步骤:
初始化日志系统,并把
logger设为全局可用(方便其他函数调用)。确定配置文件路径:
优先从环境变量
CONFIG_FILE取;否则默认使用当前目录下的
config.toml。加载配置
config = toml.load(config_file),读取:account:账号密码字典{username: password};browser.ua:自定义 UA;如果没配置,就从预设的USER_AGENT列表里随机选一个,并回写到 config。调用
set_push_type()获取一个push函数(可能是 Server酱、企业微信、钉钉、邮件等,也可能只是 logger)。构建汇总消息字符串
message = "联想签到: \n"。遍历所有账号:
调用
login(username, password)拿到登录 Session;若登录失败
session is None,跳过该账号;否则调用
sign(session),拿到该账号的一行签到结果字符串,拼接到message;每个账号之间
sleep(random.randint(0,20)),随机等待 0–20 秒。所有账号处理完后,调用
push(message)发送汇总结果。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论