diff --git a/assets/images/luoluoTool.ico b/assets/images/luoluoTool.ico new file mode 100644 index 0000000..fd0d4d6 Binary files /dev/null and b/assets/images/luoluoTool.ico differ diff --git a/assets/images/luoluoTool.png b/assets/images/luoluoTool.png new file mode 100644 index 0000000..93a9d8e Binary files /dev/null and b/assets/images/luoluoTool.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ec6ca6f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,47 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "LuoLuoTool" +version = "0.1" +description = "《桃源深处有人家》日常任务挂机工具" +readme = "README.md" +requires-python = ">=3.9" +license = {text = "MIT"} +authors = [ + {name = "moweishan", email = ""} +] +keywords = ["game", "automation", "挂机", "桃源深处有人家"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: MIT License", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +dependencies = [ + "customtkinter>=5.2.2", + "Pillow>=10.0.0", + "pyautogui>=0.9.54", + "pywin32>=306", + "opencv-python>=4.8.0", + "numpy>=1.24.0", + "loguru>=0.7.0", +] + +[project.gui-scripts] +luoluo = "src.main:main" + +[project.urls] +Homepage = "https://bk.moweishan.top/" +Repository = "https://github.com/moweishan/LuoLuoTool" + +[tool.setuptools.packages.find] +where = ["."] +include = ["src*"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..da00a4c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +# LuoLuoTool 依赖列表 +# 《桃源深处有人家》挂机工具 + +# ========== UI框架 ========== +customtkinter>=5.2.2 # 现代化UI框架 +Pillow>=10.0.0 # 图像处理 + +# ========== 游戏自动化 ========== +pyautogui>=0.9.54 # 鼠标键盘模拟 +pywin32>=306 # Windows API +opencv-python>=4.8.0 # 图像识别 +numpy>=1.24.0 # 数值计算 diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 0000000..3e408b3 --- /dev/null +++ b/src/core/__init__.py @@ -0,0 +1,14 @@ +""" +LuoLuoTool 核心模块 +游戏自动化控制核心 +""" + +from .automation import AutomationController +from .game_window import GameWindowManager +from .actions import GameActions + +__all__ = [ + "AutomationController", + "GameWindowManager", + "GameActions", +] diff --git a/src/core/actions.py b/src/core/actions.py new file mode 100644 index 0000000..05205cb --- /dev/null +++ b/src/core/actions.py @@ -0,0 +1,41 @@ +""" +游戏动作定义 +""" + +import time +from typing import Optional +from src.utils.logger import logger + +from .game_window import GameWindowManager + + +class GameActions: + """游戏动作执行器""" + + def __init__(self, window_manager: GameWindowManager): + self.window = window_manager + + def click(self, x: int, y: int, delay: float = 0.1): + """点击指定坐标""" + self.window.click(x, y) + time.sleep(delay) + + def click_template(self, template_name: str, timeout: float = 5.0) -> bool: + """点击匹配到的模板图像""" + # TODO: 实现模板匹配点击 + logger.debug(f"尝试点击模板: {template_name}") + return False + + def wait(self, seconds: float): + """等待指定时间""" + time.sleep(seconds) + + def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float = 0.5): + """滑动操作""" + # TODO: 实现滑动 + logger.debug(f"滑动: ({x1}, {y1}) -> ({x2}, {y2})") + + def press_key(self, key: str): + """按下键盘按键""" + # TODO: 实现按键 + logger.debug(f"按键: {key}") diff --git a/src/core/automation.py b/src/core/automation.py new file mode 100644 index 0000000..4703fa3 --- /dev/null +++ b/src/core/automation.py @@ -0,0 +1,87 @@ +""" +自动化控制主类 +""" + +import threading +import time +from typing import Optional, Callable +from src.utils.logger import logger + +from .game_window import GameWindowManager +from .actions import GameActions + + +class AutomationController: + """自动化控制器""" + + def __init__(self): + self.window_manager = GameWindowManager() + self.actions = GameActions(self.window_manager) + + self._running = False + self._paused = False + self._thread: Optional[threading.Thread] = None + self._callback: Optional[Callable] = None + + @property + def is_running(self) -> bool: + return self._running + + @property + def is_paused(self) -> bool: + return self._paused + + def set_callback(self, callback: Callable): + """设置状态回调函数""" + self._callback = callback + + def _notify(self, message: str): + """通知UI更新""" + logger.info(message) + if self._callback: + self._callback(message) + + def start(self): + """开始自动化""" + if self._running: + return + + if not self.window_manager.is_window_captured: + self._notify("请先捕获游戏窗口") + return + + self._running = True + self._paused = False + self._thread = threading.Thread(target=self._run_loop, daemon=True) + self._thread.start() + self._notify("自动化已启动") + + def stop(self): + """停止自动化""" + self._running = False + self._paused = False + if self._thread: + self._thread.join(timeout=2) + self._notify("自动化已停止") + + def pause(self): + """暂停/继续""" + if not self._running: + return + self._paused = not self._paused + status = "已暂停" if self._paused else "已继续" + self._notify(f"自动化{status}") + + def _run_loop(self): + """主运行循环""" + while self._running: + if self._paused: + time.sleep(0.1) + continue + + try: + # TODO: 实现具体的任务执行逻辑 + time.sleep(0.1) + except Exception as e: + logger.error(f"运行错误: {e}") + self._notify(f"错误: {e}") diff --git a/src/core/game_window.py b/src/core/game_window.py new file mode 100644 index 0000000..5d739fd --- /dev/null +++ b/src/core/game_window.py @@ -0,0 +1,97 @@ +""" +游戏窗口管理 +""" + +import win32gui +import win32con +from typing import Optional, Tuple +from src.utils.logger import logger + + +class GameWindowManager: + """游戏窗口管理器""" + + # 游戏窗口类名和标题 + GAME_CLASS_NAME = "UnityWndClass" # Unity游戏常用类名 + GAME_WINDOW_TITLE = "桃源深处有人家" + + def __init__(self): + self._hwnd: Optional[int] = None + self._window_rect: Tuple[int, int, int, int] = (0, 0, 0, 0) + + @property + def is_window_captured(self) -> bool: + """是否已捕获窗口""" + if self._hwnd is None: + return False + return win32gui.IsWindow(self._hwnd) + + @property + def hwnd(self) -> Optional[int]: + """窗口句柄""" + return self._hwnd + + @property + def window_rect(self) -> Tuple[int, int, int, int]: + """窗口矩形 (left, top, right, bottom)""" + if self.is_window_captured: + self._window_rect = win32gui.GetWindowRect(self._hwnd) + return self._window_rect + + @property + def client_size(self) -> Tuple[int, int]: + """客户端区域大小 (width, height)""" + if not self.is_window_captured: + return (0, 0) + left, top, right, bottom = self.window_rect + return (right - left, bottom - top) + + def find_window(self) -> bool: + """查找游戏窗口""" + # 先尝试精确匹配 + hwnd = win32gui.FindWindow(None, self.GAME_WINDOW_TITLE) + + # 如果没找到,尝试模糊匹配 + if hwnd == 0: + def callback(hwnd, extra): + if win32gui.IsWindowVisible(hwnd): + title = win32gui.GetWindowText(hwnd) + if self.GAME_WINDOW_TITLE in title: + extra.append(hwnd) + return True + + windows = [] + win32gui.EnumWindows(callback, windows) + if windows: + hwnd = windows[0] + + if hwnd != 0: + self._hwnd = hwnd + self._window_rect = win32gui.GetWindowRect(hwnd) + logger.info(f"找到游戏窗口: {hwnd}, 大小: {self.client_size}") + return True + + logger.warning("未找到游戏窗口") + return False + + def capture_window(self) -> bool: + """捕获游戏窗口""" + return self.find_window() + + def bring_to_front(self): + """将窗口置前""" + if self.is_window_captured: + win32gui.SetForegroundWindow(self._hwnd) + + def click(self, x: int, y: int): + """在窗口内点击""" + if not self.is_window_captured: + return + + left, top, _, _ = self.window_rect + # 转换为屏幕坐标 + screen_x = left + x + screen_y = top + y + + # TODO: 使用pyautogui或win32api发送点击 + logger.debug(f"点击坐标: ({screen_x}, {screen_y})") diff --git a/src/core/tasks/__init__.py b/src/core/tasks/__init__.py new file mode 100644 index 0000000..7cf453f --- /dev/null +++ b/src/core/tasks/__init__.py @@ -0,0 +1,14 @@ +""" +挂机任务模块 +所有任务逻辑代码写死实现 +""" + +from .daily_tasks import DailyTaskRunner +from .misc_tasks import MiscTaskRunner +from .pending_tasks import PendingTaskRunner + +__all__ = [ + "DailyTaskRunner", + "MiscTaskRunner", + "PendingTaskRunner", +] diff --git a/src/core/tasks/daily_tasks.py b/src/core/tasks/daily_tasks.py new file mode 100644 index 0000000..a3d5017 --- /dev/null +++ b/src/core/tasks/daily_tasks.py @@ -0,0 +1,69 @@ +""" +日常挂机任务 +""" + +from typing import Dict, Any +from src.utils.logger import logger + + +class DailyTaskRunner: + """日常任务执行器""" + + # 任务配置 - 代码写死 + TASKS = { + "daily_mission": { + "name": "每日委托", + "enabled": True, + "description": "完成每日任务", + }, + "resin_farming": { + "name": "清体力", + "enabled": True, + "description": "消耗体力刷资源", + }, + "monthly_card": { + "name": "领月卡", + "enabled": False, + "description": "领取月卡奖励", + }, + "friend_gift": { + "name": "好友礼物", + "enabled": True, + "description": "领取好友赠送的礼物", + }, + "shop_daily": { + "name": "每日商店", + "enabled": False, + "description": "购买每日商店物品", + }, + } + + def __init__(self): + self._config = self.TASKS.copy() + + def get_tasks(self) -> Dict[str, Any]: + """获取所有任务配置""" + return self._config + + def update_task(self, task_id: str, enabled: bool): + """更新任务启用状态""" + if task_id in self._config: + self._config[task_id]["enabled"] = enabled + logger.info(f"任务 {task_id} 状态更新为: {enabled}") + + def run(self, actions): + """执行启用的任务""" + for task_id, config in self._config.items(): + if not config["enabled"]: + continue + + logger.info(f"执行任务: {config['name']}") + try: + self._execute_task(task_id, actions) + except Exception as e: + logger.error(f"任务 {config['name']} 执行失败: {e}") + + def _execute_task(self, task_id: str, actions): + """执行具体任务""" + # TODO: 实现具体任务逻辑 + logger.debug(f"执行任务逻辑: {task_id}") diff --git a/src/core/tasks/misc_tasks.py b/src/core/tasks/misc_tasks.py new file mode 100644 index 0000000..2f70cce --- /dev/null +++ b/src/core/tasks/misc_tasks.py @@ -0,0 +1,64 @@ +""" +杂项功能任务 +""" + +from typing import Dict, Any +from src.utils.logger import logger + + +class MiscTaskRunner: + """杂项功能执行器""" + + # 功能配置 - 代码写死 + FEATURES = { + "auto_pickup": { + "name": "自动拾取", + "enabled": True, + "description": "自动拾取掉落物品", + }, + "auto_skip": { + "name": "自动跳过对话", + "enabled": False, + "description": "自动跳过游戏对话", + }, + "auto_heal": { + "name": "自动回血", + "enabled": True, + "description": "低血量自动回血", + }, + "auto_repair": { + "name": "自动修理", + "enabled": False, + "description": "装备损坏自动修理", + }, + } + + def __init__(self): + self._config = self.FEATURES.copy() + + def get_features(self) -> Dict[str, Any]: + """获取所有功能配置""" + return self._config + + def update_feature(self, feature_id: str, enabled: bool): + """更新功能启用状态""" + if feature_id in self._config: + self._config[feature_id]["enabled"] = enabled + logger.info(f"功能 {feature_id} 状态更新为: {enabled}") + + def run(self, actions): + """执行启用的功能""" + for feature_id, config in self._config.items(): + if not config["enabled"]: + continue + + logger.info(f"执行功能: {config['name']}") + try: + self._execute_feature(feature_id, actions) + except Exception as e: + logger.error(f"功能 {config['name']} 执行失败: {e}") + + def _execute_feature(self, feature_id: str, actions): + """执行具体功能""" + # TODO: 实现具体功能逻辑 + logger.debug(f"执行功能逻辑: {feature_id}") diff --git a/src/core/tasks/pending_tasks.py b/src/core/tasks/pending_tasks.py new file mode 100644 index 0000000..6e705b1 --- /dev/null +++ b/src/core/tasks/pending_tasks.py @@ -0,0 +1,48 @@ +""" +待定功能任务 +预留功能占位 +""" + +from typing import Dict, Any +from src.utils.logger import logger + + +class PendingTaskRunner: + """待定功能执行器""" + + # 待定功能配置 - 代码写死 + PENDING_FEATURES = { + "feature_a": { + "name": "功能A(待开发)", + "enabled": False, + "description": "预留功能A", + }, + "feature_b": { + "name": "功能B(待开发)", + "enabled": False, + "description": "预留功能B", + }, + "feature_c": { + "name": "功能C(待开发)", + "enabled": False, + "description": "预留功能C", + }, + } + + def __init__(self): + self._config = self.PENDING_FEATURES.copy() + + def get_features(self) -> Dict[str, Any]: + """获取所有待定功能""" + return self._config + + def update_feature(self, feature_id: str, enabled: bool): + """更新功能状态""" + if feature_id in self._config: + self._config[feature_id]["enabled"] = enabled + logger.info(f"待定功能 {feature_id} 状态更新为: {enabled}") + + def run(self, actions): + """执行启用的功能""" + logger.info("待定功能模块 - 暂无实现") + # 待定功能暂不执行任何操作 diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..27ad265 --- /dev/null +++ b/src/main.py @@ -0,0 +1,30 @@ +""" +LuoLuoTool - 《桃源深处有人家》挂机工具 +作者: moweishan +博客: https://bk.moweishan.top/ +版本: 0.1 +""" + +import sys +from pathlib import Path + +# 添加项目根目录到路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from src.utils.logger import setup_logger +from src.ui import LuoLuoApp + + +def main(): + """程序入口""" + # 初始化日志 + setup_logger() + + # 启动应用 + app = LuoLuoApp() + app.run() + + +if __name__ == "__main__": + main() diff --git a/src/ui/__init__.py b/src/ui/__init__.py new file mode 100644 index 0000000..b7a9f0b --- /dev/null +++ b/src/ui/__init__.py @@ -0,0 +1,8 @@ +""" +LuoLuoTool UI模块 +使用CustomTkinter构建现代化界面 +""" + +from .app import LuoLuoApp + +__all__ = ["LuoLuoApp"] diff --git a/src/ui/app.py b/src/ui/app.py new file mode 100644 index 0000000..cf0d7c5 --- /dev/null +++ b/src/ui/app.py @@ -0,0 +1,229 @@ +""" +LuoLuoTool 主应用类 +窗口标题: LuoLuo +""" + +import customtkinter as ctk +from pathlib import Path +from src.utils.logger import logger + +from .pages.run_page import RunPage +from .pages.daily_config_page import DailyConfigPage +from .pages.misc_config_page import MiscConfigPage +from .pages.pending_config_page import PendingConfigPage +from .pages.about_page import AboutPage + + +class LuoLuoApp: + """LuoLuoTool主应用""" + + # 窗口配置 + WINDOW_TITLE = "LuoLuo" + WINDOW_WIDTH = 900 + WINDOW_HEIGHT = 650 + + # 侧边栏配置 + SIDEBAR_WIDTH = 140 + + # 主题配置 + THEME_COLOR = ("#3B8ED0", "#1F6AA5") # 浅色, 深色 + + def __init__(self): + # 设置主题 + ctk.set_appearance_mode("System") + ctk.set_default_color_theme("blue") + + # 创建主窗口 + self.root = ctk.CTk() + self.root.title(self.WINDOW_TITLE) + self.root.geometry(f"{self.WINDOW_WIDTH}x{self.WINDOW_HEIGHT}") + self.root.minsize(800, 550) + + # 当前页面 + self.current_page = None + self.pages = {} + + # 初始化UI + self._setup_ui() + + # 设置窗口图标(在UI初始化后设置) + self._set_window_icon() + + logger.info("LuoLuoApp初始化完成") + + def _set_window_icon(self): + """设置窗口图标(仅左上角)""" + assets_dir = Path(__file__).parent.parent.parent / "assets" / "images" + ico_path = assets_dir / "luoluoTool.ico" + png_path = assets_dir / "luoluoTool.png" + + try: + # 优先使用 ICO 文件(Windows 原生支持) + if ico_path.exists(): + self.root.iconbitmap(str(ico_path)) + logger.info(f"已设置窗口图标(ICO): {ico_path}") + elif png_path.exists(): + # 备用:使用 PNG + from PIL import Image, ImageTk + icon_image = Image.open(png_path) + if icon_image.mode != 'RGBA': + icon_image = icon_image.convert('RGBA') + icon_image = icon_image.resize((32, 32), Image.Resampling.LANCZOS) + self._icon_photo = ImageTk.PhotoImage(icon_image) + self.root.iconphoto(True, self._icon_photo) + logger.info(f"已设置窗口图标(PNG): {png_path}") + else: + logger.warning("未找到图标文件") + except Exception as e: + logger.warning(f"加载图标失败: {e}") + + def _setup_ui(self): + """初始化界面""" + # 配置网格布局 + self.root.grid_columnconfigure(1, weight=1) + self.root.grid_rowconfigure(0, weight=1) + + # 创建侧边栏 + self._create_sidebar() + + # 创建主内容区域 + self._create_main_content() + + # 创建底部状态栏 + self._create_statusbar() + + # 初始化页面 + self._init_pages() + + # 默认显示运行页面 + self.show_page("run") + + def _create_sidebar(self): + """创建侧边栏""" + self.sidebar = ctk.CTkFrame( + self.root, + width=self.SIDEBAR_WIDTH, + corner_radius=0 + ) + self.sidebar.grid(row=0, column=0, rowspan=2, sticky="nsew") + self.sidebar.grid_rowconfigure(6, weight=1) + + # Logo/标题 + self.logo_label = ctk.CTkLabel( + self.sidebar, + text="🎮 LuoLuo", + font=ctk.CTkFont(size=18, weight="bold") + ) + self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10)) + + # 侧边栏按钮 + self.nav_buttons = {} + nav_items = [ + ("run", "▶ 运行", 1), + ("daily", "📋 日常", 2), + ("misc", "🔧 杂项", 3), + ("pending", "⏳ 待定", 4), + ("about", "ℹ 关于", 5), + ] + + for page_id, text, row in nav_items: + btn = ctk.CTkButton( + self.sidebar, + text=text, + anchor="w", + fg_color="transparent", + text_color=("gray10", "gray90"), + hover_color=("gray70", "gray30"), + height=40, + command=lambda p=page_id: self.show_page(p) + ) + btn.grid(row=row, column=0, padx=10, pady=5, sticky="ew") + self.nav_buttons[page_id] = btn + + # 主题切换 + self.theme_switch = ctk.CTkSwitch( + self.sidebar, + text="🌙 深色", + command=self._toggle_theme + ) + self.theme_switch.grid(row=7, column=0, padx=20, pady=20, sticky="s") + + def _create_main_content(self): + """创建主内容区域""" + self.main_frame = ctk.CTkFrame(self.root, corner_radius=0, fg_color="transparent") + self.main_frame.grid(row=0, column=1, sticky="nsew", padx=10, pady=10) + self.main_frame.grid_columnconfigure(0, weight=1) + self.main_frame.grid_rowconfigure(0, weight=1) + + def _create_statusbar(self): + """创建状态栏""" + self.statusbar = ctk.CTkFrame(self.root, height=30, corner_radius=0) + self.statusbar.grid(row=1, column=1, sticky="ew", padx=10, pady=(0, 5)) + + self.status_label = ctk.CTkLabel( + self.statusbar, + text="就绪", + font=ctk.CTkFont(size=12) + ) + self.status_label.pack(side="left", padx=10) + + self.version_label = ctk.CTkLabel( + self.statusbar, + text="v0.1", + font=ctk.CTkFont(size=12), + text_color="gray" + ) + self.version_label.pack(side="right", padx=10) + + def _init_pages(self): + """初始化所有页面 - 预加载提升切换速度""" + self.pages["run"] = RunPage(self.main_frame, self) + self.pages["daily"] = DailyConfigPage(self.main_frame, self) + self.pages["misc"] = MiscConfigPage(self.main_frame, self) + self.pages["pending"] = PendingConfigPage(self.main_frame, self) + self.pages["about"] = AboutPage(self.main_frame, self) + + # 预构建所有页面,避免懒加载导致的卡顿 + for page_id, page in self.pages.items(): + if page.frame is None: + page.frame = page.build() + + def show_page(self, page_id: str): + """显示指定页面 - 优化切换性能""" + if self.current_page == page_id: + return + + # 使用 after 延迟更新UI,避免阻塞 + self.root.after(0, lambda: self._do_show_page(page_id)) + + def _do_show_page(self, page_id: str): + """实际执行页面切换""" + # 隐藏当前页面 + if self.current_page: + self.pages[self.current_page].hide() + self.nav_buttons[self.current_page].configure(fg_color="transparent") + + # 显示新页面 + self.current_page = page_id + self.pages[page_id].show() + self.nav_buttons[page_id].configure(fg_color=self.THEME_COLOR) + + logger.debug(f"切换到页面: {page_id}") + + def _toggle_theme(self): + """切换主题""" + if self.theme_switch.get(): + ctk.set_appearance_mode("Dark") + self.theme_switch.configure(text="☀ 浅色") + else: + ctk.set_appearance_mode("Light") + self.theme_switch.configure(text="🌙 深色") + + def set_status(self, text: str): + """设置状态栏文本""" + self.status_label.configure(text=text) + + def run(self): + """运行应用""" + logger.info("启动LuoLuo应用") + self.root.mainloop() diff --git a/src/ui/pages/__init__.py b/src/ui/pages/__init__.py new file mode 100644 index 0000000..d50e751 --- /dev/null +++ b/src/ui/pages/__init__.py @@ -0,0 +1,19 @@ +""" +UI页面模块 +""" + +from .base_page import BasePage +from .run_page import RunPage +from .daily_config_page import DailyConfigPage +from .misc_config_page import MiscConfigPage +from .pending_config_page import PendingConfigPage +from .about_page import AboutPage + +__all__ = [ + "BasePage", + "RunPage", + "DailyConfigPage", + "MiscConfigPage", + "PendingConfigPage", + "AboutPage", +] diff --git a/src/ui/pages/about_page.py b/src/ui/pages/about_page.py new file mode 100644 index 0000000..1a23fa2 --- /dev/null +++ b/src/ui/pages/about_page.py @@ -0,0 +1,154 @@ +""" +关于页面 +""" + +import customtkinter as ctk +import webbrowser +from pathlib import Path +from PIL import Image + +from .base_page import BasePage + + +class AboutPage(BasePage): + """关于页面""" + + # 项目信息 + VERSION = "0.1" + AUTHOR = "moweishan" + BLOG_URL = "https://bk.moweishan.top/" + DESCRIPTION = "专为《桃源深处有人家》打造的\n日常任务挂机工具" + + def build(self) -> ctk.CTkFrame: + """构建关于页面""" + frame = ctk.CTkFrame(self.parent, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + frame.grid_rowconfigure(0, weight=1) + + # 居中容器 + center_frame = ctk.CTkFrame(frame, fg_color="transparent") + center_frame.grid(row=0, column=0, pady=50) + + # 加载并显示图标 + self._load_icon(center_frame) + + # 应用名称 + name_label = ctk.CTkLabel( + center_frame, + text="LuoLuoTool", + font=ctk.CTkFont(size=28, weight="bold") + ) + name_label.pack() + + # 版本号 + version_label = ctk.CTkLabel( + center_frame, + text=f"版本 {self.VERSION}", + font=ctk.CTkFont(size=14), + text_color="gray" + ) + version_label.pack(pady=(5, 20)) + + # 分隔线 + separator = ctk.CTkFrame(center_frame, height=2, width=200) + separator.pack(pady=10) + + # 描述 + desc_label = ctk.CTkLabel( + center_frame, + text=self.DESCRIPTION, + font=ctk.CTkFont(size=13), + justify="center" + ) + desc_label.pack(pady=20) + + # 分隔线 + separator2 = ctk.CTkFrame(center_frame, height=2, width=200) + separator2.pack(pady=10) + + # 作者信息 + author_frame = ctk.CTkFrame(center_frame, fg_color="transparent") + author_frame.pack(pady=20) + + author_label = ctk.CTkLabel( + author_frame, + text=f"作者: {self.AUTHOR}", + font=ctk.CTkFont(size=13) + ) + author_label.pack() + + # 博客链接 + blog_btn = ctk.CTkButton( + author_frame, + text="🌐 访问博客", + command=self._open_blog + ) + blog_btn.pack(pady=10) + + # 按钮区域 + btn_frame = ctk.CTkFrame(center_frame, fg_color="transparent") + btn_frame.pack(pady=20) + + check_update_btn = ctk.CTkButton( + btn_frame, + text="🔍 检查更新", + command=self._check_update + ) + check_update_btn.pack(side="left", padx=5) + + return frame + + def _load_icon(self, parent): + """加载并显示图标""" + assets_dir = Path(__file__).parent.parent.parent.parent / "assets" / "images" + + # 优先使用 PNG 格式在关于页面显示 + png_path = assets_dir / "luoluoTool.png" + ico_path = assets_dir / "luoluoTool.ico" + + try: + if png_path.exists(): + # 使用 PNG 图标 + icon_image = Image.open(png_path) + # 调整大小为 100x100 + icon_image = icon_image.resize((100, 100), Image.Resampling.LANCZOS) + icon_ctk = ctk.CTkImage(light_image=icon_image, dark_image=icon_image, size=(100, 100)) + + icon_label = ctk.CTkLabel(parent, image=icon_ctk, text="") + icon_label.pack(pady=(0, 20)) + # 保持引用防止GC + self._about_icon = icon_ctk + elif ico_path.exists(): + # 如果没有PNG,尝试ICO + icon_image = Image.open(ico_path) + icon_image = icon_image.resize((100, 100), Image.Resampling.LANCZOS) + icon_ctk = ctk.CTkImage(light_image=icon_image, dark_image=icon_image, size=(100, 100)) + + icon_label = ctk.CTkLabel(parent, image=icon_ctk, text="") + icon_label.pack(pady=(0, 20)) + self._about_icon = icon_ctk + else: + # 使用默认emoji + icon_label = ctk.CTkLabel( + parent, + text="🎮", + font=ctk.CTkFont(size=64) + ) + icon_label.pack(pady=(0, 20)) + except Exception as e: + # 出错时使用默认emoji + icon_label = ctk.CTkLabel( + parent, + text="🎮", + font=ctk.CTkFont(size=64) + ) + icon_label.pack(pady=(0, 20)) + + def _open_blog(self): + """打开作者博客""" + webbrowser.open(self.BLOG_URL) + + def _check_update(self): + """检查更新""" + # TODO: 实现检查更新功能 + self.app.set_status("当前已是最新版本") diff --git a/src/ui/pages/base_page.py b/src/ui/pages/base_page.py new file mode 100644 index 0000000..e385217 --- /dev/null +++ b/src/ui/pages/base_page.py @@ -0,0 +1,40 @@ +""" +页面基类 +""" + +import customtkinter as ctk +from typing import Optional + + +class BasePage: + """页面基类""" + + def __init__(self, parent: ctk.CTkFrame, app: "LuoLuoApp"): + self.parent = parent + self.app = app + self.frame: Optional[ctk.CTkFrame] = None + + def build(self) -> ctk.CTkFrame: + """构建页面,子类重写""" + raise NotImplementedError + + def show(self): + """显示页面""" + if self.frame is None: + self.frame = self.build() + self.frame.grid(row=0, column=0, sticky="nsew") + self.on_show() + + def hide(self): + """隐藏页面""" + if self.frame: + self.frame.grid_forget() + self.on_hide() + + def on_show(self): + """页面显示时的回调,子类可重写""" + pass + + def on_hide(self): + """页面隐藏时的回调,子类可重写""" + pass diff --git a/src/ui/pages/daily_config_page.py b/src/ui/pages/daily_config_page.py new file mode 100644 index 0000000..3fd1994 --- /dev/null +++ b/src/ui/pages/daily_config_page.py @@ -0,0 +1,99 @@ +""" +日常挂机配置页面 +""" + +import customtkinter as ctk + +from .base_page import BasePage +from ...core.tasks.daily_tasks import DailyTaskRunner + + +class DailyConfigPage(BasePage): + """日常挂机配置页面""" + + def __init__(self, parent: ctk.CTkFrame, app: "LuoLuoApp"): + super().__init__(parent, app) + self.task_runner = DailyTaskRunner() + self.switches = {} + + def build(self) -> ctk.CTkFrame: + """构建页面""" + frame = ctk.CTkFrame(self.parent, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + + # 标题 + title = ctk.CTkLabel( + frame, + text="📋 日常挂机配置", + font=ctk.CTkFont(size=18, weight="bold") + ) + title.grid(row=0, column=0, padx=20, pady=(20, 10), sticky="w") + + # 说明 + desc = ctk.CTkLabel( + frame, + text="配置每日自动执行的日常任务", + font=ctk.CTkFont(size=12), + text_color="gray" + ) + desc.grid(row=1, column=0, padx=20, pady=(0, 20), sticky="w") + + # 任务列表 + tasks_frame = ctk.CTkFrame(frame) + tasks_frame.grid(row=2, column=0, sticky="ew", padx=20, pady=10) + tasks_frame.grid_columnconfigure(0, weight=1) + + tasks = self.task_runner.get_tasks() + for idx, (task_id, config) in enumerate(tasks.items()): + self._create_task_item(tasks_frame, task_id, config, idx) + + # 保存按钮 + save_btn = ctk.CTkButton( + frame, + text="💾 保存配置", + command=self._save_config + ) + save_btn.grid(row=3, column=0, padx=20, pady=20, sticky="e") + + return frame + + def _create_task_item(self, parent, task_id: str, config: dict, row: int): + """创建任务项""" + frame = ctk.CTkFrame(parent, fg_color="transparent") + frame.grid(row=row, column=0, sticky="ew", padx=10, pady=5) + frame.grid_columnconfigure(1, weight=1) + + # 开关 + switch = ctk.CTkSwitch( + frame, + text="", + width=50 + ) + switch.grid(row=0, column=0, padx=(0, 10)) + if config["enabled"]: + switch.select() + self.switches[task_id] = switch + + # 名称 + name_label = ctk.CTkLabel( + frame, + text=config["name"], + font=ctk.CTkFont(size=13, weight="bold") + ) + name_label.grid(row=0, column=1, sticky="w") + + # 描述 + desc_label = ctk.CTkLabel( + frame, + text=config["description"], + font=ctk.CTkFont(size=11), + text_color="gray" + ) + desc_label.grid(row=1, column=1, sticky="w") + + def _save_config(self): + """保存配置""" + for task_id, switch in self.switches.items(): + enabled = switch.get() == 1 + self.task_runner.update_task(task_id, enabled) + self.app.set_status("日常配置已保存") diff --git a/src/ui/pages/misc_config_page.py b/src/ui/pages/misc_config_page.py new file mode 100644 index 0000000..6e61cc6 --- /dev/null +++ b/src/ui/pages/misc_config_page.py @@ -0,0 +1,99 @@ +""" +杂项功能配置页面 +""" + +import customtkinter as ctk + +from .base_page import BasePage +from ...core.tasks.misc_tasks import MiscTaskRunner + + +class MiscConfigPage(BasePage): + """杂项功能配置页面""" + + def __init__(self, parent: ctk.CTkFrame, app: "LuoLuoApp"): + super().__init__(parent, app) + self.task_runner = MiscTaskRunner() + self.switches = {} + + def build(self) -> ctk.CTkFrame: + """构建页面""" + frame = ctk.CTkFrame(self.parent, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + + # 标题 + title = ctk.CTkLabel( + frame, + text="🔧 杂项功能配置", + font=ctk.CTkFont(size=18, weight="bold") + ) + title.grid(row=0, column=0, padx=20, pady=(20, 10), sticky="w") + + # 说明 + desc = ctk.CTkLabel( + frame, + text="配置挂机时的辅助功能", + font=ctk.CTkFont(size=12), + text_color="gray" + ) + desc.grid(row=1, column=0, padx=20, pady=(0, 20), sticky="w") + + # 功能列表 + features_frame = ctk.CTkFrame(frame) + features_frame.grid(row=2, column=0, sticky="ew", padx=20, pady=10) + features_frame.grid_columnconfigure(0, weight=1) + + features = self.task_runner.get_features() + for idx, (feature_id, config) in enumerate(features.items()): + self._create_feature_item(features_frame, feature_id, config, idx) + + # 保存按钮 + save_btn = ctk.CTkButton( + frame, + text="💾 保存配置", + command=self._save_config + ) + save_btn.grid(row=3, column=0, padx=20, pady=20, sticky="e") + + return frame + + def _create_feature_item(self, parent, feature_id: str, config: dict, row: int): + """创建功能项""" + frame = ctk.CTkFrame(parent, fg_color="transparent") + frame.grid(row=row, column=0, sticky="ew", padx=10, pady=5) + frame.grid_columnconfigure(1, weight=1) + + # 开关 + switch = ctk.CTkSwitch( + frame, + text="", + width=50 + ) + switch.grid(row=0, column=0, padx=(0, 10)) + if config["enabled"]: + switch.select() + self.switches[feature_id] = switch + + # 名称 + name_label = ctk.CTkLabel( + frame, + text=config["name"], + font=ctk.CTkFont(size=13, weight="bold") + ) + name_label.grid(row=0, column=1, sticky="w") + + # 描述 + desc_label = ctk.CTkLabel( + frame, + text=config["description"], + font=ctk.CTkFont(size=11), + text_color="gray" + ) + desc_label.grid(row=1, column=1, sticky="w") + + def _save_config(self): + """保存配置""" + for feature_id, switch in self.switches.items(): + enabled = switch.get() == 1 + self.task_runner.update_feature(feature_id, enabled) + self.app.set_status("杂项配置已保存") diff --git a/src/ui/pages/pending_config_page.py b/src/ui/pages/pending_config_page.py new file mode 100644 index 0000000..aa97098 --- /dev/null +++ b/src/ui/pages/pending_config_page.py @@ -0,0 +1,96 @@ +""" +待定功能配置页面 +""" + +import customtkinter as ctk + +from .base_page import BasePage +from ...core.tasks.pending_tasks import PendingTaskRunner + + +class PendingConfigPage(BasePage): + """待定功能配置页面""" + + def __init__(self, parent: ctk.CTkFrame, app: "LuoLuoApp"): + super().__init__(parent, app) + self.task_runner = PendingTaskRunner() + self.switches = {} + + def build(self) -> ctk.CTkFrame: + """构建页面""" + frame = ctk.CTkFrame(self.parent, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + + # 标题 + title = ctk.CTkLabel( + frame, + text="⏳ 待定功能配置", + font=ctk.CTkFont(size=18, weight="bold") + ) + title.grid(row=0, column=0, padx=20, pady=(20, 10), sticky="w") + + # 说明 + desc = ctk.CTkLabel( + frame, + text="预留功能配置(待开发)", + font=ctk.CTkFont(size=12), + text_color="gray" + ) + desc.grid(row=1, column=0, padx=20, pady=(0, 20), sticky="w") + + # 功能列表 + features_frame = ctk.CTkFrame(frame) + features_frame.grid(row=2, column=0, sticky="ew", padx=20, pady=10) + features_frame.grid_columnconfigure(0, weight=1) + + features = self.task_runner.get_features() + for idx, (feature_id, config) in enumerate(features.items()): + self._create_feature_item(features_frame, feature_id, config, idx) + + # 提示信息 + info_frame = ctk.CTkFrame(frame, fg_color=("#fff3cd", "#3d3a2a")) + info_frame.grid(row=3, column=0, sticky="ew", padx=20, pady=20) + + info_label = ctk.CTkLabel( + info_frame, + text="💡 这些功能正在开发中,敬请期待!", + font=ctk.CTkFont(size=12) + ) + info_label.pack(padx=20, pady=15) + + return frame + + def _create_feature_item(self, parent, feature_id: str, config: dict, row: int): + """创建功能项""" + frame = ctk.CTkFrame(parent, fg_color="transparent") + frame.grid(row=row, column=0, sticky="ew", padx=10, pady=5) + frame.grid_columnconfigure(1, weight=1) + + # 开关(禁用状态) + switch = ctk.CTkSwitch( + frame, + text="", + width=50, + state="disabled" + ) + switch.grid(row=0, column=0, padx=(0, 10)) + if config["enabled"]: + switch.select() + self.switches[feature_id] = switch + + # 名称 + name_label = ctk.CTkLabel( + frame, + text=config["name"], + font=ctk.CTkFont(size=13, weight="bold") + ) + name_label.grid(row=0, column=1, sticky="w") + + # 描述 + desc_label = ctk.CTkLabel( + frame, + text=config["description"], + font=ctk.CTkFont(size=11), + text_color="gray" + ) + desc_label.grid(row=1, column=1, sticky="w") diff --git a/src/ui/pages/run_page.py b/src/ui/pages/run_page.py new file mode 100644 index 0000000..3038d9e --- /dev/null +++ b/src/ui/pages/run_page.py @@ -0,0 +1,200 @@ +""" +运行页面 - 主控制面板 +包含基础配置和运行控制 +""" + +import customtkinter as ctk +from src.utils.logger import logger + +from .base_page import BasePage +from ...core.automation import AutomationController + + +class RunPage(BasePage): + """运行页面""" + + def __init__(self, parent: ctk.CTkFrame, app: "LuoLuoApp"): + super().__init__(parent, app) + self.automation = AutomationController() + self.automation.set_callback(self._on_automation_message) + + self.log_textbox: Optional[ctk.CTkTextbox] = None + + def build(self) -> ctk.CTkFrame: + """构建运行页面""" + frame = ctk.CTkFrame(self.parent, fg_color="transparent") + frame.grid_columnconfigure(0, weight=1) + frame.grid_rowconfigure(3, weight=1) + + # ===== 基础配置区域 ===== + config_frame = ctk.CTkFrame(frame) + config_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) + config_frame.grid_columnconfigure(1, weight=1) + + ctk.CTkLabel( + config_frame, + text="⚙️ 基础配置", + font=ctk.CTkFont(size=14, weight="bold") + ).grid(row=0, column=0, columnspan=3, padx=10, pady=(10, 5), sticky="w") + + # 游戏窗口 + ctk.CTkLabel(config_frame, text="游戏窗口:").grid(row=1, column=0, padx=10, pady=5, sticky="w") + self.window_label = ctk.CTkLabel(config_frame, text="未捕获", text_color="gray") + self.window_label.grid(row=1, column=1, padx=10, pady=5, sticky="w") + self.capture_btn = ctk.CTkButton( + config_frame, + text="🔍 重新捕获", + width=100, + command=self._capture_window + ) + self.capture_btn.grid(row=1, column=2, padx=10, pady=5) + + # 运行热键 + ctk.CTkLabel(config_frame, text="运行热键:").grid(row=2, column=0, padx=10, pady=5, sticky="w") + self.hotkey_entry = ctk.CTkEntry(config_frame, placeholder_text="F9") + self.hotkey_entry.insert(0, "F9") + self.hotkey_entry.grid(row=2, column=1, padx=10, pady=5, sticky="w") + + # 日志等级 + ctk.CTkLabel(config_frame, text="日志等级:").grid(row=3, column=0, padx=10, pady=5, sticky="w") + self.loglevel_combo = ctk.CTkComboBox( + config_frame, + values=["DEBUG", "INFO", "WARNING", "ERROR"], + width=120 + ) + self.loglevel_combo.set("INFO") + self.loglevel_combo.grid(row=3, column=1, padx=10, pady=5, sticky="w") + + # ===== 窗口状态区域 ===== + status_frame = ctk.CTkFrame(frame) + status_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5) + + self.status_label = ctk.CTkLabel( + status_frame, + text="🎮 等待捕获游戏窗口...", + font=ctk.CTkFont(size=12) + ) + self.status_label.pack(padx=20, pady=15) + + # ===== 控制按钮区域 ===== + control_frame = ctk.CTkFrame(frame, fg_color="transparent") + control_frame.grid(row=2, column=0, sticky="ew", padx=5, pady=10) + + self.start_btn = ctk.CTkButton( + control_frame, + text="🚀 开始挂机", + font=ctk.CTkFont(size=14, weight="bold"), + height=40, + fg_color="#2ecc71", + hover_color="#27ae60", + command=self._start_automation + ) + self.start_btn.pack(side="left", padx=5) + + self.stop_btn = ctk.CTkButton( + control_frame, + text="⏹ 停止", + font=ctk.CTkFont(size=14), + height=40, + fg_color="#e74c3c", + hover_color="#c0392b", + state="disabled", + command=self._stop_automation + ) + self.stop_btn.pack(side="left", padx=5) + + self.pause_btn = ctk.CTkButton( + control_frame, + text="⏸ 暂停", + font=ctk.CTkFont(size=14), + height=40, + state="disabled", + command=self._pause_automation + ) + self.pause_btn.pack(side="left", padx=5) + + # ===== 日志区域 ===== + log_frame = ctk.CTkFrame(frame) + log_frame.grid(row=3, column=0, sticky="nsew", padx=5, pady=5) + log_frame.grid_columnconfigure(0, weight=1) + log_frame.grid_rowconfigure(1, weight=1) + + ctk.CTkLabel( + log_frame, + text="📋 运行日志", + font=ctk.CTkFont(size=12, weight="bold") + ).grid(row=0, column=0, padx=10, pady=(10, 5), sticky="w") + + self.log_textbox = ctk.CTkTextbox( + log_frame, + wrap="word", + state="disabled", + font=ctk.CTkFont(size=11) + ) + self.log_textbox.grid(row=1, column=0, sticky="nsew", padx=10, pady=(0, 10)) + + # 尝试自动捕获窗口 + self._capture_window() + + return frame + + def _capture_window(self): + """捕获游戏窗口""" + if self.automation.window_manager.capture_window(): + size = self.automation.window_manager.client_size + self.window_label.configure( + text=f"桃源深处有人家 - {size[0]}x{size[1]}", + text_color="#2ecc71" + ) + self.status_label.configure( + text=f"✅ 已捕获游戏窗口 ({size[0]}x{size[1]})", + text_color="#2ecc71" + ) + self.app.set_status("窗口已捕获") + else: + self.window_label.configure(text="未捕获", text_color="#e74c3c") + self.status_label.configure( + text="❌ 未找到游戏窗口,请确保游戏已运行", + text_color="#e74c3c" + ) + self.app.set_status("未找到游戏窗口") + + def _start_automation(self): + """开始自动化""" + self.automation.start() + self.start_btn.configure(state="disabled") + self.stop_btn.configure(state="normal") + self.pause_btn.configure(state="normal", text="⏸ 暂停") + self.app.set_status("运行中") + + def _stop_automation(self): + """停止自动化""" + self.automation.stop() + self.start_btn.configure(state="normal") + self.stop_btn.configure(state="disabled") + self.pause_btn.configure(state="disabled", text="⏸ 暂停") + self.app.set_status("已停止") + + def _pause_automation(self): + """暂停/继续""" + self.automation.pause() + if self.automation.is_paused: + self.pause_btn.configure(text="▶ 继续") + self.app.set_status("已暂停") + else: + self.pause_btn.configure(text="⏸ 暂停") + self.app.set_status("运行中") + + def _on_automation_message(self, message: str): + """接收自动化消息""" + self._add_log(message) + + def _add_log(self, message: str): + """添加日志""" + if self.log_textbox: + from datetime import datetime + timestamp = datetime.now().strftime("%H:%M:%S") + self.log_textbox.configure(state="normal") + self.log_textbox.insert("end", f"[{timestamp}] {message}\n") + self.log_textbox.see("end") + self.log_textbox.configure(state="disabled") diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..40ef64c --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,8 @@ +""" +工具模块 +""" + +from .logger import setup_logger +from .config import ConfigManager + +__all__ = ["setup_logger", "ConfigManager"] diff --git a/src/utils/config.py b/src/utils/config.py new file mode 100644 index 0000000..18f7294 --- /dev/null +++ b/src/utils/config.py @@ -0,0 +1,93 @@ +""" +配置管理 +""" + +import json +from pathlib import Path +from typing import Any, Dict +from src.utils.logger import logger + + +class ConfigManager: + """配置管理器""" + + # 默认配置 + DEFAULT_CONFIG = { + "version": "0.1", + "window": { + "width": 900, + "height": 650, + "theme": "System", # System, Light, Dark + }, + "automation": { + "hotkey": "F9", + "log_level": "INFO", + }, + "tasks": { + "daily": {}, + "misc": {}, + "pending": {}, + } + } + + def __init__(self, config_path: Path = None): + if config_path is None: + config_path = Path(__file__).parent.parent.parent / "configs" / "settings.json" + + self.config_path = config_path + self.config_path.parent.mkdir(exist_ok=True) + + self._config = self.DEFAULT_CONFIG.copy() + self.load() + + def load(self): + """加载配置""" + if self.config_path.exists(): + try: + with open(self.config_path, "r", encoding="utf-8") as f: + loaded = json.load(f) + self._config.update(loaded) + logger.info(f"配置已加载: {self.config_path}") + except Exception as e: + logger.error(f"加载配置失败: {e}") + self.save() # 保存默认配置 + else: + self.save() # 首次运行,保存默认配置 + + def save(self): + """保存配置""" + try: + with open(self.config_path, "w", encoding="utf-8") as f: + json.dump(self._config, f, indent=4, ensure_ascii=False) + logger.info(f"配置已保存: {self.config_path}") + except Exception as e: + logger.error(f"保存配置失败: {e}") + + def get(self, key: str, default: Any = None) -> Any: + """获取配置项""" + keys = key.split(".") + value = self._config + + for k in keys: + if isinstance(value, dict) and k in value: + value = value[k] + else: + return default + + return value + + def set(self, key: str, value: Any): + """设置配置项""" + keys = key.split(".") + config = self._config + + for k in keys[:-1]: + if k not in config: + config[k] = {} + config = config[k] + + config[keys[-1]] = value + + def get_all(self) -> Dict: + """获取所有配置""" + return self._config.copy() diff --git a/src/utils/logger.py b/src/utils/logger.py new file mode 100644 index 0000000..ccd84a3 --- /dev/null +++ b/src/utils/logger.py @@ -0,0 +1,82 @@ +""" +日志配置 - 使用标准库logging +""" + +import sys +import logging +from pathlib import Path +from datetime import datetime + + +def setup_logger(log_dir: Path = None, level: str = "INFO"): + """ + 配置日志系统 + + Args: + log_dir: 日志文件目录,默认为项目根目录下的logs文件夹 + level: 日志级别 + """ + if log_dir is None: + log_dir = Path(__file__).parent.parent.parent / "logs" + + log_dir.mkdir(exist_ok=True) + + # 设置日志级别 + log_level = getattr(logging, level.upper(), logging.INFO) + + # 配置根日志记录器 + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + + # 清除现有处理器 + logger.handlers.clear() + + # 控制台处理器 + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(log_level) + console_format = logging.Formatter( + '%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d - %(message)s', + datefmt='%H:%M:%S' + ) + console_handler.setFormatter(console_format) + logger.addHandler(console_handler) + + # 文件处理器 + log_file = log_dir / f"luoluo_{datetime.now().strftime('%Y-%m-%d')}.log" + file_handler = logging.FileHandler(log_file, encoding='utf-8') + file_handler.setLevel(logging.DEBUG) + file_format = logging.Formatter( + '%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + file_handler.setFormatter(file_format) + logger.addHandler(file_handler) + + return logger + + +# 创建一个简单的logger别名,兼容loguru的接口 +class SimpleLogger: + """简单的logger包装类,提供类似loguru的接口""" + + def __init__(self): + self._logger = logging.getLogger("LuoLuoTool") + + def debug(self, message): + self._logger.debug(message) + + def info(self, message): + self._logger.info(message) + + def warning(self, message): + self._logger.warning(message) + + def error(self, message): + self._logger.error(message) + + def critical(self, message): + self._logger.critical(message) + + +# 全局logger实例 +logger = SimpleLogger()