import reimport timeimport randomimport requests
class VocBbsSigner:
BASE = "https://bbs.voc.com.cn" TIMEOUT = 20
def __init__(self, username: str, password: str): self.username = username self.password = password
self.s = requests.Session() self.s.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/120.0 Safari/537.36", "Referer": f"{self.BASE}/", })
def _get(self, url: str, **kwargs) -> requests.Response: return self.s.get(url, timeout=self.TIMEOUT, allow_redirects=True, **kwargs)
def _post(self, url: str, data: dict, **kwargs) -> requests.Response: return self.s.post(url, data=data, timeout=self.TIMEOUT, allow_redirects=True, **kwargs)
@staticmethod def _sleep(a=0.8, b=1.8): time.sleep(random.uniform(a, b))
@staticmethod def _pick(html: str, patterns: list[str], default: str = "") -> str: for p in patterns: m = re.search(p, html, re.I | re.S) if m: return m.group(1) return default
def login(self) -> bool: """ 登录(通用 Discuz 方式): 1) 打开登录页,提取 formhash/loginhash 2) POST 提交 username/password 3) 用 cookie/页面关键字判断是否登录成功 """ login_page = self._get(f"{self.BASE}/member.php?mod=logging&action=login") html = login_page.text
loginhash = self._pick(html, [ r'loginhash=([a-zA-Z0-9]+)', r'name="loginhash"\s+value="([^"]+)"', ])
formhash = self._pick(html, [ r'name="formhash"\s+value="([^"]+)"', r'formhash=([a-zA-Z0-9]+)', ])
if not formhash: idx = self._get(f"{self.BASE}/").text formhash = self._pick(idx, [ r'name="formhash"\s+value="([^"]+)"', r'formhash=([a-zA-Z0-9]+)', ])
if not formhash: print("[login] 未获取到 formhash:可能不是 Discuz/页面结构不同/触发风控。") return False
self._sleep()
post_url = f"{self.BASE}/member.php?mod=logging&action=login&loginsubmit=yes" if loginhash: post_url += f"&loginhash={loginhash}"
payload = { "formhash": formhash, "referer": f"{self.BASE}/", "username": self.username, "password": self.password, "questionid": "0", "answer": "", "cookietime": "2592000", }
resp = self._post(post_url, data=payload) txt = resp.text ck = self.s.cookies.get_dict()
if any(k.lower() in ("discuz_uid", "uid", "auth") for k in ck.keys()): print("[login] cookie 显示已登录。") return True
if any(k in txt for k in ["退出", "欢迎您回来", "您已登录"]): print("[login] 页面显示已登录。") return True
if any(k in txt for k in ["验证码", "安全验证", "滑块", "人机验证"]): print("[login] 可能触发验证码/安全验证:建议改用 Playwright 登录一次后复用登录态。") else: print("[login] 登录失败:可能账号密码错误或站点登录机制不同。")
return False
def sign_try_dsu_paulsign(self) -> bool: """ 尝试 Discuz 常见签到插件:dsu_paulsign 常见入口: 1) plugin.php?id=dsu_paulsign:sign 2) plugin.php?id=dsu_paulsign:sign&operation=qiandao 这里采取: - 先 GET 入口抓 formhash - 再 POST 提交签到 """ entry_urls = [ f"{self.BASE}/plugin.php?id=dsu_paulsign:sign", f"{self.BASE}/plugin.php?id=dsu_paulsign:sign&operation=qiandao", ]
for entry in entry_urls: try: self._sleep() page = self._get(entry) html = page.text
if any(k in html for k in ["您需要先登录", "登录", "未定义操作", "插件不存在", "抱歉"]): continue
formhash = self._pick(html, [ r'name="formhash"\s+value="([^"]+)"', r'formhash=([a-zA-Z0-9]+)', ]) if not formhash: if any(k in html for k in ["已签到", "今日已签", "签到成功"]): print("[sign] 页面显示已签到/已成功。入口:", entry) return True continue
post_url = f"{self.BASE}/plugin.php?id=dsu_paulsign:sign&operation=qiandao&inajax=1" payload = { "formhash": formhash, "qdxq": "kx", "qdmode": "1", "todaysay": "签到打卡~", "fastreply": "0", }
self._sleep() r = self._post(post_url, data=payload, headers={"Referer": entry}) txt = r.text
if any(k in txt for k in ["成功", "已签到", "签到成功", "succeed", "success"]): print("[sign] 签到成功/已签到。") return True
if any(k in txt for k in ["需要先登录", "权限", "抱歉", "间隔", "灌水"]): print("[sign] 被拒绝:可能未登录/无权限/频率限制。返回片段:") print(txt[:200]) return False
except Exception as e: print("[sign] 尝试入口异常:", entry, e)
print("[sign] 未命中 dsu_paulsign 签到入口。可能:1) 论坛不是 Discuz;2) 签到插件不同;3) 签到在 App/任务中心。") return False
def run(self): if not self.login(): return ok = self.sign_try_dsu_paulsign() print("[done]", "已尝试签到" if ok else "未完成签到")
if __name__ == "__main__": USERNAME = "你的用户名" PASSWORD = "你的密码"
VocBbsSigner(USERNAME, PASSWORD).run()