├── .github
└── workflows
│ └── build.yaml
├── .gitignore
├── .goreleaser.yaml
├── README.md
├── assets
├── reward.png
├── screenshot1.png
├── screenshot10.png
├── screenshot11.png
├── screenshot12.png
├── screenshot13.png
├── screenshot14.png
├── screenshot2.png
├── screenshot3.png
├── screenshot4.png
├── screenshot5.png
├── screenshot6.png
├── screenshot7.png
├── screenshot8.png
├── screenshot9.png
└── sponsors.svg
├── certs
└── SunnyRoot.cer
├── go.mod
├── go.sum
├── inject
└── main.js
├── lib
├── FileSaver.min.js
└── jszip.min.js
├── main.go
└── pkg
├── argv
└── argv.go
├── certificate
└── certificate.go
├── proxy
└── proxy.go
└── util
└── util.go
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | release:
5 | types: [created] # 表示在创建新的 Release 时触发
6 |
7 | jobs:
8 | build-go-binary:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | goos: [windows, darwin] # 需要打包的系统
13 | goarch: [amd64, arm64] # 需要打包的架构
14 | steps:
15 | - uses: actions/checkout@v3
16 | - uses: wangyoucao577/go-release-action@v1.30
17 | with:
18 | github_token: ${{ secrets.GITHUB_TOKEN }} # 一个默认的变量,用来实现往 Release 中添加文件
19 | goos: ${{ matrix.goos }}
20 | goarch: ${{ matrix.goarch }}
21 | goversion: 1.18 # 可以指定编译使用的 Golang 版本
22 | binary_name: "wx_video_download" # 可以指定二进制文件的名称
23 | extra_files: README.md # 需要包含的额外文件
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.exe
3 | dist/
4 |
5 | wx_video_download*
6 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | # This is an example .goreleaser.yml file with some sensible defaults.
2 | # Make sure to check the documentation at https://goreleaser.com
3 |
4 | # The lines below are called `modelines`. See `:help modeline`
5 | # Feel free to remove those if you don't want/need to use them.
6 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json
7 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj
8 |
9 | version: 2
10 |
11 | before:
12 | hooks:
13 | # You may remove this if you don't use go modules.
14 | - go mod tidy
15 | # you may remove this if you don't need go generate
16 | - go generate ./...
17 |
18 | builds:
19 | - env:
20 | - CGO_ENABLED=0
21 | goos:
22 | - windows
23 | - darwin
24 | main: ./main.go
25 | binary: wx_video_download
26 | ignore:
27 | - goos: darwin
28 | goarch: "386"
29 | - goos: windows
30 | goarch: "386"
31 |
32 | archives:
33 | - format: tar.gz
34 | # this name template makes the OS and Arch compatible with the results of `uname`.
35 | name_template: >-
36 | {{ .ProjectName }}_
37 | {{- title .Os }}_
38 | {{- if eq .Arch "amd64" }}x86_64
39 | {{- else if eq .Arch "386" }}i386
40 | {{- else }}{{ .Arch }}{{ end }}
41 | {{- if .Arm }}v{{ .Arm }}{{ end }}
42 | # use zip for windows archives
43 | format_overrides:
44 | - goos: windows
45 | format: zip
46 |
47 | changelog:
48 | sort: asc
49 | filters:
50 | exclude:
51 | - "^docs:"
52 | - "^test:"
53 |
54 | release:
55 | footer: >-
56 |
57 | ---
58 |
59 | Released by [GoReleaser](https://github.com/goreleaser/goreleaser).
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 微信视频号下载器
2 |
3 | 体积小、使用简单、支持 macOS 和 Windows 系统。
4 |
5 | ## 250514 更新
6 |
7 | 1、修复无法下载图片视频的问题
8 |
9 | ## 25042501 更新
10 |
11 | 1、修复了下载的视频无法播放的问题
12 |
13 | 点击查看更多更新内容
14 |
15 | ## 250425 更新
16 |
17 | 1、修复了无法下载视频的问题
18 |
19 | ## 250424 更新
20 |
21 | 1、修复了 下载按钮样式不一致的问题
22 |
23 | 2、修复了 更多按钮点击不显示更多菜单的问题
24 |
25 | 3、增加了 windows 启动失败时提示是否以管理员身份运行
26 |
27 |
28 | ## 250215 更新
29 |
30 | 1、在控制台显示下载进度,当获取不到进度时显示已下载的字节数
31 |
32 | 2、在「更多」菜单中增加封面图片下载
33 |
34 | 3、自动检测当前网络设备并代理
35 |
36 | 4、支持命令行参数指定要代理的网络设备和程序使用的端口号
37 |
38 | ```bash
39 | ./wx_video_download_xxx --dev=Wi-Fi --port=1080
40 | ```
41 |
42 | > 一般情况下无需手动指定设备与端口号,直接 ./wx_video_download_xxx 即可
43 |
44 | ## 241216 更新
45 |
46 | 1、支持下载直播回放
47 |
48 | 2、支持 macOS 系统
49 |
50 | 在微信 Version 3.8.9 (28564) 测试可用
51 |
52 | macOS 下使用说明
53 |
54 | ```bash
55 | chmod +x ./wx_video_download_drawin_xxx
56 | sudo ./wx_video_download_drawin_xxx
57 | ```
58 |
59 | 此时会提示文件不能打开,需要到系统设置中允许,然后重新执行 `sudo ./wx_video_download_drawin_xxx`。
60 |
61 |
62 | 在安装证书的过程中会申请权限,同意即可。后续打开无需使用 `sudo`,只需要双击运行
63 |
64 |
65 | 关闭 `macOS` 终端时请使用 `Command + c` 的方式,否则可能会出现系统代理未取消,导致网络无法访问的问题
66 |
67 |
68 | > 当出现网络无法访问时请检查系统代理并手动取消即可。
69 |
70 |
71 |
72 |
73 | ## 241106 更新
74 |
75 | 1、修复了 非首次打开的视频,下载下来都无法播放的问题
76 |
77 | 现在点击页面上「更多推荐」视频,下载下来的视频可以正常打开播放了。
78 |
79 | 当出现「解密失败,停止下载」的提示,关闭全部视频页面、窗口。重新打开,就可以下载。
80 |
81 | ## 241104 更新
82 |
83 | 1、 支持下载不同质量的视频
84 |
85 | 2、 修复了下载的视频无法拖动进度条的问题
86 |
87 | 3、 修复了长视频内容进度未加载就下载,导致视频无法播放或不完整的问题
88 |
89 | 4、 修复了某些视频误判断为图片导致无法下载的问题
90 |
91 | 5、 修复了直播间一直加载中的问题
92 |
93 | 
94 |
95 | 关于不同质量的视频,详情见下方使用说明。
96 |
97 | ## 241102 更新
98 |
99 | 在「更多」下拉菜单增加「下载视频」按钮,兼容不同详情页布局不同导致没有下载按钮的问题。
100 |
101 | 
102 |
103 | ## 241101 更新
104 |
105 | 现在无需手动下载证书并安装了。
106 |
107 | 修复了下载时提示找不到 `lib/jszip.min.js` 的问题。
108 |
109 | ## 241031 更新
110 |
111 | 又遇到之前无法在页面下载的问题,这次改成了下载压缩包,视频在压缩包内的形式。
112 | 目前是可行的,但无法保证之后仍然可行。
113 |
114 | 建议使用 [WechatVideoSniffer2.0](https://github.com/kanadeblisst00/WechatVideoSniffer2.0) 稳定性更高。
115 |
116 | ## 241030 更新
117 |
118 | 当视频号内容是多张图片时,也会出现下载按钮。点击将会下载一个包含了全部图片的压缩包。
119 |
120 | ## 241022 更新
121 |
122 | 当视频被删除时没有正确地显示「被删除」而是一直处于加载中状态。
123 | 下载按钮修改成和其他操作按钮相同的样式。
124 |
125 | ## 241016 更新
126 |
127 | 前一个版本又下载不了,改回在页面直接下载又正常了,是和微信客户端版本有关吗,对这块不了解。
128 | 如果 241016 这个版本用不了,可以试试其他版本。
129 | 我目前微信客户端版本是 `Weixin 3.9.12.17`,可以正常下载的。
130 |
131 | ## 241011 更新
132 |
133 | 应该是视频号又改版了,不能直接在页面下载了。改成点击下载按钮复制视频链接到粘贴板,然后到谷歌或其他浏览器打开下载。
134 | 另外测试了很多视频都可以直接下载,没有加密了。所以如果有加密视频,新版本可能会下载失败。
135 |
136 | > 在页面直接下载,理论上还是能实现,实现上要麻烦许多,后面再研究。
137 |
138 |
> 
155 |
156 | 等待视频开始播放,然后暂停视频,点击下载按扭即可下载视频。下载成功后,会在上方显示已下载的文件,下载文件名最后面会标志该视频质量。
157 |
158 | 
159 |
160 | 默认会下载下拉菜单中第一个质量视频。点开更多,可以下载其他质量的视频,包括原始视频。
161 |
162 | 
163 |
164 |
165 | 不同视频这里显示的选项是不同的,没有找到对 xWT111 具体的说明,属于什么分辨率、尺寸多大等等。
166 |
167 | 经过测试,如果原始视频有 104MB,这里尺寸最大的是 xWT111 为 17MB,最小的是 xWT98 为 7MB。
168 |
169 | 
170 |
171 | 仅供参考。
172 |
173 | ## 常见问题
174 |
175 | 1、服务启动了,打开视频详情后一直在加载,而且终端没有日志信息。
176 |
177 | 尝试在终端 `Ctrl+C`,按一次即可。
178 |
179 | 2、解密失败,停止下载
180 |
181 | 关闭全部视频页面、窗口。重新打开,就可以下载。
182 |
183 | ## 开发说明
184 |
185 | 先以 管理员身份 启动终端,然后 `go run main.go` 即可。
186 |
187 | ## 打包
188 |
189 | ```bash
190 | go build -o wx_video_download.exe main.go
191 | ```
192 |
193 | 打包后可以使用 `upx` 压缩,体积可以从 17MB 压缩到 5MB。
194 |
195 | ## 其他
196 |
197 | 此程序大部分参考自以下项目代码
198 |
199 | https://github.com/kanadeblisst00/WechatVideoSniffer2.0
200 |
201 | 此程序的核心实现依赖以下库
202 |
203 | https://github.com/qtgolang/SunnyNet
204 |
205 | ## 我的赞赏码
206 |
207 | 如果我的项目对你有所帮助,可以请我喝杯咖啡 ☕️
208 |
209 | [](https://sponsorkit-iota.vercel.app/api/sponsors)
210 |
--------------------------------------------------------------------------------
/assets/reward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/reward.png
--------------------------------------------------------------------------------
/assets/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot1.png
--------------------------------------------------------------------------------
/assets/screenshot10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot10.png
--------------------------------------------------------------------------------
/assets/screenshot11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot11.png
--------------------------------------------------------------------------------
/assets/screenshot12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot12.png
--------------------------------------------------------------------------------
/assets/screenshot13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot13.png
--------------------------------------------------------------------------------
/assets/screenshot14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot14.png
--------------------------------------------------------------------------------
/assets/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot2.png
--------------------------------------------------------------------------------
/assets/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot3.png
--------------------------------------------------------------------------------
/assets/screenshot4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot4.png
--------------------------------------------------------------------------------
/assets/screenshot5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot5.png
--------------------------------------------------------------------------------
/assets/screenshot6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot6.png
--------------------------------------------------------------------------------
/assets/screenshot7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot7.png
--------------------------------------------------------------------------------
/assets/screenshot8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot8.png
--------------------------------------------------------------------------------
/assets/screenshot9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ltaoo/wx_channels_download/64fb9925405a5e8f258ebeaf4bdeff65c43fa08c/assets/screenshot9.png
--------------------------------------------------------------------------------
/assets/sponsors.svg:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/certs/SunnyRoot.cer:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDwjCCAqqgAwIBAgIRAQAAAAAAAAAAAAAAAAAAAAAwDQYJKoZIhvcNAQELBQAw
3 | ajELMAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaUppbmcxEDAOBgNVBAcTB0JlaUpp
4 | bmcxETAPBgNVBAoTCFN1bm55TmV0MREwDwYDVQQLEwhTdW5ueU5ldDERMA8GA1UE
5 | AxMIU3VubnlOZXQwIBcNMjIxMTA0MDcwNTM0WhgPMjEyMjEwMTEwNzA1MzRaMGox
6 | CzAJBgNVBAYTAkNOMRAwDgYDVQQIEwdCZWlKaW5nMRAwDgYDVQQHEwdCZWlKaW5n
7 | MREwDwYDVQQKEwhTdW5ueU5ldDERMA8GA1UECxMIU3VubnlOZXQxETAPBgNVBAMT
8 | CFN1bm55TmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzU+hPfoE
9 | y+4+VZVUhfb0wKF7YSr79GyxNCo8/l8i1gI3pbaxv4PF/W5xWdE3LHND6b8FVmot
10 | pXqJcalx2FP48JdAKsmlzEZcl3MngHsKH7OPSvz8p76xvlHaFutVQjQFr8R3dX3B
11 | m8yNy6sNcP+3IrxOEUYsMWc5/lVHTyTYkruMAvCZIYzcc5Y2YXzExENbfPxwzNQh
12 | H/XsZlc4FGaZq6DV/0oMOXSSFOXcuJo2ULW/bOQho2jZ2zG1mf+Te3i8Psoanrrf
13 | sMXiOjB6ZH4tKv+O9NjJJi5o64Ulh35lt4qTHwGQD6pMs3yJn/l+N7kv85amLJzi
14 | fBSbJ1eYhjUpPQIDAQABo2EwXzAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYI
15 | KwYBBQUHAwIGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKC5
16 | TwkvGBAx7xu1CyvX5chP7zOdMA0GCSqGSIb3DQEBCwUAA4IBAQDDAl162QjUsv7H
17 | 1+pn7MT/RDcqXNqBAUEc9FF6ozkRnLxdWBMLWxI8KHKm8JoBQB+TLiokSkenfMtA
18 | 7eRX7xzCBghuLi2XjMDUlaoVVKp+HNNoPSyn+UE/lUlKoCJCFgyt5p+bp9MP+YDm
19 | pOnNjZTktyvwRj+Bgm1USzVY3IXlV+/H9la3vRi/G5n+yl3ZQMjwh6erbqwUzd6X
20 | 8j/L3BdoOkrOHzpodiAmp7Mf105Nh77EoUsh13TJy1CJLrIJzMDO1ryhzuVyxbJA
21 | evcsWTxTr9qR/P09XImwOFmFKNimKC8IGwP/xVxqdH9WapsX6VZV5NbRG8vnqaM5
22 | V6TbUzep
23 | -----END CERTIFICATE-----
24 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module wx_channel
2 |
3 | go 1.22
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | github.com/fatih/color v1.17.0
9 | github.com/qtgolang/SunnyNet v1.0.3
10 | )
11 |
12 | require (
13 | github.com/Trisia/gosysproxy v1.1.0 // indirect
14 | github.com/mattn/go-colorable v0.1.13 // indirect
15 | github.com/mattn/go-isatty v0.0.20 // indirect
16 | golang.org/x/crypto v0.31.0 // indirect
17 | golang.org/x/sys v0.28.0 // indirect
18 | golang.org/x/text v0.21.0 // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Trisia/gosysproxy v1.1.0 h1:rBU1mazMXLsZiiaAHXtRoDPt2gzWdA6uxjaLskzaoAA=
2 | github.com/Trisia/gosysproxy v1.1.0/go.mod h1:PTPPgpRpyTJEL/FMxEE2OcQcGNbUm961xZcgihwraZM=
3 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
4 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
5 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
6 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
7 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
8 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
9 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
10 | github.com/qtgolang/SunnyNet v1.0.3 h1:E0e9ojaphPjsHKhQ5/WvDcaQWiZjld7NPB2QEZff1Do=
11 | github.com/qtgolang/SunnyNet v1.0.3/go.mod h1:7ijev0f9p7N8ietfayQMkLFEw6SjK5hviWNfCa7NCCQ=
12 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
13 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
14 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
15 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
16 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
17 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
18 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
19 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
20 |
--------------------------------------------------------------------------------
/inject/main.js:
--------------------------------------------------------------------------------
1 | const defaultRandomAlphabet =
2 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3 | function __wx_uid__() {
4 | return random_string(12);
5 | }
6 | /**
7 | * 返回一个指定长度的随机字符串
8 | * @param length
9 | * @returns
10 | */
11 | function random_string(length) {
12 | return random_string_with_alphabet(length, defaultRandomAlphabet);
13 | }
14 | function random_string_with_alphabet(length, alphabet) {
15 | let b = new Array(length);
16 | let max = alphabet.length;
17 | for (let i = 0; i < b.length; i++) {
18 | let n = Math.floor(Math.random() * max);
19 | b[i] = alphabet[n];
20 | }
21 | return b.join("");
22 | }
23 | function sleep() {
24 | return new Promise((resolve) => {
25 | setTimeout(() => {
26 | resolve();
27 | }, 1000);
28 | });
29 | }
30 | function __wx_channels_copy(text) {
31 | const textArea = document.createElement("textarea");
32 | textArea.value = text;
33 | textArea.style.cssText = "position: absolute; top: -999px; left: -999px;";
34 | document.body.appendChild(textArea);
35 | textArea.select();
36 | document.execCommand("copy");
37 | document.body.removeChild(textArea);
38 | }
39 | function __wx_channel_loading() {
40 | if (window.__wx_channels_tip__ && window.__wx_channels_tip__.loading) {
41 | return window.__wx_channels_tip__.loading("下载中");
42 | }
43 | return {
44 | hide() {},
45 | };
46 | }
47 | function __wx_log(msg) {
48 | fetch("/__wx_channels_api/tip", {
49 | method: "POST",
50 | headers: {
51 | "Content-Type": "application/json",
52 | },
53 | body: JSON.stringify(msg),
54 | });
55 | }
56 | function __wx_channels_video_decrypt(t, e, p) {
57 | for (
58 | var r = new Uint8Array(t), n = 0;
59 | n < t.byteLength && e + n < p.decryptor_array.length;
60 | n++
61 | )
62 | r[n] ^= p.decryptor_array[n];
63 | return r;
64 | }
65 | window.VTS_WASM_URL =
66 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/decrypt-video-core/1.3.0/wasm_video_decode.wasm";
67 | window.MAX_HEAP_SIZE = 33554432;
68 | var decryptor_array;
69 | let decryptor;
70 | /** t 是要解码的视频内容长度 e 是 decryptor_array 的长度 */
71 | function wasm_isaac_generate(t, e) {
72 | decryptor_array = new Uint8Array(e);
73 | var r = new Uint8Array(Module.HEAPU8.buffer, t, e);
74 | decryptor_array.set(r.reverse());
75 | if (decryptor) {
76 | decryptor.delete();
77 | }
78 | }
79 | let loaded = false;
80 | /** 获取 decrypt_array */
81 | async function __wx_channels_decrypt(seed) {
82 | if (!loaded) {
83 | await __wx_load_script(
84 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/decrypt-video-core/1.3.0/wasm_video_decode.js"
85 | );
86 | loaded = true;
87 | }
88 | await sleep();
89 | decryptor = new Module.WxIsaac64(seed);
90 | // 调用该方法时,会调用 wasm_isaac_generate 方法
91 | // 131072 是 decryptor_array 的长度
92 | decryptor.generate(131072);
93 | // decryptor.delete();
94 | // const r = Uint8ArrayToBase64(decryptor_array);
95 | // decryptor_array = undefined;
96 | return decryptor_array;
97 | }
98 | async function show_progress_or_loaded_size(response) {
99 | const content_length = response.headers.get("Content-Length");
100 | const chunks = [];
101 | const total_size = content_length ? parseInt(content_length, 10) : 0;
102 | if (total_size) {
103 | __wx_log({
104 | msg: `${total_size} Bytes`,
105 | });
106 | }
107 | let loaded_size = 0;
108 | const reader = response.body.getReader();
109 | while (true) {
110 | const { done, value } = await reader.read();
111 | if (done) {
112 | break;
113 | }
114 | chunks.push(value);
115 | loaded_size += value.length;
116 | if (total_size) {
117 | const progress = (loaded_size / total_size) * 100;
118 | __wx_log({
119 | msg: `${progress.toFixed(2)}%`,
120 | });
121 | } else {
122 | __wx_log({
123 | msg: `${loaded_size} Bytes`,
124 | });
125 | }
126 | }
127 | const blob = new Blob(chunks);
128 | return blob;
129 | }
130 | /** 用于下载已经播放的视频内容 */
131 | async function __wx_channels_download(profile, filename) {
132 | console.log("__wx_channels_download");
133 | const data = profile.data;
134 | const blob = new Blob(data, { type: "video/mp4" });
135 | await __wx_load_script(
136 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/FileSaver.min.js"
137 | );
138 | saveAs(blob, filename + ".mp4");
139 | }
140 | /** 下载非加密视频 */
141 | async function __wx_channels_download2(profile, filename) {
142 | console.log("__wx_channels_download2");
143 | const url = profile.url;
144 | // __wx_log({
145 | // msg: `${filename}
146 | // ${url}
147 | // ${profile.key}`,
148 | // });
149 | await __wx_load_script(
150 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/FileSaver.min.js"
151 | );
152 | const ins = __wx_channel_loading();
153 | const response = await fetch(url);
154 | const blob = await show_progress_or_loaded_size(response);
155 | __wx_log({
156 | msg: "下载完成",
157 | });
158 | ins.hide();
159 | saveAs(blob, filename + ".mp4");
160 | }
161 | /** 下载图片视频 */
162 | async function __wx_channels_download3(profile, filename) {
163 | console.log("__wx_channels_download3");
164 | const files = profile.files;
165 | await __wx_load_script(
166 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/FileSaver.min.js"
167 | );
168 | await __wx_load_script(
169 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/jszip.min.js"
170 | );
171 | const zip = new JSZip();
172 | zip.file("contact.txt", JSON.stringify(profile.contact, null, 2));
173 | const folder = zip.folder("images");
174 | console.log("files", files)
175 | const fetchPromises = files
176 | .map((f) => f.url)
177 | .map(async (url, index) => {
178 | const response = await fetch(url);
179 | const blob = await response.blob();
180 | folder.file(index + 1 + ".png", blob);
181 | });
182 | const ins = __wx_channel_loading();
183 | try {
184 | await Promise.all(fetchPromises);
185 | const content = await zip.generateAsync({ type: "blob" });
186 | ins.hide();
187 | saveAs(content, filename + ".zip");
188 | } catch (err) {
189 | __wx_log({
190 | msg: "下载失败\n" + err.message,
191 | });
192 | }
193 | }
194 | /** 下载加密视频 */
195 | async function __wx_channels_download4(profile, filename) {
196 | console.log("__wx_channels_download4");
197 | const url = profile.url;
198 | // console.log("__wx_channels_download4", url);
199 | // __wx_log({
200 | // msg: `${filename}
201 | // ${url}`,
202 | // });
203 | await __wx_load_script(
204 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/FileSaver.min.js"
205 | );
206 | const ins = __wx_channel_loading();
207 | const response = await fetch(url);
208 | const blob = await show_progress_or_loaded_size(response);
209 | __wx_log({
210 | msg: "下载完成,开始解密",
211 | });
212 | let array = new Uint8Array(await blob.arrayBuffer());
213 | if (profile.decryptor_array) {
214 | array = __wx_channels_video_decrypt(array, 0, profile);
215 | }
216 | ins.hide();
217 | const result = new Blob([array], { type: "video/mp4" });
218 | saveAs(result, filename + ".mp4");
219 | }
220 | function __wx_load_script(src) {
221 | return new Promise((resolve, reject) => {
222 | const script = document.createElement("script");
223 | script.type = "text/javascript";
224 | script.src = src;
225 | script.onload = resolve;
226 | script.onerror = reject;
227 | document.head.appendChild(script);
228 | });
229 | }
230 | function __wx_channels_handle_copy__() {
231 | __wx_channels_copy(location.href);
232 | if (window.__wx_channels_tip__ && window.__wx_channels_tip__.toast) {
233 | window.__wx_channels_tip__.toast("复制成功", 1e3);
234 | }
235 | }
236 | async function __wx_channels_handle_log__() {
237 | await __wx_load_script(
238 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/FileSaver.min.js"
239 | );
240 | const content = document.body.innerHTML;
241 | const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
242 | saveAs(blob, "log.txt");
243 | }
244 | async function __wx_channels_handle_click_download__(spec) {
245 | var profile = __wx_channels_store__.profile;
246 | // profile = __wx_channels_store__.profiles.find((p) => p.id === profile.id);
247 | if (!profile) {
248 | alert("检测不到视频,请将本工具更新到最新版");
249 | return;
250 | }
251 | // console.log(__wx_channels_store__);
252 | var filename = (() => {
253 | if (profile.title) {
254 | return profile.title;
255 | }
256 | if (profile.id) {
257 | return profile.id;
258 | }
259 | return new Date().valueOf();
260 | })();
261 | const _profile = {
262 | ...profile,
263 | };
264 | if (spec) {
265 | _profile.url = profile.url + "&X-snsvideoflag=" + spec.fileFormat;
266 | filename = filename + "_" + spec.fileFormat;
267 | }
268 | // console.log("__wx_channels_handle_click_download__", url);
269 | __wx_log({
270 | msg: `${filename}
271 | ${location.href}
272 | ${_profile.url}
273 | ${_profile.key || ""}`,
274 | });
275 | if (_profile.type === "picture") {
276 | __wx_channels_download3(_profile, filename);
277 | return;
278 | }
279 | if (!_profile.key) {
280 | __wx_channels_download2(_profile, filename);
281 | return;
282 | }
283 | _profile.data = __wx_channels_store__.buffers;
284 | try {
285 | const r = await __wx_channels_decrypt(_profile.key);
286 | // console.log("[]after __wx_channels_decrypt", r);
287 | _profile.decryptor_array = r;
288 | } catch (err) {
289 | __wx_log({
290 | msg: `解密失败,停止下载`,
291 | });
292 | alert("解密失败,停止下载");
293 | return;
294 | }
295 | __wx_channels_download4(_profile, filename);
296 | }
297 | function __wx_channels_download_cur__() {
298 | var profile = __wx_channels_store__.profile;
299 | if (!profile) {
300 | alert("检测不到视频,请将本工具更新到最新版");
301 | return;
302 | }
303 | if (__wx_channels_store__.buffers.length === 0) {
304 | alert("没有可下载的内容");
305 | return;
306 | }
307 | var filename = (() => {
308 | if (profile.title) {
309 | return profile.title;
310 | }
311 | if (profile.id) {
312 | return profile.id;
313 | }
314 | return new Date().valueOf();
315 | })();
316 | profile.data = __wx_channels_store__.buffers;
317 | __wx_channels_download(profile, filename);
318 | }
319 | async function __wx_channels_handle_download_cover() {
320 | var profile = __wx_channels_store__.profile;
321 | // profile = __wx_channels_store__.profiles.find((p) => p.id === profile.id);
322 | if (!profile) {
323 | alert("检测不到视频,请将本工具更新到最新版");
324 | return;
325 | }
326 | // console.log(__wx_channels_store__);
327 | var filename = (() => {
328 | if (profile.title) {
329 | return profile.title;
330 | }
331 | if (profile.id) {
332 | return profile.id;
333 | }
334 | return new Date().valueOf();
335 | })();
336 | const _profile = {
337 | ...profile,
338 | };
339 | await __wx_load_script(
340 | "https://res.wx.qq.com/t/wx_fed/cdn_libs/res/FileSaver.min.js"
341 | );
342 | __wx_log({
343 | msg: `下载封面\n${_profile.coverUrl}`,
344 | });
345 | const ins = __wx_channel_loading();
346 | try {
347 | const url = _profile.coverUrl.replace(/^http/, "https");
348 | const response = await fetch(url);
349 | const blob = await response.blob();
350 | saveAs(blob, filename + ".jpg");
351 | } catch (err) {
352 | alert(err.message);
353 | }
354 | ins.hide();
355 | }
356 | var __wx_channels_tip__ = {};
357 | var __wx_channels_store__ = {
358 | profile: null,
359 | profiles: [],
360 | keys: {},
361 | buffers: [],
362 | };
363 | var $icon = document.createElement("div");
364 | $icon.innerHTML =
365 | '
>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(d&(1<