2 |
3 | 当前系统:{{ osLabel }} · 架构:{{ archLabel }}
4 |
5 |
6 |
7 |
40 |
41 |
44 |
--------------------------------------------------------------------------------
/internal/interceptor/assets.go:
--------------------------------------------------------------------------------
1 | package interceptor
2 |
3 | import (
4 | _ "embed"
5 | )
6 |
7 | //go:embed inject/lib/FileSaver.min.js
8 | var js_file_saver []byte
9 |
10 | //go:embed inject/lib/jszip.min.js
11 | var js_zip []byte
12 |
13 | //go:embed inject/lib/floating-ui.core.1.7.4.min.js
14 | var js_floating_ui_core []byte
15 |
16 | //go:embed inject/lib/floating-ui.dom.1.7.4.min.js
17 | var js_floating_ui_dom []byte
18 |
19 | //go:embed inject/lib/mitt.umd.js
20 | var js_mitt []byte
21 |
22 | //go:embed inject/lib/weui.umd.js
23 | var js_weui []byte
24 |
25 | //go:embed inject/lib/recorder.min.js
26 | var js_recorder []byte
27 |
28 | //go:embed inject/lib/pagespy.min.js
29 | var js_pagespy []byte
30 |
31 | //go:embed inject/src/pagespy.js
32 | var js_debug []byte
33 |
34 | //go:embed inject/src/error.js
35 | var js_error []byte
36 |
37 | //go:embed inject/src/eventbus.js
38 | var js_eventbus []byte
39 |
40 | //go:embed inject/src/components.js
41 | var js_components []byte
42 |
43 | //go:embed inject/src/utils.js
44 | var js_utils []byte
45 |
46 | //go:embed inject/src/main.js
47 | var js_main []byte
48 |
49 | //go:embed inject/src/live.js
50 | var js_live_main []byte
51 |
52 | var Assets = &ChannelInjectedFiles{
53 | JSFileSaver: js_file_saver,
54 | JSZip: js_zip,
55 | JSRecorder: js_recorder,
56 | JSPageSpy: js_pagespy,
57 | JSFloatingUICore: js_floating_ui_core,
58 | JSFloatingUIDOM: js_floating_ui_dom,
59 | JSWeui: js_weui,
60 | JSMitt: js_mitt,
61 | JSDebug: js_debug,
62 | JSError: js_error,
63 | JSEventBus: js_eventbus,
64 | JSComponents: js_components,
65 | JSUtils: js_utils,
66 | JSMain: js_main,
67 | JSLiveMain: js_live_main,
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/certificate/certs/private.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEAzU+hPfoEy+4+VZVUhfb0wKF7YSr79GyxNCo8/l8i1gI3pbax
3 | v4PF/W5xWdE3LHND6b8FVmotpXqJcalx2FP48JdAKsmlzEZcl3MngHsKH7OPSvz8
4 | p76xvlHaFutVQjQFr8R3dX3Bm8yNy6sNcP+3IrxOEUYsMWc5/lVHTyTYkruMAvCZ
5 | IYzcc5Y2YXzExENbfPxwzNQhH/XsZlc4FGaZq6DV/0oMOXSSFOXcuJo2ULW/bOQh
6 | o2jZ2zG1mf+Te3i8PsoanrrfsMXiOjB6ZH4tKv+O9NjJJi5o64Ulh35lt4qTHwGQ
7 | D6pMs3yJn/l+N7kv85amLJzifBSbJ1eYhjUpPQIDAQABAoIBAQC8e18Wq6GdqhE1
8 | torLFYVqFpVTBggaQ3KG5kPqbmJnv89gZZFWtV2dJLgQ8b3KI+N0Anae94kCQrVN
9 | UHaAV87Q6Lnyzf5Uwz+blg7sp4gKxGhHOmukf69jfndN1SwHRAT4cNAOX63PHwIJ
10 | uPX1B/0TeXXd6+MEU7Ts5VM6uCPOx5N4OlpL+A/QoPV+Uspm5C3YcZOXjTTPAtUY
11 | JgH2nCMbCRsVIBteJQXANlSsaJP7ZgRswYKVcolNeM/zsjoNjfQUiuJbhaM6rKIa
12 | xxV4j0TxorZpp3ablUF6HCeWoG3wRalNxVFSLd7YTXwuRcKyp3NE9KpGc4dxDEqm
13 | 4F7TmIVNAoGBAOUxLZdfMFsUmCklUikifezYkyS1eF9wjvcFJaH07KaDRPgB6VRC
14 | hcWM+Mn6flZKtUNG5DaykxZXsh+OO8HSK85DkMterpz83cAaTvG0QxvChUNHkWqB
15 | 5dwXSDSVqgP2tDm7CqyjC9vZY3sdCeE+dzvBlL6bGTsEFPggD7emJ2z3AoGBAOVT
16 | XmZ1yq4a1lozr2GMehYdcj7KKD18mXOlVicyl++PvJG6Hmci3RlG2sKk3PSLlRCZ
17 | 1CDhkmrNRlVwzov8uSxZOHAO+bOSqc64oRMMcyAB3uVDNicqUL2cVWiVGvcOmTc0
18 | SgRqSU/HASMxDtT9D5nX+y0t3SL1SYRe5iBIaNJrAoGBAN5UqYx5G7iPLthjSuN6
19 | gTu8EGmA3MeAsj8wsAP/S35wQvxvJkDF020DRujwZZQiHtqnr4TcEFGROsrfuFpa
20 | HoKWCqUuMSc7KYZMPx67pooMVighChCO+ENcFoBkWyxDKywBpOY5uKxJovZwAgCO
21 | Dy5ZqIiKfpxAZnMY7wZRWVebAoGAQEbCwdMoMO6CwBuWf7ABFCvCtsiwyLMgy6I+
22 | 6JOstE/EWdAh72R9NjV+4WmWKNDqwhFrvJ+dC2Rn31DUA7adLEoBoJ8B7Awinjdv
23 | pkgqCIGduQLCre2VXd/wrHSGb1LfLPLyABTOYZb0walhb99SPRulYj9lqQO5TGnQ
24 | 9KF3B+sCgYACexhvn8ZzN637bfjaE7qge/uJcPE3CDFXxk4iYcYj6Vyv/aEBL1fJ
25 | DPxp+LrRqH4+9/GI5pKHZf3/MU1A2ea93QIo7SCRCL7+nCHZ6er4I+XFmpugd6wq
26 | +6JLOUgK6K0YSrzNgheVM3HWxMqT9qgrOV0CX530ia1uWSQzamhCIg==
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/internal/interceptor/inject/src/live.js:
--------------------------------------------------------------------------------
1 | window.__wx_channels_live_store__ = {};
2 | function __wx_copy_live_download_command() {
3 | var profile = __wx_channels_live_store__.profile;
4 | if (!profile) {
5 | WXU.error({ msg: "检测不到视频,请将本工具更新到最新版" });
6 | return;
7 | }
8 | var filename = (() => {
9 | return new Date().valueOf();
10 | })();
11 | var _profile = {
12 | ...profile,
13 | };
14 | var command = `ffmpeg -i "${_profile.url}" -c copy -y "live_${filename}.flv"`;
15 | WXU.log({ prefix: "", msg: "" });
16 | WXU.log({ prefix: "", msg: "直播下载命令" });
17 | WXU.log({ prefix: "", msg: command });
18 | WXU.toast("请在终端查看下载命令");
19 | }
20 |
21 | async function __wx_insert_live_download_btn() {
22 | var $elm1 = await WXU.find_elm(function () {
23 | return document.querySelector(".host__info .extra");
24 | });
25 | if ($elm1) {
26 | var relative_node = $elm1.children[0];
27 | if (!relative_node) {
28 | return;
29 | }
30 | var __wx_channels_live_download_btn__ = download_btn4();
31 | __wx_channels_live_download_btn__.onclick = function () {
32 | __wx_copy_live_download_command();
33 | };
34 | $elm1.insertBefore(__wx_channels_live_download_btn__, relative_node);
35 | return;
36 | }
37 | }
38 |
39 | (() => {
40 | var error_tip_timer = setTimeout(() => {
41 | WXU.error({ msg: "没有捕获到视频详情", alert: 0 });
42 | }, 5000);
43 | var live_page_mounted = false;
44 | WXU.onFetchLiveProfile((feed) => {
45 | console.log("[live.js]onFetchLiveProfile", feed);
46 | if (live_page_mounted) {
47 | return;
48 | }
49 | live_page_mounted = true;
50 | clearTimeout(error_tip_timer);
51 | error_tip_timer = null;
52 | WXU.set_live_feed(feed);
53 | __wx_insert_live_download_btn();
54 | });
55 | })();
56 |
--------------------------------------------------------------------------------
/winres/winres.json:
--------------------------------------------------------------------------------
1 | {
2 | "RT_GROUP_ICON": {
3 | "APP": {
4 | "0000": [
5 | "icon.png"
6 | ]
7 | }
8 | },
9 | "RT_MANIFEST": {
10 | "#1": {
11 | "0409": {
12 | "identity": {
13 | "name": "wx_video_download",
14 | "version": "1.0.0"
15 | },
16 | "description": "视频号视频下载工具",
17 | "minimum-os": "win7",
18 | "execution-level": "as invoker",
19 | "ui-access": false,
20 | "auto-elevate": true,
21 | "dpi-awareness": "system",
22 | "disable-theming": false,
23 | "disable-window-filtering": false,
24 | "high-resolution-scrolling-aware": false,
25 | "ultra-high-resolution-scrolling-aware": false,
26 | "long-path-aware": false,
27 | "printer-driver-isolation": false,
28 | "gdi-scaling": false,
29 | "segment-heap": false,
30 | "use-common-controls-v6": false
31 | }
32 | }
33 | },
34 | "RT_VERSION": {
35 | "#1": {
36 | "0000": {
37 | "fixed": {
38 | "file_version": "1.0.0.0",
39 | "product_version": "1.0.0.0"
40 | },
41 | "info": {
42 | "0409": {
43 | "Comments": "视频号视频下载工具",
44 | "CompanyName": "com.funzm",
45 | "FileDescription": "视频号视频下载工具",
46 | "FileVersion": "1.0.0.0",
47 | "InternalName": "wx_video_download",
48 | "LegalCopyright": "Copyright © 2024",
49 | "LegalTrademarks": "",
50 | "OriginalFilename": "wx_video_download.exe",
51 | "PrivateBuild": "",
52 | "ProductName": "视频号视频下载工具",
53 | "ProductVersion": "1.0.0.0",
54 | "SpecialBuild": ""
55 | }
56 | }
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/internal/manager/manager.go:
--------------------------------------------------------------------------------
1 | package manager
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | type ServerManager struct {
9 | servers map[string]Server
10 | mu sync.RWMutex
11 | }
12 |
13 | func NewServerManager() *ServerManager {
14 | return &ServerManager{
15 | servers: make(map[string]Server),
16 | }
17 | }
18 |
19 | func (sm *ServerManager) RegisterServer(server Server) {
20 | sm.mu.Lock()
21 | defer sm.mu.Unlock()
22 | sm.servers[server.Name()] = server
23 | }
24 |
25 | func (sm *ServerManager) StartServer(name string) error {
26 | sm.mu.RLock()
27 | server, exists := sm.servers[name]
28 | sm.mu.RUnlock()
29 |
30 | if !exists {
31 | return fmt.Errorf("server %s not found", name)
32 | }
33 |
34 | return server.Start()
35 | }
36 |
37 | func (sm *ServerManager) StopServer(name string) error {
38 | sm.mu.RLock()
39 | server, exists := sm.servers[name]
40 | sm.mu.RUnlock()
41 |
42 | if !exists {
43 | return fmt.Errorf("server %s not found", name)
44 | }
45 |
46 | return server.Stop()
47 | }
48 |
49 | func (sm *ServerManager) GetStatus(name string) (ServerStatus, error) {
50 | sm.mu.RLock()
51 | server, exists := sm.servers[name]
52 | sm.mu.RUnlock()
53 |
54 | if !exists {
55 | return StatusStopped, fmt.Errorf("server %s not found", name)
56 | }
57 |
58 | return server.Status(), nil
59 | }
60 |
61 | func (sm *ServerManager) GetAllStatus() map[string]ServerStatus {
62 | sm.mu.RLock()
63 | defer sm.mu.RUnlock()
64 |
65 | statuses := make(map[string]ServerStatus)
66 | for name, server := range sm.servers {
67 | statuses[name] = server.Status()
68 | }
69 | return statuses
70 | }
71 |
72 | func (sm *ServerManager) ListServers() []string {
73 | sm.mu.RLock()
74 | defer sm.mu.RUnlock()
75 |
76 | names := make([]string, 0, len(sm.servers))
77 | for name := range sm.servers {
78 | names = append(names, name)
79 | }
80 | return names
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 微信视频号下载器
2 |
3 | 体积小、使用简单、支持 macOS 和 Windows 系统。
4 |
5 | ## 使用说明
6 |
7 | 下载[构建包](https://github.com/ltaoo/wx_channels_download/releases),**以管理员身份运行**,首次打开会自动安装证书,然后启动服务。
8 |
9 | 当终端提示「代理服务启动成功」就说明可以使用了。
10 |
11 | 
12 |
13 | > 已安装证书会跳过安装证书步骤。
14 |
15 | 打开微信 PC 端,点击需要下载的视频,在视频下方的操作按钮一栏,会多出一个下载按钮,如下所示
16 |
17 | 
18 |
19 | 如果没有,在页面侧边或底部会有悬浮按钮,拥有相同的功能
20 |
21 | | 首页推荐 | 视频详情页 |
22 | | --- | --- |
23 | |  |  |
24 |
25 |
26 | 等待视频开始播放,然后暂停视频,点击下载按扭即可下载视频。下载成功后,会在上方显示已下载的文件,下载文件名最后面会标志该视频质量。
27 |
28 | 
29 |
30 | 下载按钮默认会下载视频号默认质量的视频(即当前播放的视频,一般都是体积最小的),可以在下拉菜单下载其他质量的视频
31 |