feat: 添加 POE-AutoFlask 项目
- 新增主程序文件 main.py - 添加项目图标和资源文件 - 实现自动喝药和自定义按键功能 - 添加异常处理和程序打包配置 - 编写项目 README 和依赖列表
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
build
|
||||
dist
|
||||
.venv
|
||||
PyQt-Fluent-Widgets
|
||||
.idea
|
||||
39
POE-AutoFlask.spec
Normal file
39
POE-AutoFlask.spec
Normal file
@ -0,0 +1,39 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('icons/icon.png', 'icons')],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='POE-AutoFlask',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=['icons\\icon.ico'],
|
||||
)
|
||||
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
pip install -r requirements.txt
|
||||
|
||||
pyinstaller --name=POE-AutoFlask --onefile main.py --noconsole --add-data "icons/icon.png;icons" --icon=icons/icon.ico --clean
|
||||
BIN
icons/icon.ico
Normal file
BIN
icons/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
icons/icon.png
Normal file
BIN
icons/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
409
main.py
Normal file
409
main.py
Normal file
@ -0,0 +1,409 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import keyboard
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QKeySequence, QIcon, QIntValidator
|
||||
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout,
|
||||
QDialog, QMessageBox)
|
||||
from qfluentwidgets import (ComboBox, LineEdit, CheckBox, PushButton, BodyLabel, StrongBodyLabel,
|
||||
HeaderCardWidget)
|
||||
from qfluentwidgets.common import setTheme, Theme
|
||||
|
||||
|
||||
# 获取资源文件路径的函数
|
||||
def resource_path(relative_path):
|
||||
"""返回打包或开发环境下的资源路径"""
|
||||
if hasattr(sys, '_MEIPASS'):
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.abspath("."), relative_path)
|
||||
|
||||
# KeyListenerOne: 负责功能区1(按“`”键模拟勾选的按键)
|
||||
class KeyListenerOne(QThread):
|
||||
def __init__(self, keys_to_send, delay):
|
||||
super().__init__()
|
||||
self.running = True # 控制线程运行状态
|
||||
self.is_active = True # 由App类控制的全局激活状态
|
||||
self.keys_to_send = keys_to_send # 要模拟的按键列表
|
||||
self.delay = delay / 1000.0 # 按键之间的延迟(秒)
|
||||
|
||||
def run(self):
|
||||
# 此线程不再注册F2热键,F2由App类全局管理
|
||||
while self.running:
|
||||
# 只有当全局激活状态为True且“`”键被按下时才执行
|
||||
if self.is_active and keyboard.is_pressed('`'):
|
||||
# 模拟勾选的按键
|
||||
for key in self.keys_to_send:
|
||||
keyboard.write(key)
|
||||
time.sleep(self.delay) # 按键之间的延迟
|
||||
# 功能区1不再需要每轮循环延迟
|
||||
time.sleep(0.01) # 避免CPU占用过高
|
||||
|
||||
def set_active(self, state):
|
||||
"""设置功能区1的激活状态。"""
|
||||
self.is_active = state
|
||||
|
||||
def stop(self):
|
||||
"""停止线程运行。"""
|
||||
self.running = False
|
||||
|
||||
|
||||
# KeyListenerTwo: 负责功能区2(启停循环自动输入)
|
||||
class KeyListenerTwo(QThread):
|
||||
# 添加一个信号,用于在状态改变时通知主UI
|
||||
status_changed = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, auto_keys, auto_delay, auto_loop_delay, auto_key_trigger):
|
||||
super().__init__()
|
||||
self.running = True # 控制线程运行状态
|
||||
self.is_active = True # 由App类控制的全局激活状态
|
||||
self.individual_auto_toggle = False # 功能区2独立的启停状态
|
||||
self.auto_keys = auto_keys # 自动输入的按键列表
|
||||
self.auto_delay = auto_delay / 1000.0 # 自动输入按键之间的延迟(秒)
|
||||
self.auto_loop_delay = auto_loop_delay / 1000.0 # 每轮自动输入循环之间的延迟(秒)
|
||||
self.auto_key_trigger = auto_key_trigger # 启停自动输入的触发按键
|
||||
|
||||
def run(self):
|
||||
# 注册功能区2独立的触发按键
|
||||
if self.auto_key_trigger:
|
||||
# 使用 try-except 避免重复注册热键时出错
|
||||
try:
|
||||
keyboard.add_hotkey(self.auto_key_trigger, self.toggle_individual_auto_typing)
|
||||
except Exception as e:
|
||||
print(f"Failed to register hotkey {self.auto_key_trigger}: {e}")
|
||||
|
||||
while self.running:
|
||||
# 只有当全局激活状态为True且功能区2被独立触发时才执行
|
||||
if self.is_active and self.individual_auto_toggle:
|
||||
# 执行自动输入
|
||||
for key in self.auto_keys:
|
||||
keyboard.write(key)
|
||||
time.sleep(self.auto_delay) # 自动输入按键之间的延迟
|
||||
time.sleep(self.auto_loop_delay) # 每轮自动输入循环后的延迟
|
||||
time.sleep(0.01) # 避免CPU占用过高
|
||||
|
||||
def set_active(self, state):
|
||||
"""设置功能区2的全局激活状态。"""
|
||||
self.is_active = state
|
||||
|
||||
def toggle_individual_auto_typing(self):
|
||||
"""切换功能区2的独立自动输入状态。"""
|
||||
self.individual_auto_toggle = not self.individual_auto_toggle
|
||||
# 发射信号,将新的状态(True为启动,False为停止)传递出去
|
||||
self.status_changed.emit(self.individual_auto_toggle)
|
||||
|
||||
def stop(self):
|
||||
"""停止线程运行并取消注册功能区2的触发热键。"""
|
||||
self.running = False
|
||||
if self.auto_key_trigger:
|
||||
try:
|
||||
# 尝试解绑热键
|
||||
keyboard.remove_hotkey(self.auto_key_trigger)
|
||||
except KeyError:
|
||||
pass # 忽略热键不存在的错误
|
||||
|
||||
|
||||
# KeyCaptureDialog: 用于捕获用户按键的对话框
|
||||
class KeyCaptureDialog(QDialog):
|
||||
def __init__(self, prompt, max_length=5):
|
||||
super().__init__()
|
||||
self.setWindowTitle(prompt)
|
||||
self.setModal(True)
|
||||
self.resize(300, 100)
|
||||
self.keys = [] # 捕获到的按键列表
|
||||
self.max_length = max_length # 最大捕获按键数量
|
||||
self.label = BodyLabel("请依次按下按键(最多{}个),按 Enter 完成".format(max_length), self)
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.label)
|
||||
self.setLayout(layout)
|
||||
self.start_listening()
|
||||
|
||||
def start_listening(self):
|
||||
self.show()
|
||||
self.grabKeyboard() # 捕获键盘输入
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
key = event.key()
|
||||
if key in (Qt.Key_Return, Qt.Key_Enter):
|
||||
self.done(0) # 完成对话框
|
||||
return
|
||||
|
||||
if len(self.keys) < self.max_length:
|
||||
key_seq = event.text() # 获取按键字符
|
||||
if key_seq:
|
||||
self.keys.append(key_seq)
|
||||
self.keys = list(set(self.keys))
|
||||
self.label.setText("已捕获: " + ', '.join(self.keys))
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.releaseKeyboard() # 释放键盘捕获
|
||||
event.accept()
|
||||
|
||||
|
||||
# TriggerCaptureDialog: 用于捕获触发按键的对话框
|
||||
class TriggerCaptureDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("设置启停按键")
|
||||
self.setModal(True)
|
||||
self.resize(300, 100)
|
||||
self.key = ''
|
||||
self.label = BodyLabel("请按下用于启停自动输入的按键组合(按 Enter 完成)...", self)
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.label)
|
||||
self.setLayout(layout)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
||||
self.done(0) # 完成对话框
|
||||
return
|
||||
|
||||
key_seq_str = QKeySequence(event.modifiers() | event.key()).toString(QKeySequence.NativeText)
|
||||
if key_seq_str and key_seq_str.lower() not in ('enter', 'return'):
|
||||
self.key = key_seq_str.lower()
|
||||
self.label.setText(f"已捕获: {self.key}")
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.releaseKeyboard() # 释放键盘捕获
|
||||
event.accept()
|
||||
|
||||
|
||||
# App: 主应用程序窗口
|
||||
class App(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("POE-AutoFlask")
|
||||
flags = Qt.Window | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
|
||||
self.setWindowFlags(flags)
|
||||
self.setFixedSize(391,543)
|
||||
self.setWindowIcon(QIcon(resource_path("icons/icon.png")))
|
||||
|
||||
# UI 元素初始化
|
||||
self._global_active = True
|
||||
self.label = StrongBodyLabel("状态:监听中 (F2 切换)", self)
|
||||
self.button = PushButton("停止监听 (F2) 切换", self)
|
||||
self.button.setEnabled(True)
|
||||
self.button.clicked.connect(self._toggle_global_active)
|
||||
|
||||
# 功能区1的复选框布局
|
||||
self.checkbox_layout = QHBoxLayout()
|
||||
self.checkboxes = []
|
||||
self.key_labels = ['1', '2', '3', '4', '5']
|
||||
for key in self.key_labels:
|
||||
checkbox = CheckBox(key)
|
||||
checkbox.setChecked(True)
|
||||
self.checkbox_layout.addWidget(checkbox)
|
||||
self.checkboxes.append(checkbox)
|
||||
|
||||
# 功能区1的按键间延迟设置
|
||||
self.delay_combobox = ComboBox()
|
||||
self.delay_options = ["50 ms", "100 ms", "200 ms", "300 ms", "500 ms", "1000 ms", "2000 ms", "5000 ms"]
|
||||
self.delay_combobox.addItems(self.delay_options)
|
||||
self.delay_combobox.setCurrentText("50 ms")
|
||||
self.delay_combobox.setToolTip("按键之间的延迟")
|
||||
|
||||
# 功能区1的布局 (移除每轮循环延迟)
|
||||
feature1_text = BodyLabel("按键间延迟(ms):", self)
|
||||
feature1_form = QHBoxLayout()
|
||||
feature1_form.addWidget(feature1_text,1)
|
||||
feature1_form.addWidget(self.delay_combobox,9)
|
||||
|
||||
|
||||
feature1_group = HeaderCardWidget()
|
||||
feature1_group.setBorderRadius(8)
|
||||
feature1_group.setTitle("按`键自动喝药,可勾选取消")
|
||||
feature1_layout = QVBoxLayout()
|
||||
feature1_layout.addLayout(self.checkbox_layout)
|
||||
feature1_layout.addLayout(feature1_form)
|
||||
feature1_group.viewLayout.addLayout(feature1_layout)
|
||||
|
||||
# 功能区2的初始化
|
||||
self.auto_keys = []
|
||||
self.auto_key_trigger = ''
|
||||
|
||||
self.auto_delay_combobox = ComboBox()
|
||||
self.auto_delay_combobox.addItems(self.delay_options)
|
||||
self.auto_delay_combobox.setCurrentText("50 ms")
|
||||
self.auto_delay_combobox.setToolTip("自动输入按键之间的延迟")
|
||||
|
||||
# --- 修改:将每轮循环延迟的 QComboBox 改为 QLineEdit ---
|
||||
self.auto_loop_delay_input = LineEdit() # 默认1000ms
|
||||
self.auto_loop_delay_input.setText("1000")
|
||||
self.auto_loop_delay_input.setValidator(QIntValidator(0, 999999)) # 限制只能输入0到999999的整数
|
||||
self.auto_loop_delay_input.setToolTip("每轮自动输入循环之间的延迟 (ms)")
|
||||
|
||||
self.auto_keys_button = PushButton("设置自动输入按键")
|
||||
self.auto_trigger_button = PushButton("设置启停按键")
|
||||
|
||||
self.auto_keys_button.clicked.connect(self.set_auto_keys)
|
||||
self.auto_trigger_button.clicked.connect(self.set_auto_trigger)
|
||||
|
||||
self.auto_keys_display = BodyLabel("当前自动输入按键: 无")
|
||||
self.auto_trigger_display = BodyLabel("当前启停按键: 无")
|
||||
|
||||
self.feature2_widgets_to_disable = [
|
||||
self.auto_delay_combobox,
|
||||
self.auto_loop_delay_input,
|
||||
self.auto_keys_button,
|
||||
self.auto_trigger_button,
|
||||
]
|
||||
|
||||
# 功能区2的布局
|
||||
feature2_form = QFormLayout()
|
||||
feature2_hLayout = QHBoxLayout()
|
||||
feature2_hLayout.addWidget(BodyLabel("按键间延迟(ms):", self),1)
|
||||
feature2_hLayout.addWidget(self.auto_delay_combobox,9)
|
||||
feature2_form.addRow(feature2_hLayout)
|
||||
feature2_hLayout2 = QHBoxLayout()
|
||||
feature2_hLayout2.addWidget(BodyLabel("每轮循环延迟(ms):", self),1)
|
||||
feature2_hLayout2.addWidget(self.auto_loop_delay_input,9)
|
||||
feature2_form.addRow(feature2_hLayout2)
|
||||
feature2_form.addRow(self.auto_keys_button)
|
||||
feature2_form.addRow(self.auto_keys_display)
|
||||
feature2_form.addRow(self.auto_trigger_button)
|
||||
feature2_form.addRow(self.auto_trigger_display)
|
||||
|
||||
feature2_group = HeaderCardWidget()
|
||||
feature2_group.setBorderRadius(8)
|
||||
feature2_group.setTitle("设置自定义启停,循环自动输入自定义")
|
||||
# feature2_group = QGroupBox("功能区2:启停循环自动输入")
|
||||
feature2_group.viewLayout.addLayout(feature2_form)
|
||||
self.feature2_group = feature2_group # 保存对group的引用
|
||||
|
||||
# 主布局
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.label)
|
||||
layout.addWidget(feature1_group)
|
||||
layout.addWidget(feature2_group)
|
||||
layout.addWidget(self.button)
|
||||
self.setLayout(layout)
|
||||
|
||||
# 初始化两个独立的监听线程
|
||||
self.listener_one = KeyListenerOne(self.get_enabled_keys(),
|
||||
int(self.delay_combobox.currentText().replace(" ms", "")))
|
||||
self.listener_one.start()
|
||||
|
||||
loop_delay_val = int(self.auto_loop_delay_input.text()) if self.auto_loop_delay_input.text() else 1000
|
||||
self.listener_two = KeyListenerTwo(self.auto_keys,
|
||||
int(self.auto_delay_combobox.currentText().replace(" ms", "")),
|
||||
loop_delay_val,
|
||||
self.auto_key_trigger)
|
||||
self.listener_two.status_changed.connect(self._update_feature2_ui_state)
|
||||
self.listener_two.start()
|
||||
|
||||
keyboard.add_hotkey('f2', self._toggle_global_active)
|
||||
self._update_status_label() # 初始化状态标签
|
||||
|
||||
for cb in self.checkboxes:
|
||||
cb.stateChanged.connect(self.update_keys)
|
||||
self.delay_combobox.currentTextChanged.connect(self.update_delay)
|
||||
self.auto_delay_combobox.currentTextChanged.connect(self.update_auto_delay)
|
||||
self.auto_loop_delay_input.textChanged.connect(self.update_auto_loop_delay)
|
||||
|
||||
def _toggle_global_active(self):
|
||||
self._global_active = not self._global_active
|
||||
self.listener_one.set_active(self._global_active)
|
||||
self.listener_two.set_active(self._global_active)
|
||||
self._update_status_label()
|
||||
|
||||
def _update_status_label(self):
|
||||
status_text = "状态:监听中 (F2 切换)" if self._global_active else "状态:已暂停监听 (F2 切换)"
|
||||
button_text = "暂停所有功能 (F2 切换)" if self._global_active else "启动所有功能 (F2 切换)"
|
||||
|
||||
self.label.setText(status_text)
|
||||
self.button.setText(button_text)
|
||||
|
||||
def _update_feature2_ui_state(self, is_running):
|
||||
for widget in self.feature2_widgets_to_disable:
|
||||
widget.setEnabled(not is_running)
|
||||
|
||||
def get_enabled_keys(self):
|
||||
return [cb.text() for cb in self.checkboxes if cb.isChecked()]
|
||||
|
||||
def update_keys(self):
|
||||
self.listener_one.keys_to_send = self.get_enabled_keys()
|
||||
|
||||
def update_delay(self):
|
||||
self.listener_one.delay = int(self.delay_combobox.currentText().replace(" ms", "")) / 1000.0
|
||||
|
||||
def update_auto_delay(self):
|
||||
self.listener_two.auto_delay = int(self.auto_delay_combobox.currentText().replace(" ms", "")) / 1000.0
|
||||
|
||||
def update_auto_loop_delay(self):
|
||||
try:
|
||||
delay_ms = int(self.auto_loop_delay_input.text())
|
||||
self.listener_two.auto_loop_delay = delay_ms / 1000.0
|
||||
except (ValueError, AttributeError):
|
||||
self.listener_two.auto_loop_delay = 1.0
|
||||
|
||||
def set_auto_keys(self):
|
||||
dialog = KeyCaptureDialog("设置自动输入按键")
|
||||
dialog.exec_()
|
||||
self.auto_keys = dialog.keys
|
||||
self.listener_two.auto_keys = self.auto_keys # 更新功能区2监听线程的按键
|
||||
self.auto_keys_display.setText("当前自动输入按键: " + ', '.join(self.auto_keys) if self.auto_keys else "无")
|
||||
|
||||
def set_auto_trigger(self):
|
||||
dialog = TriggerCaptureDialog()
|
||||
dialog.exec_()
|
||||
new_trigger_key = dialog.key
|
||||
|
||||
if new_trigger_key and new_trigger_key != self.auto_key_trigger:
|
||||
self.auto_key_trigger = new_trigger_key
|
||||
self.auto_trigger_display.setText(
|
||||
"当前启停按键: " + self.auto_key_trigger if self.auto_key_trigger else "无")
|
||||
|
||||
self.listener_two.stop()
|
||||
self.listener_two.wait()
|
||||
|
||||
loop_delay_val = int(self.auto_loop_delay_input.text()) if self.auto_loop_delay_input.text() else 1000
|
||||
self.listener_two = KeyListenerTwo(self.auto_keys,
|
||||
int(self.auto_delay_combobox.currentText().replace(" ms", "")),
|
||||
loop_delay_val,
|
||||
self.auto_key_trigger)
|
||||
self.listener_two.status_changed.connect(self._update_feature2_ui_state)
|
||||
self.listener_two.start()
|
||||
elif not new_trigger_key:
|
||||
self.auto_trigger_display.setText("当前启停按键: 无")
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.listener_one.stop()
|
||||
self.listener_one.wait() # 等待线程结束
|
||||
self.listener_two.stop()
|
||||
self.listener_two.wait() # 等待线程结束
|
||||
# 在程序退出时全局解绑所有热键,确保清理
|
||||
keyboard.unhook_all()
|
||||
event.accept()
|
||||
|
||||
|
||||
# show_exception_dialog: 全局异常处理函数
|
||||
def show_exception_dialog(exc_type, exc_value, exc_traceback):
|
||||
error_msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
|
||||
msg_box = QMessageBox()
|
||||
msg_box.setIcon(QMessageBox.Critical)
|
||||
msg_box.setWindowTitle("程序异常")
|
||||
msg_box.setText("程序发生异常:\n" + str(exc_value))
|
||||
msg_box.setDetailedText(error_msg)
|
||||
msg_box.setStandardButtons(QMessageBox.Retry | QMessageBox.Close)
|
||||
result = msg_box.exec_()
|
||||
if result == QMessageBox.Retry:
|
||||
python = sys.executable
|
||||
args = sys.argv[:]
|
||||
args.insert(0, python)
|
||||
sys.exit(subprocess.call(args))
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# 主程序入口
|
||||
if __name__ == '__main__':
|
||||
sys.excepthook = show_exception_dialog # 设置全局异常处理
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
setTheme(Theme.LIGHT) # 设置主题为自动模式
|
||||
window = App()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
Reference in New Issue
Block a user