1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/55RK8C若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/55RK8C若失效,可用地址
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.代码如下
# -*- coding: utf-8 -*-import osimport reimport sysimport timeimport randomfrom dataclasses import dataclassfrom typing import Optional, Dict, Tuple, Listimport requestsclass SignResult:ok: boolstatus: str # success / already / failedmessage: strdetail: str = ""class HongdouAutoSign:def __init__(self, base_url: str, username: str, password: str, ua: Optional[str] = None, timeout: int = 20):self.base_url = base_url.rstrip("/")self.username = usernameself.password = passwordself.timeout = timeoutself.session = requests.Session()self.session.headers.update({"User-Agent": ua or os.getenv("HONGDOU_UA", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/122.0.0.0 Safari/537.36"),"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language": "zh-CN,zh;q=0.9","Connection": "keep-alive",})# ---------- 基础工具方法 ----------def _get(self, path: str, **kwargs) -> requests.Response:url = path if path.startswith("http") else f"{self.base_url}{path}"resp = self.session.get(url, timeout=self.timeout, **kwargs)resp.raise_for_status()return respdef _post(self, path: str, data=None, headers=None, **kwargs) -> requests.Response:url = path if path.startswith("http") else f"{self.base_url}{path}"resp = self.session.post(url, data=data, headers=headers, timeout=self.timeout, **kwargs)resp.raise_for_status()return resp@staticmethoddef _guess_encoding(resp: requests.Response) -> str:# Discuz老站可能是 GBK,requests 默认可能不准,做个兜底# 不强制依赖 chardet,优先用 apparent_encodingenc = (getattr(resp, "apparent_encoding", None) or resp.encoding or "utf-8").lower()return enc@staticmethoddef (html_text: str) -> Optional[str]:# 常见:name="formhash" value="xxxx"m = re.search(r'name="formhash"\s+value="([^"]+)"', html_text)if m:return m.group(1)# 或者:formhash=xxxxm = re.search(r"formhash=([0-9a-zA-Z]+)", html_text)return m.group(1) if m else None@staticmethoddef _extract_loginhash(html_text: str) -> Optional[str]:# 常见:loginhash=xxxxm = re.search(r"loginhash=([0-9a-zA-Z]+)", html_text)return m.group(1) if m else None@staticmethoddef _contains_any(text: str, keywords: List[str]) -> bool:return any(k in text for k in keywords)# ---------- 核心流程方法 ----------def fetch_home_tokens(self) -> Tuple[Optional[str], Optional[str], str]:"""访问首页,提取 loginhash / formhash,用于登录。返回:(loginhash, formhash, html_text)"""resp = self._get("/")enc = self._guess_encoding(resp)resp.encoding = enchtml_text = resp.textloginhash = self._extract_loginhash(html_text)formhash = self._extract_formhash(html_text)return loginhash, formhash, html_textdef login(self) -> SignResult:"""执行登录:Discuz 标准登录接口 member.php?mod=logging&action=login&loginsubmit=yes..."""loginhash, formhash, _ = self.fetch_home_tokens()# 有些站点首页未必包含 loginhash/formhash,我们再访问一次登录页兜底if not loginhash or not formhash:resp = self._get("/member.php?mod=logging&action=login")resp.encoding = self._guess_encoding(resp)html_text = resp.textloginhash = loginhash or self._extract_loginhash(html_text)formhash = formhash or self._extract_formhash(html_text)if not formhash:return SignResult(False, "failed", "无法提取 formhash(站点结构可能不同/被防护拦截)")loginhash_q = f"&loginhash={loginhash}" if loginhash else ""login_url = f"/member.php?mod=logging&action=login&loginsubmit=yes{loginhash_q}&inajax=1"data = {"username": self.username,"password": self.password,"cookietime": "2592000","quickforward": "yes","handlekey": "ls","formhash": formhash,}# Discuz 登录常见需要这个headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8","X-Requested-With": "XMLHttpRequest","Referer": f"{self.base_url}/member.php?mod=logging&action=login",}try:resp = self._post(login_url, data=data, headers=headers)except Exception as e:return SignResult(False, "failed", f"登录请求失败:{e}")# 登录响应经常是 html/xml片段txt = resp.text# 常见成功标志:欢迎您回来 / 登录成功 / window.location / succeedhandle_lsif self._contains_any(txt, ["欢迎您回来", "登录成功", "succeedhandle_ls", "window.location"]):return SignResult(True, "success", "登录成功")# 常见失败标志if self._contains_any(txt, ["密码错误", "登录失败", "security code", "验证码", "请输入验证码"]):return SignResult(False, "failed", "登录失败:可能需要验证码/密码错误/安全校验", detail=txt[:500])# 兜底:再访问一次首页,看是否已登录(例如出现"退出")try:home = self._get("/")home.encoding = self._guess_encoding(home)if self._contains_any(home.text, ["退出", "注销", "欢迎您回来", "设置"]):return SignResult(True, "success", "登录成功(通过首页二次确认)")except Exception:passreturn SignResult(False, "failed", "登录状态不明确,可能登录失败或站点返回结构不同", detail=txt[:500])def detect_sign_entry(self) -> Optional[str]:"""探测签到入口:尝试常见 Discuz 签到插件路径,返回可用的 path(带参数)或 None"""candidates = ["/plugin.php?id=dsu_paulsign:sign","/plugin.php?id=dsu_paulsign:sign&operation=qiandao","/plugin.php?id=dsu_paulsign:sign&operation=qiandao&inajax=1","/plugin.php?id=k_misign:sign","/plugin.php?id=k_misign:sign&operation=qiandao",]for path in candidates:try:resp = self._get(path)resp.encoding = self._guess_encoding(resp)text = resp.text# 命中一些常见的签到页面特征if self._contains_any(text, ["签到", "今日已签到", "您今天已经签到", "dsu_paulsign", "k_misign", "qiandao"]):return pathexcept Exception:continuereturn Nonedef submit_signin(self, sign_entry: str) -> SignResult:"""提交签到:- 先 GET 签到页拿 formhash- 再 POST 提交(以 dsu_paulsign 常见参数为主)"""try:page = self._get(sign_entry)page.encoding = self._guess_encoding(page)html_text = page.textexcept Exception as e:return SignResult(False, "failed", f"打开签到页失败:{e}")# 已签到判定if self._contains_any(html_text, ["今日已签到", "您今天已经签到", "已经签过", "已签到"]):return SignResult(True, "already", "今日已签到")formhash = self._extract_formhash(html_text)if not formhash:# 有些站点在源码里 formhash=xxxxformhash = self._extract_formhash(html_text)if not formhash:return SignResult(False, "failed", "签到页未提取到 formhash,可能需要调整入口或站点有防护")# 提交地址:dsu_paulsign 通常是同一个 plugin.php?id=... 带 operation=qiandao&inajax=1# 若 entry 没带 operation,就补齐post_path = sign_entryif "operation=qiandao" not in post_path:if "?" in post_path:post_path += "&operation=qiandao"else:post_path += "&operation=qiandao"if "inajax=1" not in post_path:post_path += "&inajax=1"# dsu_paulsign 常用表单参数(站点可能略有不同,但大多数兼容)# qdxq:心情,qdmode:签到模式payload = {"formhash": formhash,"qdxq": "fd", # 心情:fd/yl/kx... 不同站点定义不同,fd常见可用"qdmode": "3", # 1/2/3 常见:快速签到"todaysay": "日常签到","fastreply": "0",}headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8","X-Requested-With": "XMLHttpRequest","Referer": f"{self.base_url}{sign_entry}",}try:resp = self._post(post_path, data=payload, headers=headers)txt = resp.textexcept Exception as e:return SignResult(False, "failed", f"签到提交失败:{e}")# 成功/已签到关键词判定if self._contains_any(txt, ["签到成功", "恭喜", "您已签到", "成功", "已签到", "已经签到"]):return SignResult(True, "success", "签到成功", detail=txt[:400])if self._contains_any(txt, ["今日已签到", "您今天已经签到", "已经签过"]):return SignResult(True, "already", "今日已签到", detail=txt[:400])if self._contains_any(txt, ["未登录", "先登录", "login", "权限不足"]):return SignResult(False, "failed", "签到失败:疑似未登录或权限不足", detail=txt[:400])return SignResult(False, "failed", "签到失败:返回内容未命中成功特征,可能需要定制参数/入口", detail=txt[:600])def run(self) -> SignResult:"""一键执行:登录 -> 探测签到入口 -> 提交签到"""login_res = self.login()if not login_res.ok:return login_restime.sleep(random.uniform(1.2, 2.2))entry = self.detect_sign_entry()if not entry:return SignResult(False, "failed", "未探测到可用签到入口:站点可能非Discuz或签到插件路径不同")time.sleep(random.uniform(0.8, 1.6))return self.submit_signin(entry)def main():base_url = os.getenv("HONGDOU_BASE_URL", "https://hongdou.gxnews.com.cn")username = os.getenv("HONGDOU_USERNAME")password = os.getenv("HONGDOU_PASSWORD")if not username or not password:print("❌ 缺少环境变量:HONGDOU_USERNAME / HONGDOU_PASSWORD")print("示例:")print("export HONGDOU_USERNAME='你的账号'")print("export HONGDOU_PASSWORD='你的密码'")sys.exit(1)bot = HongdouAutoSign(base_url=base_url, username=username, password=password)res = bot.run()print("=" * 60)print(f"站点:{base_url}")print(f"结果:{res.status}")print(f"信息:{res.message}")if res.detail:print("-" * 60)print("返回片段(用于排查/适配):")print(res.detail)print("=" * 60)sys.exit(0 if res.ok else 2)if __name__ == "__main__":main()
该脚本为红豆社区自动签到脚本,主要作用包括:
自动登录红豆社区(通过 Discuz 常见登录接口
member.php?mod=logging&action=login)。自动探测"签到插件入口"(不同论坛可能用不同插件,脚本按候选路径逐个尝试)。
自动提交签到请求(获取签到页的
formhash,再 POST 提交签到表单)。输出明确结果:成功 / 已签到 / 失败原因(并附带返回片段,便于你二次适配)。
主要方法
fetch_home_tokens()
作用:访问首页,提取 Discuz 常见的两个关键值:
loginhash:登录流程里经常要带的参数(有些站点会有,有些没有)formhash:Discuz 防 CSRF 的核心字段(登录、签到通常必需)意义:没有
formhash,大概率无法提交登录/签到表单。
login()
作用:执行登录动作
优先从首页提取 token;不够则访问登录页再提取
POST 到
member.php?mod=logging&action=login&loginsubmit=yes...通过返回内容关键字判断是否登录成功(或回首页二次确认)
意义:把 Session 的 cookie 建立好,后续探测签到入口、提交签到才能带登录态。
detect_sign_entry()
作用:探测签到入口
plugin.php?id=dsu_paulsign:sign...(Discuz 最常见签到插件之一)plugin.php?id=k_misign:sign...(另一个常见签到插件)依次 GET 常见的签到插件路径:
命中页面包含"签到/已签到/插件标记"等字样就认为可用
意义:不同论坛插件不同,你又没提供具体签到路径时,这个方法能最大化自动适配。
submit_signin(sign_entry)
作用:真正执行签到提交
GET 签到页面,判断是否已签到
解析
formhashPOST 提交签到(
qdxq/qdmode/todaysay...这些是 dsu_paulsign 常见参数)根据返回内容判断 "签到成功/已签到/失败"
意义:把签到动作封装成可复用模块,后续你要扩展"领积分/领礼包/做任务",也可以按这个模式继续加。
run()
作用:把流程串起来:登录 → 探测入口 → 签到提交 → 返回最终结果
意义:对外只暴露一个"执行入口",方便青龙/cron 调用。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论