299 lines
9.9 KiB
Python
299 lines
9.9 KiB
Python
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() |