# Source: https://github.com/LinYuanovo/AutoTaskScripts/blob/main/web/%E5%B0%9A%E9%A6%99%E4%B9%A6%E8%8B%91.py # Raw: https://raw.githubusercontent.com/LinYuanovo/AutoTaskScripts/main/web/%E5%B0%9A%E9%A6%99%E4%B9%A6%E8%8B%91.py # Repo: LinYuanovo/AutoTaskScripts # Path: web/尚香书苑.py # UploadedAt: 2025-08-27T15:31:08+08:00 # SHA256: d239178748199491eac1ccb8d3adeb971977e5caabe96f2f633de43739e492e2 # Category: web版/账密 # Evidence: web/H5关键词 + username/password/login """ 作者: 临渊 日期: 2025/6/8 name: 尚香书苑 入口: 网站 (https://sxsy19.com/) 功能: 登录、签到 变量: sxsy='邮箱&密码' 或者 'cookie' 自动检测,多个账号用换行分割 使用邮箱密码将会进行登录(必须有ocr服务地址) 使用cookie将会直接使用 定时: 一天两次 cron: 10 9,10 * * * ------------更新日志------------ 2025/6/8 V1.0 初始化,完成签到功能 2025/6/11 V1.1 变量增加邮箱密码支持 2025/6/17 V1.2 增加cookie存储功能 2025/7/28 V1.3 修改头部注释,以便拉库 2025/8/27 V1.4 修改默认域名 """ DEFAULT_HOST = "sxsy21.com" # 默认域名 import requests import os import re import urllib.parse import logging import traceback import base64 import random import time import json from datetime import datetime DDDD_OCR_URL = os.getenv("DDDD_OCR_URL") or "" # dddd_ocr地址 class AutoTask: def __init__(self, site_name): """ 初始化自动任务类 :param site_name: 站点名称,用于日志显示 """ self.site_name = site_name self.cookie_file = f"{site_name}_cookie.json" self.setup_logging() def setup_logging(self): """ 配置日志系统 """ logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s\t- %(message)s', datefmt='%Y-%m-%d %H:%M:%S', handlers=[ # logging.FileHandler(f'{self.site_name}_{datetime.now().strftime("%Y%m%d")}.log', encoding='utf-8'), # 保存日志 logging.StreamHandler() ] ) def check_env(self): """ 检查环境变量 :return: 邮箱和密码,或者cookie """ try: env = os.getenv("sxsy") if not env: logging.error("[检查环境变量]没有找到环境变量sxsy") return # 多个账号用换行分割 envs = env.split('\n') for env in envs: if '&' in env: # 解析cookie字符串,提取邮箱和密码 email, password = env.split('&') yield email, password, None else: # 直接使用cookie yield None, None, env except Exception as e: logging.error(f"[检查环境变量]发生错误: {str(e)}\n{traceback.format_exc()}") raise def get_host(self): """ 获取host :return: host """ try: # 访问发布页 url = "https://sxsy.org/" payload = {} headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': 'sxsy.org' } response = requests.request("GET", url, headers=headers, data=payload) response.raise_for_status() # 检查响应状态 # 使用正则表达式匹配host pattern = r'href="https://(.*?)/' match = re.search(pattern, response.text) if match: host = match.group(1) logging.info(f"[获取host]{host}") return host logging.warning("[获取host]无法获取host,使用默认域名") return DEFAULT_HOST # 如果无法获取,返回默认域名 except requests.RequestException as e: logging.warning(f"[获取host]发生网络错误,使用默认域名") return DEFAULT_HOST except Exception as e: logging.error(f"[获取host]发生未知错误: {str(e)}\n{traceback.format_exc()}") return DEFAULT_HOST def get_param(self, host, session): """ 获取参数 :param host: 域名 :param session: 会话对象 :return: formhash, seccodehash, loginhash """ try: # 访问首页 url = f"https://{host}/member.php?mod=logging&action=login&infloat=yes&frommessage&inajax=1&ajaxtarget=messagelogin" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host } response = session.get(url, headers=headers) response.raise_for_status() # 使用正则表达式匹配 # formhash 格式: name="formhash" value="5448b1bc" pattern = r'name="formhash" value="([a-zA-Z0-9]{8})"' match = re.search(pattern, response.text) if match: formhash = match.group(1) else: logging.error("[获取formhash]无法获取formhash") return None, None, None # seccodehash 格式: seccode_cSAbDg cSAbDg pattern = r'seccode_([a-zA-Z0-9]{6})' match = re.search(pattern, response.text) if match: seccodehash = match.group(1) else: logging.error("[获取seccodehash]无法获取seccodehash") return None, None, None # loginhash 格式: main_messaqge_LCpo4 LCpo4 pattern = r'main_messaqge_([a-zA-Z0-9]{5})' match = re.search(pattern, response.text) if match: loginhash = match.group(1) else: logging.error("[获取loginhash]无法获取loginhash") return None, None, None return formhash, seccodehash, loginhash except requests.RequestException as e: logging.warning(f"[获取参数]发生网络错误: {str(e)}\n{traceback.format_exc()}") return None, None, None except Exception as e: logging.error(f"[获取参数]发生未知错误: {str(e)}\n{traceback.format_exc()}") return None, None, None def get_captcha_img(self, host, seccodehash, session): """ 获取验证码图片 :param host: 域名 :param seccodehash: seccodehash :param session: 会话对象 :return: 验证码图片 """ try: url = f"https://{host}/misc.php?mod=seccode&update={random.randint(10000, 99999)}&idhash={seccodehash}" headers = { 'referer': f'https://{host}/member.php?mod=logging&action=login', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host } response = session.get(url, headers=headers) # 图片转为base64 img_base64 = base64.b64encode(response.content).decode('utf-8') return img_base64 except Exception as e: logging.error(f"[获取验证码图片]发生未知错误: {str(e)}\n{traceback.format_exc()}") return None def get_captcha_text(self, img_base64): """ 获取验证码文字 :param img_base64: 验证码base64 :return: 验证码文字 """ try: url = DDDD_OCR_URL payload = { 'image': img_base64 } response = requests.post(url, data=payload).json() if response['code'] == 200: return response['data'] else: logging.error(f"[获取验证码]发生错误: {response['message']}") return None except Exception as e: logging.error(f"[获取验证码]发生错误: {str(e)}\n{traceback.format_exc()}") return None def check_captcha(self, host, captcha, session, seccodehash): """ 检查验证码 :param host: 域名 :param captcha: 验证码文字 :param session: 会话对象 :param seccodehash: seccodehash :return: 是否正确 """ try: url = f"https://{host}/misc.php?mod=seccode&action=check&inajax=1&modid=member::logging&idhash={seccodehash}&secverify={captcha}" headers = { 'referer': f'https://{host}/member.php?mod=logging&action=login', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host } response = session.get(url, headers=headers) response.raise_for_status() pattern = r'' match = re.search(pattern, response.text) if match: text = match.group(1) if "succeed" in text: return True else: return False else: logging.warning("[检查验证码]响应格式异常") return False except requests.RequestException as e: logging.error(f"[检查验证码]发生网络错误: {str(e)}\n{traceback.format_exc()}") return False except Exception as e: logging.error(f"[检查验证码]发生未知错误: {str(e)}\n{traceback.format_exc()}") return False def login_in(self, host, username, password, formhash, captcha, session, loginhash, seccodehash): """ 登录 :param host: 域名 :param username: 邮箱 :param password: 密码 :param formhash: formhash :param captcha: 验证码文字 :param session: 会话对象 :param loginhash: loginhash :param seccodehash: seccodehash :return: 是否成功 """ try: url = f"https://{host}/member.php?mod=logging&action=login&loginsubmit=yes&loginhash={loginhash}&inajax=1" payload = f"formhash={formhash}&referer=https://{host}/home.php?mod=spacecp&ac=credit&showcredit=1&loginfield=email&username={username}&password={password}&questionid=0&answer=&seccodehash={seccodehash}&seccodemodid=member::logging&seccodeverify={captcha}&cookietime=2592000" headers = { 'Referer': f'https://{host}/home.php?mod=spacecp&ac=credit&showcredit=1', 'content-type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host } # payload转为url编码,/替换为%2F payload = urllib.parse.quote(payload, safe='=&') response = session.post(url, headers=headers, data=payload) response.raise_for_status() pattern = r'' match = re.search(pattern, response.text) if match: text = match.group(1) if "欢迎您回来" in text: # 匹配 username_pattern = r'欢迎您回来,(.*?),现在将转入登录前页面' username_match = re.search(username_pattern, text) if username_match: matched_username = username_match.group(1) logging.info(f"[登录]成功,当前账号: {matched_username}") return True else: logging.warning("[登录]登录失败") return False else: logging.warning("[登录]响应格式异常") return False except requests.RequestException as e: logging.error(f"[登录]发生网络错误: {str(e)}\n{traceback.format_exc()}") return False except Exception as e: logging.error(f"[登录]发生未知错误: {str(e)}\n{traceback.format_exc()}") return False def get_sign_hash(self, host, session): """ 获取签到hash :param host: 域名 :param session: 会话对象 :return: 签到hash """ try: url = f"https://{host}/plugin.php?id=k_misign:sign" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host } response = session.get(url, headers=headers) response.raise_for_status() pattern = r'formhash=([a-zA-Z0-9]{8})' match = re.search(pattern, response.text) if match: formhash = match.group(1) return formhash else: logging.warning("[获取签到hash]无法获取签到hash") return None except requests.RequestException as e: logging.error(f"[获取签到hash]发生网络错误: {str(e)}\n{traceback.format_exc()}") return None def signin(self, host, session, sign_hash): """ 签到 :param host: 域名 :param session: 会话对象 :param sign_hash: 签到hash """ try: if not sign_hash: logging.error("sign_hash为空,无法进行签到") return url = f"https://{host}/plugin.php?id=k_misign:sign&operation=qiandao&format=global_usernav_extra&formhash={sign_hash}&inajax=1&ajaxtarget=k_misign_topb" payload = {} headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host } response = session.get(url, headers=headers) response.raise_for_status() # 使用正则表达式匹配CDATA中的内容 pattern = r'' match = re.search(pattern, response.text) if match: text = match.group(1) logging.info(f"[签到]{text}") else: logging.warning("[签到]响应格式异常") except requests.RequestException as e: logging.error(f"[签到]发生网络错误: {str(e)}\n{traceback.format_exc()}") except Exception as e: logging.error(f"[签到]发生未知错误: {str(e)}\n{traceback.format_exc()}") def get_user_info(self, host, session, print_info=False): """ 获取用户信息 :param host: 域名 :param session: 会话对象 :param print_info: 是否打印信息 :return: uid """ try: url = f"https://{host}/home.php?mod=spacecp&ac=credit&showcredit=1" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host } response = session.get(url, headers=headers) response.raise_for_status() # 使用正则表达式匹配金钱数量 money_pattern = r'金钱: (\d+)' match = re.search(money_pattern, response.text) uid_pattern = r'uid=(\d+)' uid_match = re.search(uid_pattern, response.text) if match: money = match.group(1) uid = uid_match.group(1) if print_info: logging.info(f"[用户{uid}]现有金钱 {money}") return uid logging.warning("[获取用户信息]无法获取用户金钱信息") return None except requests.RequestException as e: logging.error(f"[获取用户信息]发生网络错误: {str(e)}\n{traceback.format_exc()}") return None except Exception as e: logging.error(f"[获取用户信息]发生未知错误: {str(e)}\n{traceback.format_exc()}") return None def get_promotion_reward(self, host, uid): """ 获取推广奖励 :param host: 域名 :param uid: uid """ try: url = f"https://{host}/?fromuid={uid}" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host, 'Referer': f'https://{host}/fromuid={uid}' } # 不带cookie直接访问 response = requests.get(url, headers=headers) response.raise_for_status() except requests.RequestException as e: logging.error(f"[获取推广奖励]发生网络错误: {str(e)}\n{traceback.format_exc()}") except Exception as e: logging.error(f"[获取推广奖励]发生未知错误: {str(e)}\n{traceback.format_exc()}") def do_task(self, host, session): """ 执行任务 :param host: 域名 :param session: 会话对象 """ try: sign_hash = self.get_sign_hash(host, session) if sign_hash: self.signin(host, session, sign_hash) uid = self.get_user_info(host, session) self.get_promotion_reward(host, uid) self.get_user_info(host, session, print_info=True) except Exception as e: logging.error(f"[执行任务]发生未知错误: {str(e)}\n{traceback.format_exc()}") def read_cookie_file(self): """ 读取cookie文件 :return: cookie字符串或None """ try: if os.path.exists(self.cookie_file): with open(self.cookie_file, 'r', encoding='utf-8') as f: cookie_data = json.load(f) if cookie_data.get('accounts'): logging.info(f"[读取Cookie文件]成功读取{self.cookie_file}") return cookie_data['accounts'] return None except Exception as e: logging.error(f"[读取Cookie文件]发生错误: {str(e)}\n{traceback.format_exc()}") return None def write_cookie_file(self, cookies, email=None): """ 写入cookie文件 :param cookies: cookie字符串 :param email: 账号邮箱,用于标识不同账号 """ try: # 读取现有cookie文件 existing_data = {} if os.path.exists(self.cookie_file): with open(self.cookie_file, 'r', encoding='utf-8') as f: existing_data = json.load(f) # 准备新的cookie数据 cookie_data = { 'site_name': self.site_name, 'host': DEFAULT_HOST, 'accounts': existing_data.get('accounts', {}), 'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } # 更新或添加账号cookie if email: cookie_data['accounts'][email] = { 'cookies': cookies, 'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } else: # 如果没有提供email,使用默认键 cookie_data['accounts']['default'] = { 'cookies': cookies, 'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } with open(self.cookie_file, 'w', encoding='utf-8') as f: json.dump(cookie_data, f, ensure_ascii=False, indent=2) logging.info(f"[写入Cookie文件]成功写入{self.cookie_file}") except Exception as e: logging.error(f"[写入Cookie文件]发生错误: {str(e)}\n{traceback.format_exc()}") def get_session_cookies(self, session): """ 获取session的cookies字符串 :param session: 会话对象 :return: cookie字符串 """ try: cookies = [] for cookie in session.cookies: cookies.append(f"{cookie.name}={cookie.value}") return '; '.join(cookies) except Exception as e: logging.error(f"[获取Session Cookies]发生错误: {str(e)}\n{traceback.format_exc()}") return None def check_cookie_valid(self, host, session): """ 检查cookie是否有效 :param host: 域名 :param session: 会话对象 :return: 是否有效 """ try: url = f"https://{host}/home.php?mod=space" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 'Host': host } response = session.get(url, headers=headers) response.raise_for_status() if "请先登录" in response.text: return False return True except Exception as e: logging.error(f"[Cookie检测]发生错误: {str(e)}\n{traceback.format_exc()}") return False def run(self): """ 执行签到任务的主函数 """ try: logging.info(f"【{self.site_name}】开始执行签到任务") # 首先尝试读取cookie文件 accounts = self.read_cookie_file() if accounts: logging.info("[Cookie文件]检测到cookie文件,将尝试使用") for email, account_data in accounts.items(): session = requests.Session() for cookie_item in account_data['cookies'].split(';'): key, value = cookie_item.split('=', 1) session.cookies.set(key.strip(), value.strip()) # 检查cookie是否有效 if self.check_cookie_valid(DEFAULT_HOST, session): logging.info("") logging.info(f"[Cookie检测]账号 {email} 的Cookie有效") # 执行签到任务 self.do_task(DEFAULT_HOST, session) logging.info("") # 如果是最后一个账号,执行return if email == list(accounts.keys())[-1]: return else: logging.warning(f"[Cookie文件]账号 {email} 的Cookie已失效") logging.info("[Cookie文件]所有账号的Cookie都已失效,尝试使用邮箱密码登录") # 检查环境变量中是否有邮箱密码 env = os.getenv("sxsy") if not env or '&' not in env: logging.error("[Cookie文件]所有Cookie已失效且环境变量中未找到邮箱密码,无法继续") return # 删除失效的cookie文件 try: os.remove(self.cookie_file) logging.info(f"[Cookie文件]已删除失效的cookie文件: {self.cookie_file}") except Exception as e: logging.error(f"[Cookie文件]删除失效cookie文件失败: {str(e)}") host = self.get_host() for index, (email, password, cookie) in enumerate(self.check_env(), 1): logging.info("") logging.info(f"------【账号{index}】开始执行任务------") # 创建会话 session = requests.Session() if cookie: # 直接使用cookie logging.info(f"[检查环境变量]检测到cookie,将直接使用并保存到文件") for cookie_item in cookie.split(';'): key, value = cookie_item.split('=', 1) session.cookies.set(key.strip(), value.strip()) self.write_cookie_file(cookie, email) # 检查cookie是否有效 if self.check_cookie_valid(host, session): logging.info(f"[Cookie]账号 {email} 的Cookie有效") # 执行签到任务 self.do_task(host, session) return else: logging.warning(f"[Cookie]账号 {email} 的Cookie已失效") continue else: logging.info(f"[检查环境变量]检测到邮箱密码,将进行登录") # 获取参数 formhash, seccodehash, loginhash = self.get_param(host, session) if not all([formhash, seccodehash, loginhash]): logging.error("获取参数失败,跳过当前账号") continue # 验证码重试逻辑 max_retries = 3 retry_count = 0 while retry_count < max_retries: login_in_captcha = self.get_captcha_img(host, seccodehash, session) login_in_captcha_text = self.get_captcha_text(login_in_captcha) if self.check_captcha(host, login_in_captcha_text, session, seccodehash): break retry_count += 1 if retry_count < max_retries: logging.warning(f"[验证码]验证失败,第{retry_count}次重试") time.sleep(5) else: logging.error("[验证码]验证失败,已达到最大重试次数") continue if not self.login_in(host, email, password, formhash, login_in_captcha_text, session, loginhash, seccodehash): logging.error("登录失败,跳过当前账号") continue # 登录成功后保存cookie到文件 cookies = self.get_session_cookies(session) if cookies: self.write_cookie_file(cookies, email) # 登录成功后执行签到任务 self.do_task(host, session) logging.info(f"------【账号{index}】执行任务完成------") logging.info("") except Exception as e: logging.error(f"【{self.site_name}】执行过程中发生错误: {str(e)}\n{traceback.format_exc()}") if __name__ == "__main__": auto_task = AutoTask("尚香书苑") auto_task.run()