Files
niumasoftware/shortcut_target.py
T
2026-04-03 21:50:36 +08:00

124 lines
3.9 KiB
Python

"""将用户选中的 .lnk / .url 解析为实际要存储的路径(目标程序或 URL)。"""
import base64
import json
import os
import re
import subprocess
import sys
def _looks_like_shell_lnk(path: str) -> bool:
"""Windows Shell Link 文件头为 4C 00 00 00(扩展名异常时仍可识别)。"""
try:
with open(path, "rb") as f:
return f.read(4) == b"L\x00\x00\x00"
except OSError:
return False
def _read_internet_shortcut_url(path: str) -> str | None:
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
s = line.strip()
if s[:4].upper() == "URL=":
return s[4:].strip()
except OSError:
pass
return None
def _resolve_windows_lnk(lnk_path: str) -> str | None:
if sys.platform != "win32":
return None
env = os.environ.copy()
env["LNK_B64"] = base64.b64encode(os.path.normpath(lnk_path).encode("utf-8")).decode(
"ascii"
)
ps = (
"$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($env:LNK_B64));"
"$sc=(New-Object -ComObject WScript.Shell).CreateShortcut($p);"
"@{ t=[string]$sc.TargetPath; a=[string]$sc.Arguments }|ConvertTo-Json -Compress"
)
try:
r = subprocess.run(
[
"powershell",
"-NoProfile",
"-Sta",
"-Command",
ps,
],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
env=env,
timeout=15,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
raw = (r.stdout or "").strip().lstrip("\ufeff")
if not raw:
return None
data = json.loads(raw)
target = (data.get("t") or "").strip()
args = (data.get("a") or "").strip()
if not target:
return None
low = target.lower()
if low.endswith("cmd.exe") or low.endswith("\\cmd.exe"):
for m in re.finditer(
r"([a-zA-Z]:[^\"'\s]+\.(?:bat|cmd))|\x22([a-zA-Z]:[^\"]+\.(?:bat|cmd))\x22",
args,
re.IGNORECASE,
):
cand = (m.group(1) or m.group(2) or "").strip('"')
if cand and os.path.isfile(cand):
return os.path.normpath(cand)
if os.path.isfile(target) or low.startswith("\\\\"):
return os.path.normpath(target)
return os.path.normpath(target)
except Exception:
return None
def path_for_storage(local_path: str) -> str:
"""
拖拽/选择文件时写入数据库的 path:.lnk/.url 解析为目标,其它保持原路径。
解析失败时退回本地路径。
"""
if not local_path:
return local_path
local_path = os.path.normpath(local_path.strip())
ext = os.path.splitext(local_path)[1].lower()
if ext == ".url":
try:
if os.path.isfile(local_path):
url = _read_internet_shortcut_url(local_path)
if url:
return url
except (OSError, ValueError):
pass
return local_path
try:
is_file = os.path.isfile(local_path)
except (OSError, ValueError):
is_file = False
is_lnk = ext in (".lnk", ".ink") or (
is_file and _looks_like_shell_lnk(local_path)
)
if is_lnk and is_file:
resolved = _resolve_windows_lnk(local_path)
if resolved:
return resolved
return local_path
def item_name_from_sources(local_path: str, store_path: str) -> str:
"""列表显示名:仍用快捷方式/所选文件的文件名(无扩展名),与原来行为一致。"""
base = os.path.splitext(os.path.basename(local_path))[0]
return base or os.path.basename(store_path)