import os import time import re import logging from config import CLIENT_LOG_PATH, CHAT_MESSAGES, TRADE_MESSAGES_CONFIG from message_sender import MessageSender import threading import signal import pystray from PIL import Image, ImageDraw import tempfile # 配置日志 def setup_logging(): log_file = "poe_forwarder.log" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_file, encoding='utf-8'), logging.StreamHandler() ] ) return logging.getLogger(__name__) def create_icon(): """创建一个更美观的图标""" width = 64 height = 64 # 创建新图像,使用RGBA支持透明 image = Image.new('RGBA', (width, height), (0, 0, 0, 0)) dc = ImageDraw.Draw(image) # 定义颜色 primary_color = (65, 105, 225) # 皇家蓝 secondary_color = (30, 144, 255) # 道奇蓝 # 绘制主圆形 dc.ellipse([4, 4, width-4, height-4], fill=primary_color) # 绘制内部装饰 # 绘制字母"P" font_size = 32 try: from PIL import ImageFont font = ImageFont.truetype("arial.ttf", font_size) except: font = None text = "P" text_bbox = dc.textbbox((0, 0), text, font=font) text_width = text_bbox[2] - text_bbox[0] text_height = text_bbox[3] - text_bbox[1] x = (width - text_width) // 2 y = (height - text_height) // 2 dc.text((x, y), text, fill='white', font=font) return image class TrayIcon: def __init__(self, logger): self.logger = logger self.running = True self.icon = None self.create_icon() def create_icon(self): image = create_icon() menu = ( pystray.MenuItem( "POE消息转发器", lambda: None, enabled=False ), pystray.Menu.SEPARATOR, pystray.MenuItem( "✓ 正在运行", lambda: None, enabled=False ), pystray.MenuItem( "📝 查看日志", self.open_log ), pystray.Menu.SEPARATOR, pystray.MenuItem( "⚙ 打开配置", self.open_config ), pystray.MenuItem( "❌ 退出", self.stop_application ) ) self.icon = pystray.Icon( "poe_forwarder", image, "POE消息转发器", menu ) def open_log(self): """打开日志文件""" try: os.startfile("poe_forwarder.log") except Exception as e: self.logger.error(f"打开日志文件失败: {str(e)}") def open_config(self): """打开配置文件""" try: os.startfile("config.json") except Exception as e: self.logger.error(f"打开配置文件失败: {str(e)}") def stop_application(self): """停止应用""" self.logger.info("从托盘菜单退出程序") self.running = False self.icon.stop() def run(self): """运行托盘图标""" self.icon.run() class TradeNotificationHandler: def __init__(self, message_sender, logger): self.sender = message_sender self.last_position = 0 self.logger = logger def check_new_trades(self): try: with open(CLIENT_LOG_PATH, 'r', encoding='utf-8', errors='ignore') as f: # 获取当前文件大小 f.seek(0, 2) current_position = f.tell() # 如果文件大小变小了,说明文件被重置 if current_position < self.last_position: self.logger.info(f"检测到文件重置") self.last_position = 0 # 重置到文件开始 # 如果有新内容 if current_position > self.last_position: # 只读取新增的内容 f.seek(self.last_position) new_content = f.read() # 更新位置(在处理内容之前) self.last_position = current_position # 按行处理新内容 lines = new_content.splitlines() for line in lines: if '@來自' in line or '@From' in line: self.logger.info(f"发现新消息: {line[:100]}...") self.process_trade_message(line) except Exception as e: self.logger.error(f"读取日志文件时发生错误: {str(e)}") def process_trade_message(self, message): self.logger.info("\n开始处理消息...") # 首先判断消息类型 if '我想購買' in message or 'would like to buy' in message: self.logger.info("检测到交易消息") trade_patterns = [ # 繁体中文格式 { 'pattern': r'.*\[INFO Client \d+\] @來自 ([^:]+): [^,]*,我想購買 ([^標]+)標價 ([^在]+)在 [^(]*\(倉庫頁 "([^"]+)"; 位置: ([^)]+)\)', 'format': lambda m: { 'buyer': m.group(1), 'item': m.group(2).strip(), 'price': m.group(3).strip(), 'tab': m.group(4), 'position': m.group(5) } }, # 英文格式 { 'pattern': r'.*\[INFO Client \d+\] @From ([^:]+): Hi, I would like to buy your ([^listed]+)listed for ([^in]+)in [^(]*\(stash tab "([^"]+)"; position: ([^)]+)\)', 'format': lambda m: { 'buyer': m.group(1), 'item': m.group(2).strip(), 'price': m.group(3).strip(), 'tab': m.group(4), 'position': m.group(5) } } ] for pattern_info in trade_patterns: match = re.match(pattern_info['pattern'], message) if match: self.logger.info("匹配到交易消息模式") trade_info = pattern_info['format'](match) self.logger.info(f"解析的交易信息: {trade_info}") # 发送交易通知 if TRADE_MESSAGES_CONFIG['message_forward'] and self.sender: message_content = ( f"买家: {trade_info['buyer']}\n" f"商品: {trade_info['item']}\n" f"价格: {trade_info['price']}\n" f"仓库: {trade_info['tab']}\n" f"位置: {trade_info['position']}" ) self.sender.send_message( content=message_content, title="POE 交易通知" ) break else: self.logger.info("检测到私聊消息") # 处理私聊消息 chat_patterns = [ # 繁体中文格式 r'.*\[INFO Client \d+\] @來自 ([^:]+): (.*)', # 英文格式 r'.*\[INFO Client \d+\] @From ([^:]+): (.*)' ] for pattern in chat_patterns: match = re.match(pattern, message) if match: sender = match.group(1) content = match.group(2) # 发送私聊消息 if CHAT_MESSAGES['message_forward']: message_content = ( f"发送者:{sender}\n" f"内容:{content}\n" f"时间:{time.strftime('%H:%M:%S')}" ) self.sender.send_message( content=message_content, title="POE 私聊消息" ) break def monitor_file(handler, is_running): handler.logger.info("文件监控已启动") try: while is_running(): handler.check_new_trades() time.sleep(0.1) except Exception as e: handler.logger.error(f"监控线程异常: {str(e)}") handler.logger.info("监控线程已停止") def main(): logger = setup_logging() logger.info("程序启动...") # 创建托盘图标 tray = TrayIcon(logger) logger.info("初始化消息发送器...") message_sender = MessageSender() logger.info(f"开始监控日志文件: {CLIENT_LOG_PATH}") handler = TradeNotificationHandler(message_sender, logger) # 确保文件存在 if not os.path.exists(CLIENT_LOG_PATH): logger.error(f"错误:找不到日志文件 {CLIENT_LOG_PATH}") return try: with open(CLIENT_LOG_PATH, 'r', encoding='utf-8', errors='ignore') as f: f.seek(0, 2) handler.last_position = f.tell() logger.info(f"初始文件位置: {handler.last_position}") except Exception as e: logger.error(f"读取文件位置时发生错误: {str(e)}") return # 在新线程中运行文件监控 monitor_thread = threading.Thread( target=monitor_file, args=(handler, lambda: tray.running) ) monitor_thread.daemon = True monitor_thread.start() # 运行托盘图标(这会阻塞主线程) tray.run() logger.info("程序已退出") if __name__ == "__main__": main()