-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmain.py
More file actions
221 lines (186 loc) · 8.12 KB
/
Copy pathmain.py
File metadata and controls
221 lines (186 loc) · 8.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
from app_lifecycle import init_lifecycle_manager, toggle_pause, trigger_exit
# 生命周期管理器需要在所有 atexit 方法被注册前初始化
init_lifecycle_manager()
import sys
import time
import os
import traceback
from functools import wraps, partial
from typing import Callable, ParamSpec, TypeVar
from keyboard_utils import HotKeyManager
from logger import set_loglevel, get_logger
from config import Config
from argument_parser import ArgumentParser, ArgumentError
from ocr_utils import OCREngine
from steambot_utils import SteamBot
from steamgui_automation import SteamAutomation
from push_utils import UniPush
from gta_automator import GTAAutomator
from health_check import HealthMonitor
from gta_automator.exception import *
# 用于装饰器类型注解的泛型变量
P = ParamSpec("P") # 捕获函数的参数列表 (args, kwargs)
R = TypeVar("R") # 捕获函数的返回值类型
logger = get_logger("main")
def interrupt_decorator(main_func: Callable[P, R]) -> Callable[P, R]:
"""
用于处理退出的装饰器。
"""
@wraps(main_func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
try:
return main_func(*args, **kwargs)
except KeyboardInterrupt:
trigger_exit("用户手动中断")
sys.exit(0)
except Exception as e:
logger.critical(f"未捕获的异常: {e}", exc_info=e)
sys.exit(1)
return wrapper
# --- 主程序执行 ---
@interrupt_decorator
def main():
os.system(f"title 鸡你太美")
global_start_time = time.monotonic()
# 初始化命令行参数
try:
arg_manager = ArgumentParser()
command_line_args = arg_manager.parse()
except ArgumentError as e:
logger.error(f"解析命令行参数时出错: {e}", exc_info=e)
input("\n按 Enter 键退出...")
return
# 加载配置
try:
config = Config(command_line_args["config_file_path"])
logger.info("配置加载成功。")
except Exception as e:
logger.error(f"加载配置失败: {e}", exc_info=e)
input("\n按 Enter 键退出...")
return
# 初始化日志
logger.info("根据配置重新加载日志模块。")
# 设置日志等级
if config.debug:
logger.warning("启用了 DEBUG 模式,将输出更详细的日志。")
set_loglevel(log_level="DEBUG")
else:
set_loglevel(log_level="INFO")
# 初始化热键
hotkey = HotKeyManager()
# 暂停/恢复热键
hotkey.add_hotkey("<ctrl>+<f9>", toggle_pause)
# 退出热键
hotkey.add_hotkey("<ctrl>+<f10>", partial(trigger_exit, "触发退出热键"))
logger.warning("热键初始化成功,使用 CTRL+F9 暂停和恢复 Bot,使用 CTRL+F10 退出程序。")
# 初始化 OCR
try:
ocrArgs = config.ocrArgs
ocr_engine = OCREngine(ocrArgs)
except Exception as e:
logger.error(f"初始化 OCR 引擎失败: {e}", exc_info=e)
input("\n按 Enter 键退出...")
return
# 初始化 Steam Bot
if not config.useAlterMessagingMethod:
try:
logger.info("正在初始化 Steam Bot ...")
steam_bot = SteamBot(config)
except ValueError as e:
# 配置文件中的值错误,无须打印错误堆栈。
logger.error(f"初始化 Steam Bot 失败: {e}")
input("\n按 Enter 键退出...")
return
except Exception as e:
logger.error(f"初始化 Steam Bot 失败: {e}", exc_info=e)
input("\n按 Enter 键退出...")
return
else:
logger.info("正在初始化 Steam Automation ...")
try:
steam_bot = SteamAutomation(config.AlterMessagingMethodWindowTitle)
except Exception as e:
logger.error(f"初始化 Steam Automation 失败: {e}", exc_info=e)
input("\n按 Enter 键退出...")
return
# 初始化消息推送
try:
push_integration = UniPush(config, steam_bot.get_login_status().get("name", "N/A"))
except Exception as e:
logger.error(f"初始化消息推送失败: {e}", exc_info=e)
input("\n按 Enter 键退出...")
return
# 初始化游戏控制器
automator = GTAAutomator(
config, ocr_engine.ocr_window, steam_bot.send_group_message, push_integration.push_message
)
# 初始化健康检查
def should_suppress_health_check():
"""如果 Bot 在恢复模式,跳过健康检查。"""
# 注: 早期版本中该方法还被用于跳过暂停时的检查,目前该功能已在 HealthMonitor 内部实现
try:
if automator.is_in_recovery_mode():
return False
except Exception as e:
logger.error(f"健康检查未能获取 Bot 工作模式: {e} , 将进行健康检查。")
return True
return True
if config.enableHealthCheck:
logger.warning(f"已启用健康检查。正在初始化监控模块...")
health_check_exit_func = partial(trigger_exit, "触发 BOT 不健康自动退出")
monitor = HealthMonitor(
config,
steam_bot.get_last_send_system_time,
steam_bot.get_last_send_monotonic_time,
health_check_exit_func,
push_integration.push_message,
should_suppress_health_check,
)
else:
logger.warning("未启用健康检查。")
# --- 主循环 ---
# 主循环连续出错的次数
main_loop_consecutive_error_count = 0
while True:
try:
# 执行一轮循环
automator.run_one_cycle()
# 如果成功完成,重置连续错误计数器
if main_loop_consecutive_error_count != 0:
logger.info("本轮循环成功,重置连续错误计数。")
main_loop_consecutive_error_count = 0
except Exception as e:
# 捕获到异常则累加连续出错次数
main_loop_consecutive_error_count += 1
# 出错后等待的时间, 随连续出错次数增大而指数增长, 最多等待 120 秒
wait_before_restart_loop = min(2**main_loop_consecutive_error_count * 5, 120)
logger.error(f"主循环中发生错误: {e}", exc_info=e)
# 恶意/问题玩家,退出程序
if isinstance(e, UnexpectedGameState) and e.actual_state in (
GameState.BAD_SPORT_LEVEL,
GameState.DODGY_PLAYER_LEVEL,
):
logger.critical(f"检测到恶意等级过高: {e.actual_state.value}。程序将退出以保护账号安全。")
logger.warning("提示: 如果启用了自动降低恶意值,程序会在问题玩家时自动挂机降低恶意值。")
push_integration.push_message(
f"恶意值过高({e.actual_state.value})",
"程序将退出以保护账号安全。\n提示: 如果启用了自动降低恶意值,程序会在问题玩家时自动挂机降低恶意值。",
)
return 2 # 退出程序
# 其他异常则根据配置文件决定是重试还是退出
else:
logger.info(
f"当前连续失败次数 {main_loop_consecutive_error_count},阈值 {config.mainLoopConsecutiveErrorThreshold}。"
)
if main_loop_consecutive_error_count > config.mainLoopConsecutiveErrorThreshold:
logger.critical("超过连续失败阈值,程序退出...")
# 运行超过 pushActivationDelay 分钟则推送消息
if time.monotonic() - global_start_time > config.pushActivationDelay * 60:
push_integration.push_message(
"超过连续失败阈值,程序退出", f"最后一次错误: {e} \n{traceback.format_exc()}"
)
return 1 # 退出程序
logger.info(f"未超过连续失败阈值,将在 {wait_before_restart_loop} 秒后重启循环...")
time.sleep(wait_before_restart_loop)
if __name__ == "__main__":
main()