POE2_Message_Forwarder/main.py

299 lines
9.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()