增加微信多开功能

This commit is contained in:
2026-04-07 18:54:31 +08:00
parent 3051f6ca49
commit 621d752a8d
7 changed files with 317 additions and 35 deletions
+69 -32
View File
@@ -16,7 +16,6 @@ UPDATE_CHECK_URL = "https://api.yunzer.cn/api/softwareupgrade/check?code=niumaso
def _current_version() -> str:
"""从 main 模块取当前版本号,避免循环导入。"""
try:
import importlib
m = importlib.import_module("__main__")
@@ -35,13 +34,12 @@ def _is_newer(latest: str, current: str) -> bool:
class _CheckWorker(QThread):
result = pyqtSignal(dict) # 成功:返回 data 字段
result = pyqtSignal(dict)
error = pyqtSignal(str)
def run(self):
try:
import json
import urllib.request
with urllib.request.urlopen(UPDATE_CHECK_URL, timeout=10) as resp:
body = json.loads(resp.read().decode())
if body.get("code") == 200:
@@ -53,59 +51,90 @@ class _CheckWorker(QThread):
class _DownloadWorker(QThread):
progress = pyqtSignal(int) # 0-100
finished = pyqtSignal(str) # 下载完成,返回临时文件路径
progress = pyqtSignal(int)
finished = pyqtSignal(str)
error = pyqtSignal(str)
def __init__(self, url: str):
def __init__(self, url: str, dest: str):
super().__init__()
self._url = url
self._url = url
self._dest = dest
def run(self):
try:
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".exe")
tmp.close()
dst = tmp.name
def _reporthook(count, block_size, total_size):
if total_size > 0:
pct = min(100, int(count * block_size * 100 / total_size))
self.progress.emit(pct)
urllib.request.urlretrieve(self._url, dst, _reporthook)
urllib.request.urlretrieve(self._url, self._dest, _reporthook)
self.progress.emit(100)
self.finished.emit(dst)
self.finished.emit(self._dest)
except Exception as e:
self.error.emit(str(e))
def _replace_and_restart(new_exe: str):
"""
写一个批处理脚本:等待当前进程退出 → 覆盖 exe → 启动新版。
仅在打包为 exe 时执行真正替换;开发环境直接启动下载文件。
onedir 模式:只替换 exe 本身,dll 等文件不变。
用 PowerShell 后台脚本等待原进程退出后替换并重启,完全无窗口。
"""
current_exe = sys.executable if getattr(sys, "frozen", False) else None
if current_exe:
bat = tempfile.NamedTemporaryFile(delete=False, suffix=".bat", mode="w", encoding="gbk")
bat.write(f"""@echo off
ping 127.0.0.1 -n 3 >nul
move /y "{new_exe}" "{current_exe}"
start "" "{current_exe}"
del "%~f0"
""")
bat.close()
subprocess.Popen(["cmd", "/c", bat.name], creationflags=subprocess.CREATE_NO_WINDOW)
else:
# 开发环境:直接运行下载的 exe
if not getattr(sys, "frozen", False):
subprocess.Popen([new_exe])
QApplication.quit()
return
current_exe = sys.executable
pid = os.getpid()
ps_script = f"""
$pid_target = {pid}
$src = '{new_exe.replace(chr(92), chr(92)*2)}'
$dst = '{current_exe.replace(chr(92), chr(92)*2)}'
# 等待原进程退出,最多 30 秒
$waited = 0
while ((Get-Process -Id $pid_target -ErrorAction SilentlyContinue) -and $waited -lt 30) {{
Start-Sleep -Milliseconds 500
$waited += 0.5
}}
# 重试覆盖,最多 10 次
$ok = $false
for ($i = 0; $i -lt 10; $i++) {{
try {{
Copy-Item -Path $src -Destination $dst -Force
$ok = $true
break
}} catch {{
Start-Sleep -Milliseconds 800
}}
}}
if ($ok) {{
Remove-Item $src -Force -ErrorAction SilentlyContinue
Start-Process $dst
}}
"""
tmp = tempfile.NamedTemporaryFile(
delete=False, suffix=".ps1", mode="w", encoding="utf-8"
)
tmp.write(ps_script)
tmp.close()
subprocess.Popen(
[
"powershell", "-WindowStyle", "Hidden",
"-NonInteractive", "-ExecutionPolicy", "Bypass",
"-File", tmp.name,
],
creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NO_WINDOW,
)
QApplication.quit()
class Updater(QObject):
"""对外接口:调用 check() 即可。"""
def __init__(self, parent=None):
super().__init__(parent)
self._parent_widget = parent
@@ -141,6 +170,7 @@ class Updater(QObject):
if ret != QMessageBox.StandardButton.Yes:
return
self._url = url
self._download(url)
def _on_check_error(self, err: str):
@@ -148,12 +178,19 @@ class Updater(QObject):
QMessageBox.warning(self._parent_widget, "检查更新失败", f"无法连接更新服务器:\n{err}")
def _download(self, url: str):
# 下载到原 exe 同目录,避免跨盘 move 失败
if getattr(sys, "frozen", False):
dest_dir = os.path.dirname(sys.executable)
else:
dest_dir = tempfile.gettempdir()
dest = os.path.join(dest_dir, "_update_new.exe")
self._progress = QProgressDialog("正在下载更新...", "取消", 0, 100, self._parent_widget)
self._progress.setWindowTitle("下载更新")
self._progress.setMinimumDuration(0)
self._progress.setValue(0)
self._dl_worker = _DownloadWorker(url)
self._dl_worker = _DownloadWorker(url, dest)
self._dl_worker.progress.connect(self._progress.setValue)
self._dl_worker.finished.connect(self._on_download_done)
self._dl_worker.error.connect(self._on_download_error)