提交 032b54b7 authored 作者: DJCHAI's avatar DJCHAI

图片视频下载插件

上级
File added
# 依赖包
node_modules/
# 编辑器配置
.idea/
.vscode/
.trae/
.git
# 打包产物
dist/
build/
out/
\ No newline at end of file
File added
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDCcMfogXdiLDnm
KnhQhhRxEtyHAiOentzb1LtiiDVlvmSWrjTTrzIVIARH2phumCzqjKSa4D8V9gNt
fZ3DwcnoO2OnH30Da5SSKmYgsGnNTSmV4O1LHCK2Ygj8httwR6/bKt84twjpU2BY
UbO67soTUxNPfLCuDOLlAQKTqaYMmDjVauCYvYlcb4JToiSyRG1T1SaDIqErWN6l
WCmZ/3zO0W6uWUr4lve27ZVtwHJJ5/qnqvJpV0f/r/qgdghuN3l8qBY6fw/NUFAZ
TCIJ9pCFZfDL1f/z6pxFoofVMvfp2Rm6vFDcQe+FU2omzaNMb5BFVbQYh3HO3xCK
TBNn2j8JAgMBAAECggEAB+9fayd1xP+szQ4MkFhecccNJqSBf2CjPwSWxpnsLZwo
Nvws8KVzUpMhSuzlSwsiYcqOUPnn59hSZ2fIq34yDxcLCrKWu5q1SsSYm8rTRd3O
qFy667V+MamnzVAew7Ll6XiduORDBKGipTa6sjKP9DCwBq6zd6Y+kA2winCSPmyE
EoofJj472kPE0ElfNWgEi3VXt7LblhzvBASTKVtMBhn3YNE28tOeSDIaAhiNdoaJ
MNy8G1iYSvJVKvBz7Xh0q1lraAgDAC/cMpQt6ok1X2lVj33zP4MTCtnsVeKd5evk
nNNVNiqjcnaGbNaVVNhqxdQiiaznFlzSvXG2TItJgQKBgQD6eu+s3u2p0IltAFq6
n0dKvMBDddyWZ+9taOf3+VxtFsajDtyMmE3nyGgFRRg/0FGDSoAqGCKibDnNz1lV
gv0fRjgRGgp86i0DdEGuhuDdLzGC7aBH1lKQaOUs9c9AQf5pDol+pxfQG62wcAJS
ZYBhXWkFAuCdP2qddYzlQUTciQKBgQDGubOQwpPMJd1LMmf+neaeSYNykWySqSre
6bCfU3wamsHxyoLlXnbgYtBAQHgHnjYyofeSm6mTflHsUz+LdnXAZckERBsfc/zL
0q6OGdHVbrp/+7KeivGtKkEKR+/2yyZrUgrAe+wG9IYfG5Rq+NFQ6v0FP5LXEk0S
bMBip7KugQKBgQC6PZhiCCvcfPMBuia75lxi5K9sOYF3z/YFeuAn0CB5+FpfuVgh
rckXUzqM2JAsOp+xKD7dja6K6D+emNLKsye0Dkj35k3wDUrE67c+pokhzMpamIb5
gxFG0Me+Q1MDa05YHPf81Q7urPXbX51KWLA5NPQ2RqpuIU4Ln/DQMVSbSQKBgQC2
laxsFrqg2TFHg9qU0BMx361MOF0fKxrekKvc12GxN/t14nD9uRtT8rYQMquVpEF7
p+DF7QuTHn66uYoq4v4xddOJfeHFtlXQF5SVrxdiGEh2ipWEoXiHd8kmXw4VHHYC
KPEEBVfI1CXeswJcfpwi0qUCi3So8oGpqg3hFt9PAQKBgQCQIFlEHytyAQnWffeb
bJmhQ5oBlyIa6YtRtX7RyO3MMPnNyLA1Y0jaMSJDHSx/hSZ0ct04P7Ljk102VW0s
NmZZPjqC2QcnXX3Syt8/+6sVcoXVRRnNW6ivPfghD8YGILS1ptMK9MXjPPBRDqEi
e0/okTGL7tfDFOhW0Y7JfWoTdA==
-----END PRIVATE KEY-----
{
"manifest_version": 3,
"name": "Media Info & Downloader",
"version": "1.0.0",
"description": "显示图片和视频的相关信息,并提供下载功能",
"permissions": [
"downloads",
"storage"
],
"action": {
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"default_title": "Media Info & Downloader"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["./content.js"],
"css": ["./content.css"]
}
],
"background": {
"service_worker": "./background.js"
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"action": {
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"default_title": "Media Info & Downloader"
},
"browser_specific_settings": {
"gecko": {
"id": "media-info-downloader@example.com"
}
}
}
\ No newline at end of file
{
"name": "video_info",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@esbuild/android-arm": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
"integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
"integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
"dev": true,
"optional": true
},
"esbuild": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
"integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
"dev": true,
"requires": {
"@esbuild/android-arm": "0.15.18",
"@esbuild/linux-loong64": "0.15.18",
"esbuild-android-64": "0.15.18",
"esbuild-android-arm64": "0.15.18",
"esbuild-darwin-64": "0.15.18",
"esbuild-darwin-arm64": "0.15.18",
"esbuild-freebsd-64": "0.15.18",
"esbuild-freebsd-arm64": "0.15.18",
"esbuild-linux-32": "0.15.18",
"esbuild-linux-64": "0.15.18",
"esbuild-linux-arm": "0.15.18",
"esbuild-linux-arm64": "0.15.18",
"esbuild-linux-mips64le": "0.15.18",
"esbuild-linux-ppc64le": "0.15.18",
"esbuild-linux-riscv64": "0.15.18",
"esbuild-linux-s390x": "0.15.18",
"esbuild-netbsd-64": "0.15.18",
"esbuild-openbsd-64": "0.15.18",
"esbuild-sunos-64": "0.15.18",
"esbuild-windows-32": "0.15.18",
"esbuild-windows-64": "0.15.18",
"esbuild-windows-arm64": "0.15.18"
}
},
"esbuild-android-64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
"integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
"dev": true,
"optional": true
},
"esbuild-android-arm64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
"integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
"dev": true,
"optional": true
},
"esbuild-darwin-64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
"integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
"dev": true,
"optional": true
},
"esbuild-darwin-arm64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
"integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
"dev": true,
"optional": true
},
"esbuild-freebsd-64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
"integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
"dev": true,
"optional": true
},
"esbuild-freebsd-arm64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
"integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
"dev": true,
"optional": true
},
"esbuild-linux-32": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
"integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
"dev": true,
"optional": true
},
"esbuild-linux-64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
"integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
"dev": true,
"optional": true
},
"esbuild-linux-arm": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
"integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
"dev": true,
"optional": true
},
"esbuild-linux-arm64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
"integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
"dev": true,
"optional": true
},
"esbuild-linux-mips64le": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
"integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
"dev": true,
"optional": true
},
"esbuild-linux-ppc64le": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
"integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
"dev": true,
"optional": true
},
"esbuild-linux-riscv64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
"integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
"dev": true,
"optional": true
},
"esbuild-linux-s390x": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
"integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
"dev": true,
"optional": true
},
"esbuild-netbsd-64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
"integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
"dev": true,
"optional": true
},
"esbuild-openbsd-64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
"integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
"dev": true,
"optional": true
},
"esbuild-sunos-64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
"integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
"dev": true,
"optional": true
},
"esbuild-windows-32": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
"integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
"dev": true,
"optional": true
},
"esbuild-windows-64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
"integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
"dev": true,
"optional": true
},
"esbuild-windows-arm64": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
"integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
"dev": true,
"optional": true
},
"fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true
},
"hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"requires": {
"function-bind": "^1.1.2"
}
},
"is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dev": true,
"requires": {
"hasown": "^2.0.2"
}
},
"nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"postcss": {
"version": "8.5.8",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"dev": true,
"requires": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
}
},
"resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"dev": true,
"requires": {
"is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"rollup": {
"version": "2.80.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
"integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
}
},
"source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"vite": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.4.tgz",
"integrity": "sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==",
"dev": true,
"requires": {
"esbuild": "^0.15.9",
"fsevents": "~2.3.2",
"postcss": "^8.4.18",
"resolve": "^1.22.1",
"rollup": "^2.79.1"
}
}
}
}
{
"name": "video_info",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "vite",
"build": "vite build && cp src/content.css dist/ && cp manifest.json dist/ && cp -r icons dist/ && cp -r src/index.html dist/"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"vite": "^3.2.4"
}
}
// 存储默认设置
const defaultSettings = {
modifierKeys: ['altKey', 'metaKey'] // 默认支持Alt和Command键
};
// 初始化设置
chrome.runtime.onInstalled.addListener(() => {
chrome.storage.sync.set(defaultSettings, () => {
console.log('初始化默认设置:', defaultSettings);
});
});
// 监听下载请求
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'download') {
const { url, filename } = message;
console.log('下载请求:', message);
chrome.downloads.download({
url: url,
filename: filename,
saveAs: false
}, (downloadId) => {
if (chrome.runtime.lastError) {
console.error('下载失败:', chrome.runtime.lastError);
sendResponse({ success: false, error: chrome.runtime.lastError.message });
} else {
console.log('下载开始,ID:', downloadId);
sendResponse({ success: true, downloadId: downloadId });
}
});
return true; // 表示异步响应
} else if (message.action === 'getSettings') {
// 获取设置
chrome.storage.sync.get('modifierKeys', (data) => {
sendResponse({
modifierKeys: data.modifierKeys || defaultSettings.modifierKeys
});
});
return true;
}
});
// 监听插件图标点击事件
chrome.action.onClicked.addListener((tab) => {
// 切换修饰键设置
chrome.storage.sync.get('modifierKeys', (data) => {
const currentKeys = data.modifierKeys || defaultSettings.modifierKeys;
let newKeys;
// 循环切换按键设置
if (JSON.stringify(currentKeys) === JSON.stringify(['altKey', 'metaKey'])) {
newKeys = ['ctrlKey', 'metaKey']; // Alt+Command → Ctrl+Command
} else if (JSON.stringify(currentKeys) === JSON.stringify(['ctrlKey', 'metaKey'])) {
newKeys = ['shiftKey']; // Ctrl+Command → Shift
} else {
newKeys = ['altKey', 'metaKey']; // 其他 → Alt+Command
}
chrome.storage.sync.set({ modifierKeys: newKeys }, () => {
console.log('切换按键设置:', newKeys);
// 向当前标签页发送设置变更通知
chrome.tabs.sendMessage(tab.id, {
action: 'settingsChanged',
modifierKeys: newKeys
});
// 显示通知
const keyNames = {
'altKey': 'Alt',
'metaKey': 'Command',
'ctrlKey': 'Ctrl',
'shiftKey': 'Shift'
};
const keyText = newKeys.map(key => keyNames[key]).join('+');
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'Media Info & Downloader',
message: `已切换按键设置: ${keyText}`
});
});
});
});
\ No newline at end of file
#media-info-panel {
position: fixed;
background: rgba(255, 255, 255, 0.95);
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
font-size: 12px;
color: #333;
z-index: 999999;
display: none;
opacity: 0;
transition: opacity 0.3s ease;
max-width: 250px;
font-family: Arial, sans-serif;
}
#download-btn {
margin-top: 8px;
padding: 4px 8px;
background: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
}
#download-btn:hover {
background: #45a049;
}
\ No newline at end of file
let currentElement = null;
let hideTimeout = null;
const createInfoPanel = () => {
const panel = document.createElement('div');
panel.id = 'media-info-panel';
panel.style.cssText = `
position: fixed;
background: rgba(255, 255, 255, 0.95);
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
font-size: 12px;
color: #333;
z-index: 999999;
display: none;
opacity: 0;
transition: opacity 0.3s ease;
max-width: 250px;
font-family: Arial, sans-serif;
`;
document.body.appendChild(panel);
return panel;
};
const getFileSize = async (url) => {
try {
const response = await fetch(url, { method: 'HEAD' });
const contentLength = response.headers.get('content-length');
if (contentLength) {
const size = parseInt(contentLength);
if (size < 1024) return `${size} B`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`;
return `${(size / (1024 * 1024)).toFixed(2)} MB`;
}
return '未知';
} catch {
return '未知';
}
};
// 根据URL识别文件类型
const getFileTypeFromUrl = (url) => {
// 从URL参数中提取mime_type
const mimeMatch = url.match(/mime_type=([^&]+)/);
if (mimeMatch) {
const mime = mimeMatch[1].toLowerCase();
if (mime.includes('image')) {
return mime.replace('image_', '');
} else if (mime.includes('video')) {
return mime.replace('video_', '');
}
}
// 从URL路径中提取文件扩展名
const pathMatch = url.match(/\.([^.\/?#]+)(?:[?#]|$)/i);
if (pathMatch) {
return pathMatch[1].toLowerCase();
}
// 从URL路径中提取可能的格式信息
const path = url.split('?')[0];
const segments = path.split('/');
const lastSegment = segments[segments.length - 1];
// https://p3-aio.ecombdimg.com/obj/ecom-shop-material/rVqEEgF_m_4c3a028f508afb9e6a61eaf751916efd_sx_238985_www1080-1080
// 检查常见的媒体文件格式
const commonFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'webm', 'mov', 'avi', 'mkv'];
for (const format of commonFormats) {
if (lastSegment.toLowerCase().includes(format)) {
return format;
}
}
return '未知';
};
const extractMediaInfo = async (element) => {
if (element.tagName === 'IMG') {
const src = element.src;
const width = element.naturalWidth || element.width;
const height = element.naturalHeight || element.height;
const format = getFileTypeFromUrl(src);
const size = await getFileSize(src);
return {
type: 'image',
src,
width,
height,
format,
size
};
} else if (element.tagName === 'VIDEO') {
const src = element.currentSrc || element.src;
const width = element.videoWidth || element.width;
const height = element.videoHeight || element.height;
const format = getFileTypeFromUrl(src);
const size = await getFileSize(src);
return {
type: 'video',
src,
width,
height,
format,
size
};
}
return null;
};
const showInfoPanel = async (element, event) => {
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
const panel = document.getElementById('media-info-panel') || createInfoPanel();
// 先显示面板,显示默认信息
panel.innerHTML = `
<div style="margin-bottom: 10px; font-weight: bold; text-align: center;">${element.tagName === 'IMG' ? '图片信息' : '视频信息'}</div>
<div style="margin-bottom: 4px;">尺寸: 加载中...</div>
<div style="margin-bottom: 4px;">格式: 加载中...</div>
<div style="margin-bottom: 8px;">大小: 加载中...</div>
<div style="display: flex; flex-direction: column; gap: 5px;">
<button id="download-btn" style="
padding: 6px 12px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
">下载</button>
<div style="font-size: 10px; color: #666; text-align: center;">按住Alt或Command键点击</div>
<div style="font-size: 10px; color: #666; text-align: center;">按ESC键隐藏信息</div>
</div>
`;
if (event) {
updatePanelPosition(event);
}
panel.style.display = 'block';
panel.offsetHeight;
panel.style.opacity = '1';
// 立即获取基本信息(不包含文件大小)
try {
const src = element.tagName === 'IMG' ? element.src : (element.currentSrc || element.src);
const width = element.tagName === 'IMG' ? (element.naturalWidth || element.width) : (element.videoWidth || element.width);
const height = element.tagName === 'IMG' ? (element.naturalHeight || element.height) : (element.videoHeight || element.height);
const format = getFileTypeFromUrl(src);
// 立即更新基本信息
panel.innerHTML = `
<div style="margin-bottom: 10px; font-weight: bold; text-align: center;">${element.tagName === 'IMG' ? '图片信息' : '视频信息'}</div>
<div style="margin-bottom: 4px;">尺寸: ${width} × ${height}</div>
<div style="margin-bottom: 4px;">格式: ${format}</div>
<div style="margin-bottom: 8px;">大小: 加载中...</div>
<div style="display: flex; flex-direction: column; gap: 5px;">
<button id="download-btn" style="
padding: 6px 12px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
">下载</button>
<div style="font-size: 10px; color: #666; text-align: center;">按住Alt或Command键点击</div>
<div style="font-size: 10px; color: #666; text-align: center;">按ESC键隐藏信息</div>
</div>
`;
// 添加下载按钮事件
document.getElementById('download-btn').addEventListener('click', () => {
chrome.runtime.sendMessage({
action: 'download',
url: src,
filename: `media_${Date.now()}.${format}`
});
});
// 后台获取文件大小
const size = await getFileSize(src);
// 更新文件大小
panel.innerHTML = `
<div style="margin-bottom: 10px; font-weight: bold; text-align: center;">${element.tagName === 'IMG' ? '图片信息' : '视频信息'}</div>
<div style="margin-bottom: 4px;">尺寸: ${width} × ${height}</div>
<div style="margin-bottom: 4px;">格式: ${format}</div>
<div style="margin-bottom: 8px;">大小: ${size}</div>
<div style="display: flex; flex-direction: column; gap: 5px;">
<button id="download-btn" style="
padding: 6px 12px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
">下载</button>
<div style="font-size: 10px; color: #666; text-align: center;">按住Alt或Command键点击</div>
<div style="font-size: 10px; color: #666; text-align: center;">按ESC键隐藏信息</div>
</div>
`;
// 重新添加下载按钮事件
document.getElementById('download-btn').addEventListener('click', () => {
chrome.runtime.sendMessage({
action: 'download',
url: src,
filename: `media_${Date.now()}.${format}`
});
});
} catch (error) {
// 发生错误时,显示基本信息
console.error('获取媒体信息失败:', error);
const src = element.tagName === 'IMG' ? element.src : (element.currentSrc || element.src);
const width = element.tagName === 'IMG' ? (element.naturalWidth || element.width) : (element.videoWidth || element.width);
const height = element.tagName === 'IMG' ? (element.naturalHeight || element.height) : (element.videoHeight || element.height);
const format = getFileTypeFromUrl(src);
panel.innerHTML = `
<div style="margin-bottom: 10px; font-weight: bold; text-align: center;">${element.tagName === 'IMG' ? '图片信息' : '视频信息'}</div>
<div style="margin-bottom: 4px;">尺寸: ${width} × ${height}</div>
<div style="margin-bottom: 4px;">格式: ${format}</div>
<div style="margin-bottom: 8px;">大小: 未知</div>
<div style="display: flex; flex-direction: column; gap: 5px;">
<button id="download-btn" style="
padding: 6px 12px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
">下载</button>
<div style="font-size: 10px; color: #666; text-align: center;">按住Alt或Command键点击</div>
<div style="font-size: 10px; color: #666; text-align: center;">按ESC键隐藏信息</div>
</div>
`;
// 添加下载按钮事件
document.getElementById('download-btn').addEventListener('click', () => {
chrome.runtime.sendMessage({
action: 'download',
url: src,
filename: `media_${Date.now()}.${format}`
});
});
}
};
const hideInfoPanel = () => {
const panel = document.getElementById('media-info-panel');
if (panel) {
panel.style.opacity = '0';
hideTimeout = setTimeout(() => {
panel.style.display = 'none';
}, 300);
}
};
const init = () => {
let currentMediaElement = null;
let isMouseOverPanel = false;
let isUpdatingPosition = false;
let scrollTimeout = null;
let mousePosition = { x: 0, y: 0 };
let modifierKeys = ['altKey', 'metaKey']; // 默认支持Alt和Command键
let hoverTimer = null;
// 获取设置
const getSettings = () => {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ action: 'getSettings' }, (response) => {
if (response && response.modifierKeys) {
modifierKeys = response.modifierKeys;
}
resolve();
});
});
};
// 监听设置变更
chrome.runtime.onMessage.addListener((message) => {
if (message.action === 'settingsChanged' && message.modifierKeys) {
modifierKeys = message.modifierKeys;
console.log('设置已更新:', modifierKeys);
}
});
// 检查是否按下了任何修饰键
const isModifierPressed = (event) => {
return modifierKeys.some(key => event[key]);
};
// 初始化时获取设置
getSettings();
// 跟踪鼠标位置
document.addEventListener('mousemove', (event) => {
mousePosition.x = event.clientX;
mousePosition.y = event.clientY;
// 检查是否按下了修饰键
if (isModifierPressed(event)) {
// 按住修饰键时,面板保持静止,不检测新元素
return;
}
// 检查鼠标是否在信息面板上
const panel = document.getElementById('media-info-panel');
const isOverPanel = panel && panel.contains(event.target);
if (isOverPanel) {
isMouseOverPanel = true;
return; // 鼠标在面板上,不做任何处理
}
// 不按住修饰键时检测鼠标位置的元素(穿透遮罩层)
const element = getElementUnderMouse(event);
if (element && (element.tagName === 'IMG' || element.tagName === 'VIDEO')) {
currentMediaElement = element;
isMouseOverPanel = false;
showInfoPanel(element, event);
// 重置悬停计时器
if (hoverTimer) {
clearTimeout(hoverTimer);
}
// 设置悬停计时器,3秒后重新识别
hoverTimer = setTimeout(() => {
if (element === currentMediaElement) {
showInfoPanel(element, event);
}
}, 3000);
} else {
// 鼠标离开媒体元素,清除悬停计时器并隐藏面板
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
// 如果当前有媒体元素,隐藏面板
if (currentMediaElement) {
currentMediaElement = null;
hideInfoPanel();
}
}
});
// 鼠标滚动事件处理
document.addEventListener('scroll', () => {
// 滚动时隐藏面板
hideInfoPanel();
currentMediaElement = null;
isMouseOverPanel = false;
// 清除悬停计时器
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
// 清除之前的滚动超时
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
// 滚动停止后检测鼠标位置
scrollTimeout = setTimeout(() => {
detectElementUnderMouse();
}, 300);
}, true);
// 检测鼠标位置的元素(穿透遮罩层)
const getElementUnderMouse = (event) => {
const elements = document.elementsFromPoint(event.clientX, event.clientY);
// 查找第一个非遮罩层的图片或视频元素
for (const element of elements) {
// 跳过信息面板本身
if (element.id === 'media-info-panel' || element.closest('#media-info-panel')) {
continue;
}
// 跳过可能的遮罩层元素
if (element.style.position === 'fixed' && element.style.zIndex > 1000) {
continue;
}
// 找到图片或视频元素
if (element.tagName === 'IMG' || element.tagName === 'VIDEO') {
return element;
}
}
return null;
};
// 检测鼠标位置的元素
const detectElementUnderMouse = () => {
// 滚动停止后自动检测元素
const element = getElementUnderMouse({ clientX: mousePosition.x, clientY: mousePosition.y });
if (element && (element.tagName === 'IMG' || element.tagName === 'VIDEO')) {
currentMediaElement = element;
isMouseOverPanel = false;
showInfoPanel(element, { clientX: mousePosition.x, clientY: mousePosition.y });
}
};
document.addEventListener('mouseover', (event) => {
// 检查是否按下了修饰键
if (isModifierPressed(event)) {
// 按住修饰键时,面板保持静止,不检测新元素
if (event.target.closest('#media-info-panel')) {
isMouseOverPanel = true;
isUpdatingPosition = false;
}
return;
}
const element = getElementUnderMouse(event);
if (element && (element.tagName === 'IMG' || element.tagName === 'VIDEO')) {
currentMediaElement = element;
isMouseOverPanel = false;
showInfoPanel(element, event);
}
});
// 鼠标移动事件处理(位置更新)
document.addEventListener('mousemove', (event) => {
// 检查是否按下了修饰键
if (isModifierPressed(event)) {
// 按住修饰键时,面板保持静止
return;
}
// 不按住修饰键且鼠标不在面板上且有当前媒体元素时,更新面板位置
if (!isMouseOverPanel && currentMediaElement) {
isUpdatingPosition = true;
updatePanelPosition(event);
}
});
document.addEventListener('mouseout', (event) => {
const target = event.target;
const relatedTarget = event.relatedTarget;
const panel = document.getElementById('media-info-panel');
if (target.tagName === 'IMG' || target.tagName === 'VIDEO') {
if (!panel || !relatedTarget || (!panel.contains(relatedTarget) && !isMouseOverPanel)) {
currentMediaElement = null;
hideInfoPanel();
// 清除悬停计时器
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
}
} else if (target.closest('#media-info-panel')) {
if (!relatedTarget || (!relatedTarget.closest('#media-info-panel') && !currentMediaElement)) {
isMouseOverPanel = false;
hideInfoPanel();
// 清除悬停计时器
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
}
}
});
document.addEventListener('click', (event) => {
const panel = document.getElementById('media-info-panel');
if (panel && !panel.contains(event.target)) {
currentMediaElement = null;
isMouseOverPanel = false;
hideInfoPanel();
// 清除悬停计时器
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
}
});
// 检测元素是否从DOM中移除
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
// 检查是否有媒体元素被移除
mutation.removedNodes.forEach((node) => {
if ((node.tagName === 'IMG' || node.tagName === 'VIDEO') && node === currentMediaElement) {
currentMediaElement = null;
hideInfoPanel();
}
// 检查是否有包含媒体元素的节点被移除
if (node.contains && (node.contains(document.querySelector('img')) || node.contains(document.querySelector('video')))) {
if (currentMediaElement && !document.body.contains(currentMediaElement)) {
currentMediaElement = null;
hideInfoPanel();
}
}
});
}
});
});
// 开始观察DOM变化
observer.observe(document.body, {
childList: true,
subtree: true
});
// 监听ESC键事件
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
hideInfoPanel();
currentMediaElement = null;
isMouseOverPanel = false;
// 清除悬停计时器
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
}
});
};
const updatePanelPosition = (event) => {
const panel = document.getElementById('media-info-panel');
if (!panel || panel.style.display === 'none') return;
const panelWidth = 250;
const panelHeight = 120;
const offset = 10;
let left = event.clientX + offset;
let top = event.clientY + offset;
if (left + panelWidth > window.innerWidth) {
left = window.innerWidth - panelWidth - offset;
}
if (top + panelHeight > window.innerHeight) {
top = window.innerHeight - panelHeight - offset;
}
panel.style.left = `${left}px`;
panel.style.top = `${top}px`;
};
window.onload = async () => {
const resultDom = document.getElementById('result');
try {
// 1. 查询打开的目标网站标签页
const [tab] = await chrome.tabs.query({
url: "https://tlw.wusetech.com/*",
currentWindow: true
});
if (!tab) {
resultDom.innerText = "请先打开 https://tlw.wusetech.com/ 页面";
return;
}
// 2. 注入脚本读取 localStorage.token
const [res] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => localStorage.getItem('token') // 直接在页面执行
});
const token = res.result;
if (token) {
resultDom.innerText = "获取成功:\n" + token;
console.log("token =", token);
} else {
resultDom.innerText = "未找到 token";
}
} catch (err) {
resultDom.innerText = "错误:" + err.message;
console.error(err);
}
};
init();
import { defineConfig } from 'vite'
export default defineConfig({
build: {
outDir: 'dist',
emptyOutDir: true,
rollupOptions: {
input: {
content: 'src/content.js',
background: 'src/background.js'
},
output: {
entryFileNames: '[name].js',
chunkFileNames: 'chunks/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
})
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论