POE2_Message_Forwarder/main.py

299 lines
9.9 KiB
Python
Raw Normal View History

2025-02-28 14:51:12 +08:00
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()