diff --git a/FnForum/README.md b/FnForum/README.md new file mode 100644 index 0000000..67d7d21 --- /dev/null +++ b/FnForum/README.md @@ -0,0 +1,106 @@ +# 飞牛论坛自动签到脚本 + +## 脚本功能 + +飞牛论坛自动签到脚本用于自动完成飞牛论坛的每日签到任务,支持以下功能: + +- 自动登录飞牛论坛账号 +- 自动识别验证码(通过百度OCR API) +- 每日自动签到 +- 签到结果通过通知功能发送 + +## 依赖要求 + +### 软件依赖 +- Python 3.6+ +- 以下Python库: + - requests + - beautifulsoup4 + +### 安装命令 +```bash +pip install requests beautifulsoup4 +``` + +## 配置方法 + +### 1. 环境变量配置 + +#### (1) 账号配置 - FN_CONFIG +```json +{ + "USERNAME": "你的飞牛论坛用户名", + "PASSWORD": "你的飞牛论坛密码", + "BASE_URL": "飞牛论坛基础URL(默认:https://club.fnnas.com/)" +} +``` + +#### (2) 百度OCR API配置 - BAIDU_API_CONFIG +```json +{ + "API_KEY": "你的百度OCR API Key", + "SECRET_KEY": "你的百度OCR Secret Key" +} +``` + +### 2. 百度OCR API申请 +1. 注册百度智能云账号:https://cloud.baidu.com +2. 创建文字识别应用,获取API Key和Secret Key +3. 将获取的密钥填入环境变量 + +## 使用说明 + +### 运行方式 +```bash +python3 sign_with_qinglong.py +``` + +### 定时任务示例 +```bash +0 8 * * * python3 sign_with_qinglong.py # 每天8点自动签到 +``` + +## 通知功能 + +签到结果将通过通知功能发送,示例内容: +``` +📅 签到日期: 2023-05-10 08:30:45 + +👤 账号: your_username +-------------------------------- +✅ 签到成功! + +📊 签到数据: + • 连续签到: 5天 + • 累计签到: 30天 + • 今日获得积分: 100 + +-------------------------------- +💻 脚本运行完成! +🚀 下次见! +``` + +## 常见问题 + +### 1. 验证码识别失败 +- 确认百度OCR API密钥正确 +- 检查验证码图片清晰度,可尝试更换OCR识别类型 +- 避免频繁请求触发API频率限制 + +### 2. 登录失败 +- 检查账号密码是否正确 +- 确认论坛登录页面结构是否变更 +- 尝试删除cookies.json后重新登录 + +### 3. 通知未收到 +- 确保通知模块路径正确 +- 手动测试通知功能:`python notify.py "测试通知" "测试内容"` + +## 免责协议 + +1. 本脚本仅供个人学习和研究使用,请勿用于商业用途或其他非法目的。 +2. 脚本运行可能涉及论坛账号登录和数据获取,请严格遵守飞牛论坛的用户协议和相关法律法规。 +3. 作者不对脚本的可用性、准确性负责,使用本脚本产生的一切后果由使用者自行承担。 +4. 如因使用本脚本导致账号异常、封禁或其他损失,作者不承担任何责任。 +5. 若论坛调整页面结构或策略导致脚本失效,作者无义务持续维护更新。 +6. 使用本脚本即视为同意上述条款,请勿在未经允许的情况下将脚本用于他人账号。 \ No newline at end of file diff --git a/FnForum/fn_signin.py b/FnForum/fn_signin.py new file mode 100644 index 0000000..b68c6dc --- /dev/null +++ b/FnForum/fn_signin.py @@ -0,0 +1,731 @@ +""" +任务名称 +name: 飞牛论坛签到 +定时规则 +cron: 0 0 8 * * ? +""" + +import os +import sys +import re +import json +import time +import requests +import base64 +import urllib.parse +from bs4 import BeautifulSoup +from datetime import datetime + +# 添加青龙脚本根目录到Python路径 +QL_SCRIPTS_DIR = '/ql/scripts' # 青龙脚本默认目录 +sys.path.append(QL_SCRIPTS_DIR) + +# 添加notify可能存在的其他路径 +POSSIBLE_PATHS = [ + '/ql', # 青龙根目录 + '/ql/data/scripts', # 新版青龙数据目录 + '/ql/scripts/notify', # 自定义通知目录 + os.path.dirname(__file__) # 当前脚本目录 +] + +for path in POSSIBLE_PATHS: + if os.path.exists(os.path.join(path, 'notify.py')): + sys.path.append(path) + break + +try: + from notify import send +except ImportError: + print("⚠️ 无法加载通知模块,请检查路径配置") + send = lambda title, content: None # 创建空函数防止报错 + +class ConfigLoader: + """从青龙环境变量加载配置""" + + @staticmethod + def get_env_config(): + """获取环境变量配置""" + config = {} + + # 从青龙环境变量获取配置 + fn_config = os.getenv('FN_CONFIG', '') + baidu_api_config = os.getenv('BAIDU_API_CONFIG', '') + + # 检查必要配置是否存在 + if not fn_config or not baidu_api_config: + print("错误: 缺少必要的配置 - FN_CONFIG 和 BAIDU_API_CONFIG") + return None + + # 解析配置 + try: + fn_config = json.loads(fn_config) + baidu_api_config = json.loads(baidu_api_config) + except json.JSONDecodeError as e: + print(f"配置解析失败: {e}") + return None + + # 检查必要字段 + required_fn_fields = ['USERNAME', 'PASSWORD', 'BASE_URL'] + required_baidu_fields = ['API_KEY', 'SECRET_KEY'] + + missing_fn_fields = [field for field in required_fn_fields if field not in fn_config] + missing_baidu_fields = [field for field in required_baidu_fields if field not in baidu_api_config] + + if missing_fn_fields or missing_baidu_fields: + missing_fields = missing_fn_fields + missing_baidu_fields + print(f"错误: 缺少必要的配置字段: {', '.join(missing_fields)}") + return None + + # 设置配置 + config.update(fn_config) + config.update(baidu_api_config) + + # 设置其他配置 + config['LOGIN_URL'] = config['BASE_URL'] + 'member.php?mod=logging&action=login' + config['SIGN_URL'] = config['BASE_URL'] + 'plugin.php?id=zqlj_sign' + config['COOKIE_FILE'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cookies.json') + config['CAPTCHA_API_URL'] = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic" + config['MAX_RETRIES'] = int(os.getenv('FN_MAX_RETRIES', 3)) + config['RETRY_DELAY'] = int(os.getenv('FN_RETRY_DELAY', 2)) + config['TOKEN_CACHE_FILE'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'token_cache.json') + + return config + +# 配置信息 +class Config: + # 从环境变量加载配置 + env_config = ConfigLoader.get_env_config() + + if env_config: + # 账号信息 + USERNAME = env_config['USERNAME'] + PASSWORD = env_config['PASSWORD'] + + # 网站URL + BASE_URL = env_config['BASE_URL'] + LOGIN_URL = env_config['LOGIN_URL'] + SIGN_URL = env_config['SIGN_URL'] + + # Cookie文件路径 + COOKIE_FILE = env_config['COOKIE_FILE'] + + # 验证码识别API (百度OCR API) + CAPTCHA_API_URL = env_config['CAPTCHA_API_URL'] + API_KEY = env_config['API_KEY'] + SECRET_KEY = env_config['SECRET_KEY'] + + # 重试设置 + MAX_RETRIES = env_config['MAX_RETRIES'] + RETRY_DELAY = env_config['RETRY_DELAY'] + + # Token缓存文件 + TOKEN_CACHE_FILE = env_config['TOKEN_CACHE_FILE'] + else: + # 如果缺少必要配置,设置默认值(将导致运行失败) + USERNAME = 'your_username' + PASSWORD = 'your_password' + API_KEY = 'your_api_key' + SECRET_KEY = 'your_secret_key' + BASE_URL = 'https://club.fnnas.com/' + +class FNSignIn: + def __init__(self): + self.session = requests.Session() + self.session.headers.update({ + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' + }) + self.load_cookies() + + def load_cookies(self): + """从文件加载Cookie""" + if os.path.exists(Config.COOKIE_FILE): + try: + with open(Config.COOKIE_FILE, 'r') as f: + cookies_list = json.load(f) + + # 检查是否为新格式的Cookie列表 + if isinstance(cookies_list, list) and len(cookies_list) > 0 and 'name' in cookies_list[0]: + # 新格式:包含完整Cookie属性的列表 + for cookie_dict in cookies_list: + self.session.cookies.set( + cookie_dict['name'], + cookie_dict['value'], + domain=cookie_dict.get('domain'), + path=cookie_dict.get('path') + ) + else: + # 旧格式:简单的名称-值字典 + self.session.cookies.update(cookies_list) + + print("已从文件加载Cookie") + return True + except Exception as e: + print(f"加载Cookie失败: {e}") + return False + + def save_cookies(self): + """保存Cookie到文件""" + try: + # 保存完整的Cookie信息,包括域名、路径等属性 + cookies_list = [] + for cookie in self.session.cookies: + cookie_dict = { + 'name': cookie.name, + 'value': cookie.value, + 'domain': cookie.domain, + 'path': cookie.path, + 'expires': cookie.expires, + 'secure': cookie.secure + } + cookies_list.append(cookie_dict) + + with open(Config.COOKIE_FILE, 'w') as f: + json.dump(cookies_list, f) + print("Cookie已保存到文件") + return True + except Exception as e: + print(f"保存Cookie失败: {e}") + return False + + def check_login_status(self): + """检查登录状态""" + try: + response = self.session.get(Config.BASE_URL) + soup = BeautifulSoup(response.text, 'html.parser') + + # 检查是否存在登录链接,如果存在则表示未登录 + login_links = soup.select('a[href*="member.php?mod=logging&action=login"]') + + # 检查页面内容是否包含用户名 + username_in_page = Config.USERNAME in response.text + + # 检查是否有个人中心链接 + user_center_links = soup.select('a[href*="home.php?mod=space"]') + + # 如果没有登录链接或者页面中包含用户名,则认为已登录 + if (len(login_links) == 0 or username_in_page) and len(user_center_links) > 0: + print("Cookie有效,已登录状态") + return True + else: + print("Cookie无效或已过期,需要重新登录") + return False + except Exception as e: + print(f"检查登录状态失败: {e}") + return False + + def get_access_token(self): + """获取百度API的access_token,带缓存功能""" + try: + # 检查是否有缓存的token + if os.path.exists(Config.TOKEN_CACHE_FILE): + try: + with open(Config.TOKEN_CACHE_FILE, 'r') as f: + token_data = json.load(f) + # 检查token是否过期(百度token有效期为30天) + if token_data.get('expires_time', 0) > time.time(): + print("使用缓存的access_token") + return token_data.get('access_token') + else: + print("缓存的access_token已过期,重新获取") + except Exception as e: + print(f"读取token缓存文件失败: {e}") + + # 获取新token + url = "https://aip.baidubce.com/oauth/2.0/token" + params = { + "grant_type": "client_credentials", + "client_id": Config.API_KEY, + "client_secret": Config.SECRET_KEY + } + + # 添加重试机制 + for retry in range(Config.MAX_RETRIES): + try: + response = requests.post(url, params=params) + if response.status_code == 200: + result = response.json() + access_token = str(result.get("access_token")) + expires_in = result.get("expires_in", 2592000) # 默认30天 + + # 缓存token + token_cache = { + 'access_token': access_token, + 'expires_time': time.time() + expires_in - 86400 # 提前一天过期 + } + try: + with open(Config.TOKEN_CACHE_FILE, 'w') as f: + json.dump(token_cache, f) + print("access_token已缓存") + except Exception as e: + print(f"缓存access_token失败: {e}") + + return access_token + else: + print(f"获取access_token失败,状态码: {response.status_code},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + except Exception as e: + print(f"获取access_token请求异常: {e},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + + print(f"获取access_token失败,已达到最大重试次数({Config.MAX_RETRIES})") + return None + except Exception as e: + print(f"获取access_token过程发生错误: {e}") + return None + + def recognize_captcha(self, captcha_url): + """识别验证码,带重试机制""" + for retry in range(Config.MAX_RETRIES): + try: + # 下载验证码图片 + captcha_response = self.session.get(captcha_url) + if captcha_response.status_code != 200: + print(f"下载验证码图片失败,状态码: {captcha_response.status_code},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return None + + # 将图片转换为Base64编码 + captcha_base64 = base64.b64encode(captcha_response.content).decode('utf-8') + + # 获取access_token + access_token = self.get_access_token() + if not access_token: + print(f"获取百度API access_token失败,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return None + + # 构建API请求URL + url = f"{Config.CAPTCHA_API_URL}?access_token={access_token}" + + # 构建请求参数 + payload = f'image={urllib.parse.quote_plus(captcha_base64)}&detect_direction=false¶graph=false&probability=false' + + # 设置请求头 + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json' + } + + # 发送请求 + api_response = requests.request("POST", url, headers=headers, data=payload.encode("utf-8")) + + if api_response.status_code != 200: + print(f"验证码识别API请求失败,状态码: {api_response.status_code},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return None + + # 解析API响应 + result = api_response.json() + if 'words_result' in result and len(result['words_result']) > 0: + captcha_text = result['words_result'][0]['words'] + # 清理验证码文本,移除空格和特殊字符 + captcha_text = re.sub(r'[\s\W]+', '', captcha_text) + print(f"验证码识别成功: {captcha_text}") + return captcha_text + elif 'error_code' in result: + print(f"验证码识别API返回错误: {result.get('error_code')}, {result.get('error_msg')},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return None + else: + print(f"验证码识别API返回格式异常: {result},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return None + except Exception as e: + print(f"验证码识别过程发生错误: {e},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return None + + print(f"验证码识别失败,已达到最大重试次数({Config.MAX_RETRIES})") + return None + + def login(self): + """使用账号密码登录,带重试机制""" + for retry in range(Config.MAX_RETRIES): + try: + # 获取登录页面 + response = self.session.get(Config.LOGIN_URL) + soup = BeautifulSoup(response.text, 'html.parser') + + # 获取登录表单信息 + login_form = None + for form in soup.find_all('form'): + form_id = form.get('id', '') + if form_id and ('loginform' in form_id or 'lsform' in form_id): + login_form = form + break + elif form.get('name') == 'login': + login_form = form + break + elif form.get('action') and 'logging' in form.get('action'): + login_form = form + break + + if not login_form: + # 尝试查找任何表单,可能是登录表单 + all_forms = soup.find_all('form') + if all_forms: + login_form = all_forms[0] # 使用第一个表单 + print(f"使用备选表单: ID={login_form.get('id')}, Action={login_form.get('action')}") + + if not login_form: + print(f"未找到登录表单,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + + # 提取登录表单ID中的随机部分 + form_id = login_form.get('id', '') + login_hash = form_id.split('_')[-1] if '_' in form_id else '' + + # 获取登录表单的action属性 + form_action = login_form.get('action', '') + print(f"找到登录表单: ID={form_id}, Action={form_action}") + + # 获取表单字段 + formhash = soup.find('input', {'name': 'formhash'}) + if not formhash: + print(f"未找到登录表单的formhash字段,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + + # 获取表单字段 + formhash = formhash['value'] + + # 获取用户名输入框ID + username_input = soup.find('input', {'name': 'username'}) + username_id = username_input.get('id', '') if username_input else '' + + # 获取密码输入框ID + password_input = soup.find('input', {'name': 'password'}) + password_id = password_input.get('id', '') if password_input else '' + + print(f"找到用户名输入框ID: {username_id}") + print(f"找到密码输入框ID: {password_id}") + + # 构建登录数据 + login_data = { + 'formhash': formhash, + 'referer': Config.BASE_URL, + 'loginfield': 'username', + 'username': Config.USERNAME, + 'password': Config.PASSWORD, + 'questionid': '0', + 'answer': '', + 'cookietime': '2592000', # 保持登录状态30天 + 'loginsubmit': 'true' + } + + # 添加特定的表单字段 + if username_id: + login_data[username_id] = Config.USERNAME + if password_id: + login_data[password_id] = Config.PASSWORD + + # 检查是否需要验证码 + seccodeverify = soup.find('input', {'name': 'seccodeverify'}) + if seccodeverify: + print("检测到需要验证码,尝试自动识别验证码") + + # 获取验证码ID + seccode_id = seccodeverify.get('id', '').replace('seccodeverify_', '') + + # 获取验证码图片URL + captcha_img = soup.find('img', {'src': re.compile(r'misc\.php\?mod=seccode')}) + if not captcha_img: + print(f"未找到验证码图片,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + + captcha_url = Config.BASE_URL + captcha_img['src'] + print(f"验证码图片URL: {captcha_url}") + + # 识别验证码 + captcha_text = self.recognize_captcha(captcha_url) + if not captcha_text: + print(f"验证码识别失败,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + + # 添加验证码到登录数据 + login_data['seccodeverify'] = captcha_text + login_data['seccodehash'] = seccode_id + + # 更新请求头,模拟真实浏览器 + self.session.headers.update({ + 'Origin': Config.BASE_URL.rstrip('/'), + 'Referer': Config.LOGIN_URL, + 'Content-Type': 'application/x-www-form-urlencoded', + 'Upgrade-Insecure-Requests': '1' + }) + + # 构建登录URL + login_url = f"{Config.LOGIN_URL}&loginsubmit=yes&inajax=1" + + # 发送登录请求 + login_response = self.session.post(login_url, data=login_data, allow_redirects=True) + + # 检查登录结果 + if '验证码' in login_response.text and '验证码错误' in login_response.text: + print(f"验证码错误,登录失败,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + + # 检查登录是否成功 + if 'succeedhandle_' in login_response.text or self.check_login_status(): + print(f"账号 {Config.USERNAME} 登录成功") + self.save_cookies() + return True + else: + print(f"登录失败,请检查账号密码,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + except Exception as e: + print(f"登录过程发生错误: {e},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + + print(f"登录失败,已达到最大重试次数({Config.MAX_RETRIES})") + return False + + def check_sign_status(self): + """检查签到状态,带重试机制""" + for retry in range(Config.MAX_RETRIES): + try: + response = self.session.get(Config.SIGN_URL) + soup = BeautifulSoup(response.text, 'html.parser') + + # 查找签到按钮 + sign_btn = soup.select_one('.signbtn .btna') + if not sign_btn: + print(f"未找到签到按钮,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return None, None + + # 获取签到链接和状态 + sign_text = sign_btn.text.strip() + sign_link = sign_btn.get('href') + + # 提取sign参数 + sign_param = None + if sign_link: + match = re.search(r'sign=([^&]+)', sign_link) + if match: + sign_param = match.group(1) + + return sign_text, sign_param + except Exception as e: + print(f"检查签到状态失败: {e},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return None, None + + def do_sign(self, sign_param): + """执行签到,带重试机制""" + for retry in range(Config.MAX_RETRIES): + try: + sign_url = f"{Config.SIGN_URL}&sign={sign_param}" + response = self.session.get(sign_url) + + # 检查签到结果 + if response.status_code == 200: + # 再次检查签到状态 + sign_text, _ = self.check_sign_status() + if sign_text == "今日已打卡": + print("签到成功") + return True + else: + print(f"签到请求已发送,但状态未更新,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + else: + print(f"签到请求失败,状态码: {response.status_code},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + except Exception as e: + print(f"签到过程发生错误: {e},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return False + + def get_sign_info(self): + """获取签到信息,带重试机制""" + for retry in range(Config.MAX_RETRIES): + try: + response = self.session.get(Config.SIGN_URL) + soup = BeautifulSoup(response.text, 'html.parser') + + # 查找签到信息区域 + sign_info_divs = soup.find_all('div', class_='bm') + sign_info_div = None + for div in sign_info_divs: + header = div.find('div', class_='bm_h') + if header and '我的打卡动态' in header.get_text(): + sign_info_div = div + break + + if not sign_info_div: + print(f"未找到签到信息区域,重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return {} + + # 查找签到信息列表 + info_list = sign_info_div.find('div', class_='bm_c').find_all('li') + + # 解析签到信息 + sign_info = {} + for item in info_list: + text = item.get_text(strip=True) + if ':' in text: + key, value = text.split(':', 1) + sign_info[key] = value + + return sign_info + except Exception as e: + print(f"获取签到信息失败: {e},重试({retry+1}/{Config.MAX_RETRIES})") + if retry < Config.MAX_RETRIES - 1: + time.sleep(Config.RETRY_DELAY) + continue + return {} + + print(f"获取签到信息失败,已达到最大重试次数({Config.MAX_RETRIES})") + return {} + + def run(self): + """运行签到流程,带重试机制""" + print("===== 开始运行签到脚本 =====") + + # 检查登录状态 + if not self.check_login_status(): + # 如果未登录,尝试登录 + if not self.login(): + print("登录失败,签到流程终止") + return False, "登录失败" + + # 检查签到状态 + sign_text, sign_param = self.check_sign_status() + if sign_text is None or sign_param is None: + print("获取签到状态失败,签到流程终止") + return False, "获取签到状态失败" + + print(f"当前签到状态: {sign_text}") + + # 初始化通知内容 + notify_title = f"飞牛论坛签到通知" + notify_content = f"📅 签到日期: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + notify_content += f"👤 账号: {Config.USERNAME}\n" + notify_content += "--------------------------------\n" + + # 如果未签到,执行签到 + if sign_text == "点击打卡": + print("开始执行签到...") + if self.do_sign(sign_param): + # 获取并记录签到信息 + sign_info = self.get_sign_info() + if sign_info: + print("===== 签到信息 =====") + notify_content += "✅ 签到成功!\n\n" + notify_content += "📊 签到数据:\n" + for key, value in sign_info.items(): + print(f"{key}: {value}") + notify_content += f" • {key}: {value}\n" + else: + notify_content += "✅ 签到成功!\n" + notify_content += " • 未能获取详细签到数据\n" + else: + notify_content += "❌ 签到失败!\n" + notify_content += " • 请检查日志查看详细原因\n" + print("签到失败") + # 发送通知 + send(notify_title, notify_content) + return False, "签到失败" + elif sign_text == "今日已打卡": + print("今日已签到,无需重复签到") + # 获取并记录签到信息 + sign_info = self.get_sign_info() + if sign_info: + print("===== 签到信息 =====") + notify_content += "✅ 今日已签到!\n\n" + notify_content += "📊 签到数据:\n" + for key, value in sign_info.items(): + print(f"{key}: {value}") + notify_content += f" • {key}: {value}\n" + else: + notify_content += "✅ 今日已签到!\n" + notify_content += " • 未能获取详细签到数据\n" + else: + notify_content += f"⚠️ 未知的签到状态: {sign_text},签到流程终止!\n" + print(f"未知的签到状态: {sign_text},签到流程终止") + # 发送通知 + send(notify_title, notify_content) + return False, f"未知的签到状态: {sign_text}" + + # 添加底部信息 + notify_content += "\n--------------------------------\n" + notify_content += "💻 脚本运行完成!\n" + notify_content += "🚀 下次见!" + + # 发送通知 + send(notify_title, notify_content) + + return True, "签到成功" if sign_text == "点击打卡" else "今日已签到" + + +if __name__ == "__main__": + try: + # 创建签到实例并运行 + sign = FNSignIn() + result, message = sign.run() + + # 输出最终结果 + if result: + print("===== 签到脚本执行成功 =====") + else: + print("===== 签到脚本执行失败 =====") + except KeyboardInterrupt: + print("脚本被用户中断") + except Exception as e: + print(f"脚本运行出错: {e}") + # 输出详细的异常堆栈信息 + import traceback + print(traceback.format_exc()) \ No newline at end of file