498 lines
18 KiB
Python
Executable File
498 lines
18 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
小红书Cookie监控器
|
||
监控Cookie有效期,通过QQBot发送告警
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import json
|
||
import time
|
||
from datetime import datetime, timedelta
|
||
import subprocess
|
||
import sqlite3
|
||
|
||
class XiaohongshuCookieMonitor:
|
||
"""小红书Cookie监控器"""
|
||
|
||
def __init__(self):
|
||
self.cookie_file = os.path.expanduser("~/.xiaohongshu-cli/cookies.json")
|
||
self.monitor_db = "/home/ubuntu/.nanobot/workspace/xiaohongshu_monitor.db"
|
||
self.qqbot_channel = "qq"
|
||
self.qqbot_chat_id = "FAB5DE2C5FD0EE22CC0350B40094C646"
|
||
|
||
# 初始化数据库
|
||
self._init_database()
|
||
|
||
def _init_database(self):
|
||
"""初始化监控数据库"""
|
||
conn = sqlite3.connect(self.monitor_db)
|
||
cursor = conn.cursor()
|
||
|
||
# 创建监控记录表
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS cookie_monitor (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
check_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
cookie_valid BOOLEAN,
|
||
user_id TEXT,
|
||
username TEXT,
|
||
nickname TEXT,
|
||
cookie_count INTEGER,
|
||
error_message TEXT,
|
||
notified BOOLEAN DEFAULT 0,
|
||
notification_time TIMESTAMP
|
||
)
|
||
''')
|
||
|
||
# 创建告警记录表
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS alerts (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
alert_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
alert_type TEXT,
|
||
alert_message TEXT,
|
||
cookie_status TEXT,
|
||
sent_to_qqbot BOOLEAN DEFAULT 0,
|
||
qqbot_response TEXT
|
||
)
|
||
''')
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
def check_cookie_validity(self):
|
||
"""检查Cookie有效性"""
|
||
print(f"🔍 检查Cookie有效性: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||
|
||
if not os.path.exists(self.cookie_file):
|
||
self._record_check_result(False, error_message="Cookie文件不存在")
|
||
return False
|
||
|
||
try:
|
||
# 使用xhs命令检查登录状态
|
||
result = subprocess.run(
|
||
['xhs', 'status', '--json'],
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=10
|
||
)
|
||
|
||
if result.returncode == 0:
|
||
data = json.loads(result.stdout)
|
||
if data.get('ok') and data.get('data', {}).get('authenticated'):
|
||
user = data['data']['user']
|
||
|
||
# 读取Cookie文件获取详细信息
|
||
with open(self.cookie_file, 'r', encoding='utf-8') as f:
|
||
cookies = json.load(f)
|
||
|
||
self._record_check_result(
|
||
True,
|
||
user_id=user.get('id'),
|
||
username=user.get('username'),
|
||
nickname=user.get('nickname'),
|
||
cookie_count=len(cookies)
|
||
)
|
||
|
||
print(f"✅ Cookie有效")
|
||
print(f" 用户ID: {user.get('id')}")
|
||
print(f" 用户名: {user.get('username')}")
|
||
print(f" 昵称: {user.get('nickname')}")
|
||
print(f" Cookie数量: {len(cookies)}")
|
||
|
||
return True
|
||
else:
|
||
error_msg = f"Cookie无效: {data}"
|
||
self._record_check_result(False, error_message=error_msg)
|
||
print(f"❌ {error_msg}")
|
||
return False
|
||
else:
|
||
error_msg = f"检查失败: {result.stderr[:100]}"
|
||
self._record_check_result(False, error_message=error_msg)
|
||
print(f"❌ {error_msg}")
|
||
return False
|
||
|
||
except subprocess.TimeoutExpired:
|
||
error_msg = "检查超时"
|
||
self._record_check_result(False, error_message=error_msg)
|
||
print(f"⏰ {error_msg}")
|
||
return False
|
||
except Exception as e:
|
||
error_msg = f"检查异常: {str(e)}"
|
||
self._record_check_result(False, error_message=error_msg)
|
||
print(f"❌ {error_msg}")
|
||
return False
|
||
|
||
def _record_check_result(self, valid, **kwargs):
|
||
"""记录检查结果到数据库"""
|
||
conn = sqlite3.connect(self.monitor_db)
|
||
cursor = conn.cursor()
|
||
|
||
cursor.execute('''
|
||
INSERT INTO cookie_monitor
|
||
(check_time, cookie_valid, user_id, username, nickname, cookie_count, error_message)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||
''', (
|
||
datetime.now().isoformat(),
|
||
valid,
|
||
kwargs.get('user_id'),
|
||
kwargs.get('username'),
|
||
kwargs.get('nickname'),
|
||
kwargs.get('cookie_count'),
|
||
kwargs.get('error_message')
|
||
))
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
def analyze_cookie_health(self):
|
||
"""分析Cookie健康状况"""
|
||
print("\n📊 分析Cookie健康状况...")
|
||
|
||
conn = sqlite3.connect(self.monitor_db)
|
||
cursor = conn.cursor()
|
||
|
||
# 获取最近24小时的检查记录
|
||
cursor.execute('''
|
||
SELECT
|
||
COUNT(*) as total_checks,
|
||
SUM(CASE WHEN cookie_valid THEN 1 ELSE 0 END) as valid_checks,
|
||
MIN(check_time) as first_check,
|
||
MAX(check_time) as last_check
|
||
FROM cookie_monitor
|
||
WHERE check_time >= datetime('now', '-24 hours')
|
||
''')
|
||
|
||
stats = cursor.fetchone()
|
||
|
||
if stats and stats[0] > 0:
|
||
total_checks, valid_checks, first_check, last_check = stats
|
||
success_rate = (valid_checks / total_checks) * 100 if total_checks > 0 else 0
|
||
|
||
print(f" 最近24小时检查: {total_checks} 次")
|
||
print(f" 有效检查: {valid_checks} 次")
|
||
print(f" 成功率: {success_rate:.1f}%")
|
||
print(f" 首次检查: {first_check}")
|
||
print(f" 最后检查: {last_check}")
|
||
|
||
# 检查是否需要告警
|
||
if success_rate < 50:
|
||
alert_msg = f"Cookie健康状况不佳!最近24小时成功率仅{success_rate:.1f}%"
|
||
self.send_alert("COOKIE_HEALTH_POOR", alert_msg)
|
||
return "POOR"
|
||
elif success_rate < 80:
|
||
alert_msg = f"Cookie健康状况一般,成功率{success_rate:.1f}%"
|
||
self.send_alert("COOKIE_HEALTH_WARNING", alert_msg)
|
||
return "WARNING"
|
||
else:
|
||
return "GOOD"
|
||
else:
|
||
print(" 无最近24小时检查记录")
|
||
return "UNKNOWN"
|
||
|
||
conn.close()
|
||
|
||
def check_cookie_expiry_warning(self):
|
||
"""检查Cookie过期预警"""
|
||
print("\n⚠️ 检查Cookie过期预警...")
|
||
|
||
if not os.path.exists(self.cookie_file):
|
||
return "NO_COOKIE_FILE"
|
||
|
||
try:
|
||
# 检查Cookie文件修改时间
|
||
mtime = os.path.getmtime(self.cookie_file)
|
||
cookie_age_days = (time.time() - mtime) / (24 * 3600)
|
||
|
||
print(f" Cookie文件年龄: {cookie_age_days:.1f} 天")
|
||
|
||
# 预警阈值
|
||
if cookie_age_days > 180: # 6个月
|
||
alert_msg = f"Cookie文件已存在{cookie_age_days:.0f}天,可能即将过期!"
|
||
self.send_alert("COOKIE_EXPIRY_WARNING", alert_msg)
|
||
return "EXPIRY_WARNING"
|
||
elif cookie_age_days > 300: # 10个月
|
||
alert_msg = f"Cookie文件已存在{cookie_age_days:.0f}天,很可能已过期!"
|
||
self.send_alert("COOKIE_EXPIRY_CRITICAL", alert_msg)
|
||
return "EXPIRY_CRITICAL"
|
||
else:
|
||
return "OK"
|
||
|
||
except Exception as e:
|
||
print(f"❌ 检查Cookie年龄失败: {e}")
|
||
return "ERROR"
|
||
|
||
def send_alert(self, alert_type, message):
|
||
"""发送告警到QQBot"""
|
||
print(f"🔔 发送告警: {alert_type}")
|
||
print(f" 消息: {message}")
|
||
|
||
# 记录告警到数据库
|
||
conn = sqlite3.connect(self.monitor_db)
|
||
cursor = conn.cursor()
|
||
|
||
cursor.execute('''
|
||
INSERT INTO alerts (alert_time, alert_type, alert_message, cookie_status)
|
||
VALUES (?, ?, ?, ?)
|
||
''', (
|
||
datetime.now().isoformat(),
|
||
alert_type,
|
||
message,
|
||
"需要检查"
|
||
))
|
||
|
||
alert_id = cursor.lastrowid
|
||
|
||
# 发送到QQBot
|
||
try:
|
||
# 构建QQBot消息
|
||
qqbot_message = self._format_qqbot_message(alert_type, message)
|
||
|
||
# 这里需要调用QQBot的API
|
||
# 由于QQBot集成方式未知,我们先记录到数据库
|
||
# 实际发送需要根据QQBot的API实现
|
||
|
||
cursor.execute('''
|
||
UPDATE alerts
|
||
SET sent_to_qqbot = 1,
|
||
qqbot_response = '已记录,需要手动发送'
|
||
WHERE id = ?
|
||
''', (alert_id,))
|
||
|
||
conn.commit()
|
||
|
||
print(f"✅ 告警已记录到数据库 (ID: {alert_id})")
|
||
print(f" QQBot消息: {qqbot_message}")
|
||
|
||
# 返回消息供手动发送
|
||
return qqbot_message
|
||
|
||
except Exception as e:
|
||
print(f"❌ 发送告警失败: {e}")
|
||
cursor.execute('''
|
||
UPDATE alerts
|
||
SET qqbot_response = ?
|
||
WHERE id = ?
|
||
''', (f"发送失败: {str(e)}", alert_id))
|
||
conn.commit()
|
||
return None
|
||
finally:
|
||
conn.close()
|
||
|
||
def _format_qqbot_message(self, alert_type, message):
|
||
"""格式化QQBot消息"""
|
||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
if alert_type == "COOKIE_EXPIRY_CRITICAL":
|
||
emoji = "🔴"
|
||
title = "【紧急】小红书Cookie已过期"
|
||
elif alert_type == "COOKIE_EXPIRY_WARNING":
|
||
emoji = "🟡"
|
||
title = "【警告】小红书Cookie即将过期"
|
||
elif alert_type == "COOKIE_HEALTH_POOR":
|
||
emoji = "🔴"
|
||
title = "【异常】小红书Cookie健康状况不佳"
|
||
elif alert_type == "COOKIE_HEALTH_WARNING":
|
||
emoji = "🟡"
|
||
title = "【注意】小红书Cookie健康状况一般"
|
||
else:
|
||
emoji = "⚪"
|
||
title = "【通知】小红书Cookie状态更新"
|
||
|
||
qqbot_message = f"{emoji} {title}\n"
|
||
qqbot_message += f"⏰ 时间: {timestamp}\n"
|
||
qqbot_message += f"📝 详情: {message}\n\n"
|
||
qqbot_message += "🔧 需要操作:\n"
|
||
qqbot_message += "1. 重新登录小红书获取新Cookie\n"
|
||
qqbot_message += "2. 使用EditThisCookie导出Cookie\n"
|
||
qqbot_message += "3. 运行: python3 cookie_manager.py import cookies.txt\n"
|
||
qqbot_message += "4. 测试: python3 cookie_manager.py check\n\n"
|
||
qqbot_message += "📁 文件位置: ~/.xiaohongshu-cli/cookies.json"
|
||
|
||
return qqbot_message
|
||
|
||
def get_recent_alerts(self, hours=24):
|
||
"""获取最近告警"""
|
||
conn = sqlite3.connect(self.monitor_db)
|
||
cursor = conn.cursor()
|
||
|
||
cursor.execute('''
|
||
SELECT alert_time, alert_type, alert_message, sent_to_qqbot
|
||
FROM alerts
|
||
WHERE alert_time >= datetime('now', ?)
|
||
ORDER BY alert_time DESC
|
||
''', (f'-{hours} hours',))
|
||
|
||
alerts = cursor.fetchall()
|
||
conn.close()
|
||
|
||
return alerts
|
||
|
||
def generate_daily_report(self):
|
||
"""生成每日报告"""
|
||
print("\n📋 生成每日报告...")
|
||
|
||
conn = sqlite3.connect(self.monitor_db)
|
||
cursor = conn.cursor()
|
||
|
||
# 今日统计
|
||
today = datetime.now().strftime("%Y-%m-%d")
|
||
|
||
cursor.execute('''
|
||
SELECT
|
||
COUNT(*) as total_checks,
|
||
SUM(CASE WHEN cookie_valid THEN 1 ELSE 0 END) as valid_checks
|
||
FROM cookie_monitor
|
||
WHERE date(check_time) = ?
|
||
''', (today,))
|
||
|
||
today_stats = cursor.fetchone()
|
||
|
||
# 告警统计
|
||
cursor.execute('''
|
||
SELECT COUNT(*) as total_alerts
|
||
FROM alerts
|
||
WHERE date(alert_time) = ?
|
||
''', (today,))
|
||
|
||
today_alerts = cursor.fetchone()
|
||
|
||
# 生成报告
|
||
report = f"📊 小红书Cookie监控日报\n"
|
||
report += f"📅 日期: {today}\n"
|
||
report += f"⏰ 生成时间: {datetime.now().strftime('%H:%M:%S')}\n\n"
|
||
|
||
if today_stats and today_stats[0] > 0:
|
||
total_checks, valid_checks = today_stats
|
||
success_rate = (valid_checks / total_checks) * 100
|
||
report += f"✅ 今日检查: {total_checks} 次\n"
|
||
report += f"✅ 有效检查: {valid_checks} 次\n"
|
||
report += f"📈 成功率: {success_rate:.1f}%\n"
|
||
else:
|
||
report += "ℹ️ 今日无检查记录\n"
|
||
|
||
if today_alerts and today_alerts[0] > 0:
|
||
report += f"⚠️ 今日告警: {today_alerts[0]} 条\n"
|
||
else:
|
||
report += "✅ 今日无告警\n"
|
||
|
||
# Cookie文件信息
|
||
if os.path.exists(self.cookie_file):
|
||
mtime = os.path.getmtime(self.cookie_file)
|
||
cookie_age_days = (time.time() - mtime) / (24 * 3600)
|
||
report += f"\n📁 Cookie文件信息:\n"
|
||
report += f" 位置: {self.cookie_file}\n"
|
||
report += f" 年龄: {cookie_age_days:.1f} 天\n"
|
||
|
||
with open(self.cookie_file, 'r', encoding='utf-8') as f:
|
||
cookies = json.load(f)
|
||
report += f" Cookie数量: {len(cookies)} 个\n"
|
||
else:
|
||
report += "\n❌ Cookie文件不存在\n"
|
||
|
||
report += "\n🔧 建议:\n"
|
||
if today_stats and today_stats[0] > 0 and (valid_checks / total_checks) < 0.8:
|
||
report += " ⚠️ Cookie成功率较低,建议检查Cookie有效性\n"
|
||
else:
|
||
report += " ✅ Cookie状态良好,无需操作\n"
|
||
|
||
conn.close()
|
||
|
||
print(report)
|
||
return report
|
||
|
||
def monitor_and_alert(self):
|
||
"""监控并发送告警"""
|
||
print("=" * 70)
|
||
print("小红书Cookie监控系统")
|
||
print("=" * 70)
|
||
|
||
# 1. 检查Cookie有效性
|
||
is_valid = self.check_cookie_validity()
|
||
|
||
# 2. 分析健康状况
|
||
health_status = self.analyze_cookie_health()
|
||
|
||
# 3. 检查过期预警
|
||
expiry_status = self.check_cookie_expiry_warning()
|
||
|
||
# 4. 生成报告
|
||
report = self.generate_daily_report()
|
||
|
||
# 5. 如果需要,发送告警
|
||
if not is_valid or health_status in ["POOR", "WARNING"] or expiry_status in ["EXPIRY_WARNING", "EXPIRY_CRITICAL"]:
|
||
alert_message = f"Cookie状态异常!有效性: {is_valid}, 健康: {health_status}, 过期: {expiry_status}"
|
||
qqbot_message = self.send_alert("COOKIE_STATUS_ALERT", alert_message)
|
||
|
||
print("\n🔔 已生成QQBot告警消息:")
|
||
print("-" * 50)
|
||
print(qqbot_message)
|
||
print("-" * 50)
|
||
print("\n💡 请手动发送此消息到QQBot")
|
||
|
||
print("\n" + "=" * 70)
|
||
print("监控完成")
|
||
print("=" * 70)
|
||
|
||
return is_valid
|
||
|
||
def main():
|
||
"""主函数"""
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description='小红书Cookie监控器')
|
||
subparsers = parser.add_subparsers(dest='command', help='命令')
|
||
|
||
# monitor命令
|
||
subparsers.add_parser('monitor', help='执行监控并发送告警')
|
||
|
||
# check命令
|
||
subparsers.add_parser('check', help='检查Cookie有效性')
|
||
|
||
# report命令
|
||
subparsers.add_parser('report', help='生成每日报告')
|
||
|
||
# alerts命令
|
||
alerts_parser = subparsers.add_parser('alerts', help='查看告警')
|
||
alerts_parser.add_argument('--hours', type=int, default=24, help='查看最近多少小时的告警')
|
||
|
||
# test-alert命令
|
||
subparsers.add_parser('test-alert', help='测试告警功能')
|
||
|
||
args = parser.parse_args()
|
||
|
||
monitor = XiaohongshuCookieMonitor()
|
||
|
||
if args.command == 'monitor':
|
||
monitor.monitor_and_alert()
|
||
|
||
elif args.command == 'check':
|
||
monitor.check_cookie_validity()
|
||
|
||
elif args.command == 'report':
|
||
monitor.generate_daily_report()
|
||
|
||
elif args.command == 'alerts':
|
||
alerts = monitor.get_recent_alerts(args.hours)
|
||
if alerts:
|
||
print(f"📋 最近{args.hours}小时告警:")
|
||
for alert in alerts:
|
||
alert_time, alert_type, alert_message, sent = alert
|
||
sent_status = "✅" if sent else "❌"
|
||
print(f" {alert_time} [{alert_type}] {sent_status}")
|
||
print(f" {alert_message[:100]}...")
|
||
else:
|
||
print(f"✅ 最近{args.hours}小时无告警")
|
||
|
||
elif args.command == 'test-alert':
|
||
test_message = "测试告警: Cookie监控系统工作正常"
|
||
qqbot_message = monitor.send_alert("TEST_ALERT", test_message)
|
||
print("测试告警已发送:")
|
||
print(qqbot_message)
|
||
|
||
else:
|
||
parser.print_help()
|
||
|
||
if __name__ == "__main__":
|
||
main() |