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