├── DYdownloader.png
├── LICENSE
├── README.md
├── 抖音视频下载器.user.js
└── test
└── 抖音视频下载器.user.js
/DYdownloader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuckgm/Douyin-Video-Downloader/HEAD/DYdownloader.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 IcedWatermelonJuice
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🎯抖音视频下载器(Douyin Video Downloader)
2 | * 本脚本能在下载抖音APP端禁止下载的视频
3 |
4 | * 本脚本能在pc版抖音"首页"、"推荐页"、视频"详情页"、其他"频道页"、"热点页"、"搜索页"添加下载按钮,在移动端分享页添加下载按钮,点击下载无水印视频
5 |
6 | * 本脚本能提取抖音直播间真实推流地址,能屏蔽直播间影响观看直播的不必要组件
7 |
8 | * 本脚本能展开页面左侧侧栏所有选项,屏蔽一些非必要的弹窗(强制登录页面、登录提示、满意度调查等)
9 |
10 | * 适用于PC端Chrome、Edge、华为浏览器(HWbrowser)等,移动端Kiwi、Yandex、Via等(1.30版本开始,由于部分功能使用了jquery实现跨域,这部分功能可能在via上无法使用)
11 |
12 | * 本脚本仅供学习交流使用,切勿商用!
13 |
14 | * 注意!!!由于最新抖音网页版的限制,部分功能无法在via浏览器等无法使用油猴/篡改猴/暴力猴的浏览器上使用(2021.12.1)
15 |
16 | # 📖使用流程
17 | 一、电脑端(或浏览器UA为电脑UA):
18 |
19 | 1、打开抖音网页版(https://www.douyin.com)
20 |
21 | 2、不同页面,下载图标位置不同:
22 |
23 | (1)推荐页或关注页:点击右下角三个点,选择下载
24 |
25 | (2)首页、频道页、热点页或搜索页:每个视频作者名边上都有下载按钮,点击下载;或者也可以点击视频进入详情页,参考(3)下载
26 |
27 | (3)详情页:视频名下方,分享按钮右侧为下载按钮
28 |
29 | (4)直播页:位于直播间标题右侧,“手机观看”按钮左侧。
30 |
31 |
32 |
33 | 二、移动端(或浏览器UA为手机等移动端UA)
34 |
35 | 1、打开移动端分享页URL
36 |
37 | 2、点击左下角视频作者名右侧或下方的“点击下载”按钮(注意:这里仅仅指视频分享,直播间分享请使用pc版网页)
38 |
39 |
40 |
41 | 三、下载抖音禁止下载的视频
42 |
43 | 两种方法,分为移动端、电脑端操作:
44 |
45 | 1、移动端操作:
46 |
47 | (1)APP端点击分享,选择“复制链接”
48 |
49 | (2)在从APP端获取的链接文字中找到URL(如果自己是在分不清哪个是URL,哪些不是,可以将链接通过微信或QQ发送个某个人,字体颜色不一样的https开头的就是URL)
50 |
51 | (3)浏览器中打开URL,按照“二”操作下载视频即可
52 |
53 | 2、电脑端操作:
54 |
55 | (1)APP端点击小红心先收藏
56 |
57 | (2)登录抖音网页版,左侧侧栏选择关注
58 |
59 | (3)按照“一”操作下载视频即可
60 |
61 | # 💊问题解答
62 | Q1:没有出现“下载”图标
63 |
64 | A1:按照以下方法解决
65 |
66 | (1)请确认本脚本已启用(尽量使用主流脚本管理器,如Tampermonkey、Violentmonkey)
67 |
68 | (2)关闭广告拦截插件,脚本可能被广告插件误拦截
69 |
70 | (3)多刷新下页面,有时候就很迷,油猴匹配不到脚本,需要二次刷新才能使用
71 |
72 | (4)使用Edge浏览器与Tampermonkey
73 |
74 | (5)如果都没用,按F12将控制台信息以及页面窗口截图通过Greasyfork/Github反馈给我
75 |
76 |
77 |
78 | Q2:点击下载按钮无反应
79 |
80 | A2:添加的按钮都通过右键或长按获取下载链接,将链接粘贴到第三方下载器下载(若采用第三方下载器下载,文件默认为html格式,需要手动将文件名后缀改为mp4)
81 |
82 | (1)非详情页、推荐页和关注页:点击视频进入详情页,根据(2)操作
83 |
84 | (2)详情页、推荐页或关注页:右键下载按钮的文字部分,手机用户长按文字部分(注意:是文字不是LOGO),选择复制链接
85 |
86 | (3)移动端分享页:右键按钮文字部分按钮,手机用户长按按钮文字部分,选择复制链接
87 |
88 |
89 |
90 | Q3:跳转的页面无法自动下载
91 |
92 | A3:复制跳转后的网页的URL(xxx.douyinvod.com/xxxxx),粘贴到第三方下载器下载
93 |
94 |
95 |
96 | Q4:浏览器提示“部分功能可能无法在此浏览器上使用”,页面上没有下载按钮
97 |
98 | A4:2021.12.1后,抖音视频无法直接通过网页抓取视频链接,必须通过接口模拟请求获取链接,因此采用了jquery。而抖音网页并没有导入jquery,且由于抖音CSP的限制无法人为再次导入jquery。因此,没有油猴/暴力猴/篡改猴的浏览器无法在频道页、推荐页、关注页等页面相关功能无法使用,在移动分享页、视频详情页、直播页相关功能可正常使用。
99 |
100 | # 🔔特别声明
101 | * 本人业余时间开发,并非专业开发者,代码质量可能不佳。如果有大佬想帮忙优化,可以联系我QwQ
102 |
103 | * 本脚本随缘更新。若无bug或业余时间不足,可能没时间更新脚本
104 |
105 | * 测试用平台:Windows系统Edge浏览器Tampermonkey脚本管理器
106 |
107 | * 再次提醒:本脚本仅供学习交流使用!切勿商用!切勿商用!切勿商用!
108 |
109 | # 🌎相关地址
110 | * 更新日志: https://github.com/IcedWatermelonJuice/Douyin-Video-Downloader#更新日志
111 | * Greasyfork: https://greasyfork.org/scripts/431344
112 | * Github仓库: https://github.com/IcedWatermelonJuice/Douyin-Video-Downloader
113 |
114 | # 🔍参考截图
115 | * 从左往右(从上往下)依次为:推荐页、详情页、频道页、移动端分享页、直播页
116 | 
117 | 
118 | 
119 | 
120 | 
121 |
122 | # 📕更新日志
123 | <版本 1.30> 2021.12.1
124 | * 修复下载按钮丢失、失效、错位的问题
125 | * 部分页面采用jquery跨域请求(导致部分页面功能在via等浏览器上无法使用)
126 |
127 | <版本 1.29> 2021.12.1
128 | * 修复登录面板打不开的问题
129 | * 设置页新增登录弹窗相关设置
130 | * 直播间设置页新增直播推流视频格式设置
131 | * 部分样式优化
132 | * 新增一部分关键点的控制台输出,便于后续排查问题
133 |
134 | <版本 1.28> 2021.12.1
135 | * 支持提取抖音直播间真实推流地址
136 | * 算法优化,并修复了部分bug
137 |
138 | <版本 1.27> 2021.11.30
139 | * 直播页详情增加一个设置页
140 | * 沉浸式观看模式支持自定义,支持自动启动
141 | * 沉浸式观看模式增加对聊天窗口的隐藏
142 | * 移除原本的相关直播隐藏/显示功能
143 | * 设置页样式优化
144 |
145 | <版本 1.26> 2021.11.28
146 | * 增加一个设置页。设置页入口位于右下角(抖音网页版的“意见反馈边上”),图标为一倾斜扳手。
147 | * 设置页支持自定义是否自动下载
148 | * 设置页支持自定义是否自动下载时重命名的格式(“完整”、“仅视频名”、“数字id”)
149 |
150 | <版本 1.25> 2021.11.27
151 | * 下载的视频如果能获取到视频相关信息,则保存为“视频名+@+作者名.mp4”;如果获取不到视频相关信息,则保存为“抖音无水印视频.mp4”
152 | * 视频路径增加对字节CDN的支持(zjcdn.com)
153 | * swiper类型播放器(推荐页、关注页)相关算法改进
154 |
155 | <版本 1.24> 2021.11.26
156 | * 修复由于抖音结构大改导致的按钮全部失效的问题
157 | * 直播间增加“沉浸式观看”按钮,可以隐藏所有与直播无关的内容,提高观看体验
158 |
159 | <版本 1.23> 2021.11.12
160 | * 修复部分页面下载按钮失效问题
161 | * 修复直播间无法自动隐藏相关直播的问题
162 |
163 | <版本 1.22> 2021.11.10
164 | * 解决移动端分享页下载按钮丢失的问题
165 |
166 | <版本 1.21> 2021.10.22
167 | * 修复按钮错位bug
168 | * 建议1.20版本升级到1.21版本
169 |
170 | <版本 1.20> 2021.10.21
171 | * 紧急修复下载器突然无法使用的问题
172 |
173 | <版本 1.19> 2021.10.15
174 | * 增加了当UA为pc时,对iesdouyin.com下detail页的支持
175 | * 增加了跳转功能,当UA从mobile切换至pc时,会提示是否转跳pc页
176 | * 修复了移动设备ua时,用户登录弹窗误屏蔽的问题
177 | * 优化了部分代码
178 |
179 | <版本 1.18> 2021.10.15
180 | * 优化代码
181 | * 修复了部分移动分享页无法下载的问题
182 |
183 | <版本 1.17> 2021.10.14
184 | * 代码大幅度重构,降低后续维护难度
185 | * 修复了侧栏在部分页面无法全部展开的问题
186 | * 修复了弹窗有时会误屏蔽或漏屏蔽的问题
187 |
188 | <版本 1.16> 2021.10.13
189 | * 修复了在手机分享页内"点击下载"按钮失效的问题
190 | * 在直播详情页内增加了一个"相关直播"的隐藏按钮
191 |
192 | <版本 1.15> 2021.10.9
193 | * 屏蔽弹窗功能增加了一个对强制登录弹窗的屏蔽
194 |
195 | <版本 1.14> 2021.9.23
196 | * 优化代码结构
197 | * 增加了对"热点"页的支持
198 |
199 | <版本 1.13> 2021.9.13
200 | * 增加了对"搜索结果"的支持
201 |
202 | <版本 1.12> 2021.9.2
203 | * 增加了对"关注"页的支持
204 |
205 | <版本 1.11> 2021.9.2
206 | * 增加了对移动端"分享"页的支持
207 |
208 | <版本 1.10> 2021.9.1
209 | * 增加了弹窗屏蔽功能
210 |
211 | <版本 1.9> 2021.8.28
212 | * 更新了一波版号(手动滑稽.jpg,主要是这个版本忘记更新内容了)
213 |
214 | <版本 1.8> 2021.8.28
215 | * 侧栏展开功能中,把重复的项隐藏了
216 |
217 | <版本 1.7> 2021.8.28
218 | * 侧栏展开功能增加了对"直播"主页的支持
219 |
220 | <版本 1.6> 2021.8.26
221 | * 修复了下载按钮可能会丢失或错位的问题
222 |
223 | <版本 1.5> 2021.8.26
224 | * 下载按钮增加增加了右键获取下载地址的功能
225 |
226 | <版本 1.4> 2021.8.26
227 | * 修复了下载按钮可能会丢失或错位的问题
228 |
229 | <版本 1.3> 2021.8.26
230 | * 修复了脚本对"主页"支持失效的问题
231 |
232 | <版本 1.2> 2021.8.26
233 | * 增加了对"频道"页的支持
234 |
235 | <版本 1.1> 2021.8.25
236 | * 增加了脚本图标
237 |
238 | <版本 1.0> 2021.8.25
239 | * 可在频道页/视频详情页下载抖音无水印视频(仅仅支持pc版网页,移动端请将浏览器ua改成电脑ua)
240 |
--------------------------------------------------------------------------------
/抖音视频下载器.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name 抖音视频下载器
3 | // @namespace http://tampermonkey.net/
4 | // @version 1.30
5 | // @description 下载抖音APP端禁止下载的视频、下载抖音无水印视频、免登录使用大部分功能、屏蔽不必要的弹窗,适用于拥有或可安装脚本管理器的电脑或移动端浏览器,如:PC端Chrome、Edge、华为浏览器等,移动端Kiwi、Yandex、Via等
6 | // @author 那年那兔那些事
7 | // @license MIT License
8 | // @include *://*.douyin.com/*
9 | // @include *://*.douyinvod.com/*
10 | // @include *://*.idouyinvod.com/*
11 | // @include *://*.iesdouyin.com/*
12 | // @include *://*.zjcdn.com/*
13 | // @icon https://s3.bmp.ovh/imgs/2021/08/63899211b3595b11.png
14 | // @require https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js
15 | // ==/UserScript==
16 |
17 | (function() {
18 | var tools = {
19 | checkUA: function() {
20 | var UAstr = "pc";
21 | if (/Android|webOS|iPhone|iPod|BlackBerry|HarmonyOS/i.test(navigator.userAgent)) {
22 | UAstr = "mobile";
23 | }
24 | return UAstr;
25 | },
26 | identifySite: function() {
27 | var Url = window.location.href;
28 | var UAstr = this.checkUA();
29 | var res = "others";
30 | //区分UA
31 | if (UAstr === "mobile" && Url.search("douyin.com/share/video/") !== -1) {
32 | res = "appshare";
33 | } else if (UAstr === "pc") {
34 | if (Url.search("www.iesdouyin.com/video/") !== -1) {
35 | res = "detail";
36 | } else if (Url.search("www.douyin.com") !== -1) {
37 | if (Url.search("/discover") !== -1) {
38 | res = "home";
39 | } else if (location.pathname === "/") {
40 | res = "recommend";
41 | } else if (Url.search("/follow") !== -1) {
42 | res = "follow";
43 | } else if (Url.search("/hot") !== -1) {
44 | res = "hot";
45 | } else if (Url.search("/channel") !== -1) {
46 | res = "channel";
47 | } else if (Url.search("/video") !== -1) {
48 | res = "detail";
49 | } else if (Url.search("/search") !== -1) {
50 | res = "search";
51 | }
52 | }
53 | }
54 | //不区分UA
55 | if (Url.search("live.douyin.com") !== -1) {
56 | if (location.pathname === "/") {
57 | res = "livehome";
58 | } else {
59 | res = "livedetail";
60 | }
61 | } else if (/douyinvod.com|zjcdn.com/i.test(Url) && Url.search("/video/tos/") !== -1) {
62 | res = "download";
63 | }
64 | return res;
65 | },
66 | videoName: function(type, pareObj) {
67 | if (!pareObj) {
68 | pareObj = document;
69 | }
70 | var title, author, id; //0:author,1:title,2:id
71 | switch (type) {
72 | case "share":
73 | title = pareObj.getElementsByClassName("desc")[0];
74 | author = pareObj.getElementsByClassName("author-name").children[0];
75 | id = location.pathname.split("video/")[1].replace("/", "");
76 | break;
77 | case "list":
78 | author = pareObj.children[2].children[0];
79 | title = pareObj.children[1];
80 | id = pareObj.children[0].href.split("video/")[1].replace("/", "");
81 | break;
82 | case "swiper":
83 | author = pareObj.getElementsByClassName("mzZanXbP")[0];
84 | title = pareObj.getElementsByClassName("title")[0];
85 | id = pareObj.getElementsByClassName("xgplayer-icon content-wrapper hasMarginRight")[0]
86 | .href.split("?")[0].split("video/")[1].replace("/", "");
87 | break;
88 | case "video":
89 | author = pareObj.getElementsByClassName("WLXvBZ9-")[0];
90 | title = pareObj.getElementsByClassName("AQHQ2slR")[0];
91 | id = location.pathname.split("video/")[1].replace("/", "");
92 | break;
93 | default:
94 | break;
95 | }
96 | if (!title || !author || !id) {
97 | return "";
98 | }
99 | author = author.innerText.replace(/(^\s*)|(\s*$)/g, "");
100 | title = title.innerText.replace(/(^\s*)|(\s*$)/g, "").slice(0, 30); //限制30字符
101 | return title + "@" + author + "@" + id;
102 | },
103 | downloadLink: function(url, name) {
104 | if (name) {
105 | var data = "&extraData=fileName-" + set.get("fileName") + "&&download-" + set.get(
106 | "download");
107 | return encodeURI(url + data + "&video-name=" + name);
108 | } else {
109 | return decodeURI(url).split("&video-name=");
110 |
111 | }
112 | },
113 | getData: function(url) {
114 | url = url.split("&extraData=");
115 | if (!url[1]) {
116 | return "";
117 | }
118 | url = url[1].split("&&");
119 | var data = {};
120 | var temp;
121 | for (let i in url) {
122 | temp = url[i].split("-");
123 | if (temp[0] && temp[1]) {
124 | data[temp[0]] = temp[1];
125 | }
126 | }
127 | return data;
128 | },
129 | parseUrl: function() {
130 | var res = [];
131 | var downloadData = this.downloadLink(location.href);
132 | var name = downloadData[1];
133 | var setData = tools.getData(downloadData[0]);
134 | if (setData["download"] && setData["download"] !== "auto") {
135 | res[1] = false;
136 | } else {
137 | res[1] = true;
138 | }
139 | switch (setData["fileName"]) {
140 | case "videoName":
141 | name = name.split("@")[0];
142 | break;
143 | case "id":
144 | name = name.split("@")[2];
145 | break;
146 | default:
147 | name = name.split("@")[0] + "@" + name.split("@")[1];
148 | break;
149 | }
150 | name = name ? name : "抖音视频";
151 | res[0] = name;
152 | return res;
153 | },
154 | cloneJSON: function(target) {
155 | return {
156 | ...target
157 | };
158 | },
159 | extendJSON: function(origin, target) {
160 | return {
161 | ...origin,
162 | ...target
163 | };
164 | },
165 | fetchUrl: function(id) {
166 | if (typeof jQuery !== "function") {
167 | return false;
168 | }
169 | var url = "https://www.douyin.com/web/api/v2/aweme/iteminfo/?item_ids=" + id;
170 | var resUrl;
171 | $.ajax({
172 | url: url,
173 | type: "get",
174 | async: false,
175 | success: function(res) {
176 | resUrl = res.item_list[0].video.play_addr.url_list[0].replace("playwm",
177 | "play");
178 | }
179 | })
180 | return resUrl;
181 | }
182 | }
183 |
184 | var createBtn = {
185 | share: function() {
186 | if (!document.getElementById("NewDownloadBtn")) {
187 | var OldTittle = document.getElementsByClassName("author-name")[0];
188 | var VideoObj = document.getElementsByTagName("video")[0];
189 | if (OldTittle && VideoObj) {
190 | var NewTittle = OldTittle.cloneNode(true);
191 | var VideoUrl = VideoObj.src.replace("playwm", "play");
192 | VideoUrl = tools.downloadLink(VideoUrl, tools.videoName("share"));
193 | var OriginHTML = "" + NewTittle.innerHTML + "";
194 | var BtnHtml = "点击下载";
196 | NewTittle.innerHTML = OriginHTML + " " + BtnHtml;
197 | NewTittle.id = "NewDownloadBtn";
198 | OldTittle.parentElement.insertBefore(NewTittle, OldTittle);
199 | OldTittle.remove();
200 | }
201 | }
202 | },
203 | list: function(a0, i) {
204 | var a01 = a0.children[1];
205 | var a02 = document.createElement("span");
206 | a02.innerHTML =
207 | "";
208 | var a020 = a02.children[0];
209 | a020.onmouseover = function() {
210 | a020.setAttribute("fill-opacity", "1");
211 | };
212 | a020.onmouseleave = function() {
213 | a020.setAttribute("fill-opacity", "0.4");
214 | }
215 | if (a01 === undefined) {
216 | a02.onclick = function() {
217 | alert("当前项为直播间,暂时无法在列表中提取真实推流地址。请先进入直播间再提取地址");
218 | }
219 | a0.appendChild(a02);
220 | } else {
221 | var videoName = tools.videoName("list", a0.parentElement);
222 | var videoUrl = tools.fetchUrl(videoName.split("@")[2]);
223 | a02.onclick = function() {
224 | open(tools.downloadLink(videoUrl, videoName));
225 | }
226 | a0.insertBefore(a02, a01);
227 | }
228 | a0.name = "newBtn";
229 | },
230 | swiper: {
231 | create: function(BtnList) {
232 | var newBtn = BtnList.children[1].cloneNode(true);
233 | var pathLen = newBtn.children[0].children[0].children.length;
234 | if (pathLen > 1) {
235 | for (let i = 1; i < pathLen; i++) {
236 | newBtn.children[0].children[0].children[i].style.display = "none";
237 | }
238 | }
239 | newBtn.children[0].children[0].children[0].setAttribute("d",
240 | "M14 9h8v8h-8z M10 17L26 17 18 26z M7 26h22v2h-22z M7 22h2v4h-2z M27 22h2v4h-2z"
241 | );
242 | newBtn.children[1].innerHTML = "下载";
243 | newBtn.onclick = function() {
244 | document.getElementsByTagName('video')[0].pause();
245 | }
246 | var newBtnBox = document.createElement("div");
247 | newBtnBox.setAttribute("class", "newBtnDownload");
248 | newBtnBox.appendChild(newBtn);
249 | BtnList.appendChild(newBtnBox);
250 | },
251 | change: function(BtnList, videoID, presentObj) {
252 | var newBtnBox = BtnList.getElementsByClassName("newBtnDownload")[0];
253 | if (newBtnBox) {
254 | var newBtn = newBtnBox.children[0];
255 | newBtn.setAttribute("data-id", videoID);
256 | newBtn.children[0].onclick = function() {
257 | alert("正在获取地址中,请稍后再试");
258 | }
259 | newBtn.children[1].innerHTML =
260 | "下载";
261 | var videoURL = tools.fetchUrl(videoID);
262 | var videoName = tools.videoName("swiper", presentObj);
263 | videoURL = tools.downloadLink(videoURL, videoName);
264 | newBtn.children[0].onclick = function() {
265 | open(videoURL);
266 | }
267 | newBtn.children[1].innerHTML = "下载";
269 | }
270 | }
271 | },
272 | video: function(BtnList) {
273 | if (!document.getElementById("newBtnDownload")) {
274 | var videoURL = document.getElementById("RENDER_DATA").innerText;
275 | videoURL = JSON.parse(decodeURIComponent(videoURL));
276 | videoURL = videoURL.C_20.aweme.detail.video.playAddr[0].src.replace("playwm", "play");
277 | if (videoURL) {
278 | videoURL = tools.downloadLink(videoURL, tools.videoName("video"));
279 | var newBtn = BtnList.children[2].cloneNode(true);
280 | newBtn.setAttribute("id", "newBtnDownload");
281 | newBtn.children[0].children[0].setAttribute("d",
282 | "M12 7h8v8h-8z M8 15L24 15 16 24z M5 24h22v2h-22z M5 20h2v4h-2z M25 20h2v4h-2z");
283 | newBtn.children[1].setAttribute("class", "iR6dOMAO");
284 | newBtn.children[1].innerHTML = "下载";
286 | newBtn.children[0].onclick = function() {
287 | open(videoURL);
288 | }
289 | newBtn.onclick = function() {
290 | document.getElementsByTagName('video')[0].pause();
291 | }
292 | BtnList.appendChild(newBtn);
293 | }
294 | }
295 | },
296 | live: {
297 | create: function(name, id, logo, event, attribute) {
298 | var btn = document.createElement("button");
299 | btn.setAttribute("class", "VPz4-306");
300 | btn.style.margin = "0 0 0 8px";
301 | btn.innerHTML = logo + "" + name + "";
302 | btn.id = id;
303 | for (let i in event) {
304 | btn.addEventListener(i, event[i]);
305 | }
306 | for (let i in attribute) {
307 | btn.setAttribute(i, attribute[i]);
308 | }
309 | return btn;
310 | },
311 | undisturb: function() {
312 | var list = {
313 | "searchBar": {
314 | "class": "BJUkFEKo",
315 | "extraEvent": function(state) {
316 | var liveCategory = document.getElementsByClassName("xWQs9nlt KpjwjEYL")[
317 | 0];
318 | if (!liveCategory) {
319 | console.log("直播分类模块丢失");
320 | return false;
321 | }
322 | if (state === "on") {
323 | liveCategory.style.top = "0";
324 | } else {
325 | liveCategory.style.top = "";
326 | }
327 | }
328 | },
329 | "liveCategory": {
330 | "class": "l0I0l5H4",
331 | "extraEvent": null
332 | },
333 | "relativeLive": {
334 | "class": "_3zMWm4HT",
335 | "extraEvent": null
336 | },
337 | "buttomMessage": {
338 | "class": "HPcNXBOf",
339 | "extraEvent": null
340 | },
341 | "chatWindow": {
342 | "class": "ojIOhXDJ",
343 | "extraEvent": function(state) {
344 | var livePlayer = document.getElementsByClassName("Jf1GlewW")[0];
345 | if (!livePlayer) {
346 | console.log("找不到直播播放器");
347 | return false;
348 | }
349 | if (state === "on") {
350 | livePlayer.style.margin = "auto";
351 | } else {
352 | livePlayer.style.margin = "";
353 | }
354 | }
355 | },
356 | "edgeTool": {
357 | "class": "ohjo+Xk3",
358 | "extraEvent": null
359 | },
360 | };
361 | var event = {
362 | "click": function() {
363 | var state = this.getAttribute("state-data");
364 | if (state === "off") {
365 | state = "on";
366 | this.style.background = "var(--color-primary)";
367 | this.style.color = "var(--color-anti-white)";
368 | } else {
369 | state = "off";
370 | this.style.background = "";
371 | this.style.color = "";
372 | }
373 | var setData = set.get("hideList"),
374 | target, extraEvent;
375 | for (let i in setData) {
376 | target = false;
377 | extraEvent = false;
378 | if (setData[i] && list[i]) {
379 | target = document.getElementsByClassName(list[i].class)[0];
380 | extraEvent = list[i].extraEvent;
381 | if (target) {
382 | if (state === "on") {
383 | target.style.display = "none";
384 | } else {
385 | target.style.display = "";
386 | }
387 | }
388 | if (typeof extraEvent === "function") {
389 | extraEvent(state);
390 | }
391 | }
392 | }
393 | this.setAttribute("state-data", state);
394 | console.log("沉浸式观看:" + state);
395 | }
396 | }
397 | var attribute = {
398 | "state-data": "off",
399 | "title": "沉浸式观看模式:屏蔽不必要的窗口从而提高观看体验。按钮红色亮起表示已启动,非红色表示已关闭"
400 | }
401 | var btn = this.create("沉浸观看", "undisturbWatchBtn", "", event, attribute);
402 | return btn;
403 | },
404 | download: function() {
405 | var logo =
406 | "";
407 | var event = {
408 | "click": function() {
409 | var data = document.getElementById("RENDER_DATA").innerText;
410 | data = JSON.parse(decodeURIComponent(data));
411 | data = data.initialState.roomStore.roomInfo.room.stream_url;
412 | switch (set.get("download")) {
413 | case "m3u8":
414 | data = data.hls_pull_url_map["FULL_HD1"];
415 | break;
416 | case "flv":
417 | data = data.flv_pull_url["FULL_HD1"];
418 | break;
419 | default:
420 | data = data.hls_pull_url;
421 | break;
422 | }
423 | if (data && typeof data === "string") {
424 | var exportBox = document.createElement("input");
425 | exportBox.value = data;
426 | document.body.appendChild(exportBox);
427 | exportBox.select();
428 | document.execCommand('copy');
429 | exportBox.remove();
430 | alert("抖音真实推流地址已导出到剪贴板");
431 | }
432 | }
433 | }
434 | var attribute = {
435 | "title": "点击提取抖音直播真实推流地址"
436 | }
437 | var btn = this.create("提取地址", "newBtnDownload", logo, event, attribute);
438 | return btn;
439 | }
440 | },
441 | set: function() {
442 | if (document.getElementById("downloaderSettingBtn")) {
443 | return false;
444 | }
445 | var boxClassArray = ["nxsdxGGH", "ohjo+Xk3"],
446 | box;
447 | for (let i in boxClassArray) {
448 | box = document.getElementsByClassName(boxClassArray[i])[0];
449 | if (box) {
450 | break;
451 | }
452 | }
453 | var btn = document.createElement("div");
454 | btn.id = "downloaderSettingBtn";
455 | btn.style =
456 | "align-items:center;background-color: var(--color-bg-1);border-radius: 18px;bottom: 0;box-shadow: var(--shadow-2);cursor: pointer;display: flex;font-size: 0;height: 36px;justify-content: center;margin-top: 8px;width: 36px;";
457 | btn.innerHTML =
458 | "";
459 | btn.onclick = function() {
460 | if (document.getElementById("downloaderSettingPage")) {
461 | set.close();
462 | } else {
463 | set.create()
464 | }
465 | }
466 | btn.onmouseover = function() {
467 | btn.children[0].setAttribute("fill", "var(--color-text-1)");
468 | }
469 | btn.onmouseleave = function() {
470 | btn.children[0].setAttribute("fill", "var(--color-text-3)");
471 | }
472 | box.appendChild(btn);
473 | }
474 | };
475 |
476 | var init = {
477 | main: function() {
478 | Page = currentPage;
479 | console.log("当前页判断为" + Page + "页");
480 | if (Timer !== -1) {
481 | clearInterval(Timer);
482 | console.log("已释放上一定时器(ID:" + Timer + ")");
483 | Timer = -1;
484 | }
485 | },
486 | clickFn: function() {
487 | loginPopupFlag = "wait";
488 | console.log("用户登录中...");
489 | },
490 | login: function() {
491 | var ClassArray = ["SSV0NEur", "tk3nuzSi", "wlsrobSg", "OPE8io-h", "ib4UcBI5",
492 | "q6zgm94p k-vFWw3W FDOWibym scan__button",
493 | "q6zgm94p k-vFWw3W FDOWibym video-comment-high__contain__btn"
494 | ];
495 | var BtnArray = [];
496 | var LoginBtnArray, LoginBtn;
497 | for (let i = 0; i < ClassArray.length; i++) {
498 | LoginBtnArray = document.getElementsByClassName(ClassArray[i]);
499 | if (LoginBtnArray[0]) {
500 | BtnArray.push(ClassArray[i]);
501 | }
502 | }
503 | for (let i = 0; i < BtnArray.length; i++) {
504 | LoginBtn = document.getElementsByClassName(BtnArray[i]);
505 | for (let j = 0; j < LoginBtn.length; j++) {
506 | if (LoginBtn[j] && LoginBtn[j].name !== "newLoginBtn") {
507 | LoginBtn[j].addEventListener("click", init.clickFn);
508 | LoginBtn[j].name = "newLoginBtn";
509 | }
510 | }
511 | }
512 | },
513 | edge: function() {
514 | var ClassArray = ["fb2dec3549d317f2d5116f185d19bea8-scss",
515 | "_8344e6bcc8551f4c88c21183a102908e-scss"
516 | ];
517 | var EdgeBar;
518 | for (let i = 0; i < ClassArray.length; i++) {
519 | EdgeBar = document.getElementsByClassName(ClassArray[i])[0];
520 | if (EdgeBar) {
521 | break;
522 | }
523 | }
524 | if (EdgeBar && EdgeBar.childElementCount !== 0) {
525 | for (let i = 0; i < EdgeBar.childElementCount; i++) {
526 | var EdgeOpt = EdgeBar.children[i];
527 | if (EdgeOpt.name !== "Displaying") {
528 | if (EdgeOpt.childElementCount > 0 && EdgeOpt.children[0].href
529 | .search("200204") === -1) {
530 | EdgeOpt.style.display = "flex";
531 | }
532 | EdgeOpt.name = "Displaying";
533 | }
534 | }
535 | console.log("显示" + currentPage + "页侧栏所有选项");
536 | }
537 | },
538 | live: function() {
539 | if (set.get("undisturbWatch") === "auto") {
540 | var btn = document.getElementById("undisturbWatchBtn");
541 | if (btn) {
542 | btn.click();
543 | } else {
544 | console.log("沉浸式观看按钮丢失,自动进入沉浸式观看模式失败");
545 | }
546 | }
547 | }
548 | };
549 |
550 | var main = {
551 | others: function() {
552 | init.main();
553 | },
554 | appshare: function() {
555 | init.main();
556 | Timer = setInterval(function() {
557 | createBtn.share();
558 | }, 200);
559 | console.log("抖音视频下载器(" + Page + "页)启动,定时器(id:" + Timer + ")开启");
560 | setTimeout(function() {
561 | if (Timer !== -1) {
562 | clearInterval(Timer);
563 | console.log("2s超时,定时器(id:" + Timer + ")关闭");
564 | }
565 | }, 2000);
566 | },
567 | home: function() {
568 | init.main();
569 | if(typeof jQuery!=="function"){
570 | var msg="部分功能可能无法在此浏览器上使用\n桌面端建议使用edge浏览器,移动端建议使用kiwi浏览器";
571 | console.log(msg);
572 | alert(msg);
573 | return false;
574 | }
575 | Timer = setInterval(function() {
576 | var a = document.getElementsByClassName("_2NJWgK5p");
577 | if (a.length !== 0) {
578 | for (let i = 0; i < a.length; i++) {
579 | if (a[i].name !== "newBtn") {
580 | createBtn.list(a[i], i);
581 | }
582 | }
583 | }
584 | }, 200);
585 | console.log("抖音视频下载器(" + Page + "页)启动,定时器(id:" + Timer + ")开启");
586 | },
587 | recommend: function() {
588 | init.main();
589 | if(typeof jQuery!=="function"){
590 | var msg="部分功能可能无法在此浏览器上使用\n桌面端建议使用edge浏览器,移动端建议使用kiwi浏览器";
591 | console.log(msg);
592 | alert(msg);
593 | return false;
594 | }
595 | var BtnList, newBtnBox, presentObj, videoURL, btnObj;
596 | Timer = setInterval(function() {
597 | BtnList = document.getElementsByClassName("TvKp5rIf")[0];
598 | if (BtnList) {
599 | newBtnBox = BtnList.getElementsByClassName("newBtnDownload")[0];
600 | if (!newBtnBox) {
601 | createBtn.swiper.create(BtnList);
602 | } else {
603 | btnObj = newBtnBox.children[0];
604 | presentObj = document.getElementsByClassName(
605 | "swiper-slide _79rCAeWZ swiper-slide-active")[0];
606 | var videoID = presentObj.getElementsByClassName(
607 | "xgplayer-icon content-wrapper hasMarginRight")[0].href;
608 | videoID = videoID.split("?")[0].split("video/")[1].replace("/", "");
609 | if (videoID && btnObj.getAttribute("data-id") !== videoID) {
610 | createBtn.swiper.change(BtnList, videoID, presentObj);
611 | }
612 | }
613 | }
614 | }, 200);
615 | console.log("抖音视频下载器(" + Page + "页)启动,定时器(id:" + Timer + ")开启");
616 | },
617 | follow: function() {
618 | this.recommend();
619 | },
620 | hot: function() {
621 | this.home();
622 | },
623 | channel: function() {
624 | this.home();
625 | },
626 | detail: function() {
627 | init.main();
628 | Timer = setInterval(function() {
629 | var BtnList = document.getElementsByClassName("HF-f8Lg-")[0].children[0];
630 | if (BtnList) {
631 | if (BtnList.children[2]) {
632 | createBtn.video(BtnList);
633 | }
634 | }
635 | }, 200);
636 | console.log("抖音视频下载器(" + Page + "页)启动,定时器(id:" + Timer + ")开启");
637 | setTimeout(function() {
638 | if (Timer !== -1) {
639 | clearInterval(Timer);
640 | console.log("2s超时,定时器(id:" + Timer + ")关闭");
641 | }
642 | }, 2000);
643 | },
644 | search: function() {
645 | this.home();
646 | },
647 | livehome: function() {
648 | init.main();
649 | },
650 | livedetail: function() {
651 | init.main();
652 | window.onload = function() {
653 | var beforeBtn = document.getElementsByClassName("VPz4-306")[0];
654 | var undisturbWatchBtn = createBtn.live.undisturb();
655 | var downloadBtn = createBtn.live.download();
656 | beforeBtn.parentElement.insertBefore(undisturbWatchBtn, beforeBtn);
657 | beforeBtn.parentElement.insertBefore(downloadBtn, beforeBtn);
658 | init.live();
659 | console.log("抖音视频下载器(" + Page + "页)启动");
660 | }
661 | },
662 | download: function() {
663 | init.main();
664 | var data = tools.parseUrl();
665 | if (data[1]) {
666 | var videoOBJ = document.getElementsByTagName('video')[0];
667 | videoOBJ.pause();
668 | var a = document.createElement("a");
669 | a.href = videoOBJ.children[0].src;
670 | a.download = data[0] + ".mp4";
671 | a.click();
672 | }
673 | },
674 | match: function() {
675 | switch (currentPage) {
676 | case "others":
677 | this.others();
678 | break;
679 | case "appshare":
680 | this.appshare();
681 | break;
682 | case "home":
683 | set.init();
684 | createBtn.set();
685 | init.edge();
686 | this.home();
687 | this.judge();
688 | break;
689 | case "recommend":
690 | set.init();
691 | createBtn.set();
692 | init.edge();
693 | this.recommend();
694 | this.judge();
695 | break;
696 | case "follow":
697 | set.init();
698 | createBtn.set();
699 | init.edge();
700 | this.follow();
701 | this.judge();
702 | break;
703 | case "hot":
704 | set.init();
705 | createBtn.set();
706 | init.edge();
707 | this.hot();
708 | this.judge();
709 | break;
710 | case "channel":
711 | set.init();
712 | createBtn.set();
713 | init.edge();
714 | this.channel();
715 | this.judge();
716 | break;
717 | case "detail":
718 | set.init();
719 | createBtn.set();
720 | this.detail();
721 | this.judge();
722 | break;
723 | case "search":
724 | set.init();
725 | createBtn.set();
726 | this.search();
727 | this.judge();
728 | break;
729 | case "livehome":
730 | set.init("live");
731 | createBtn.set();
732 | init.edge();
733 | this.livehome();
734 | this.judge();
735 | break;
736 | case "livedetail":
737 | set.init("live");
738 | createBtn.set();
739 | this.livedetail();
740 | this.judge();
741 | break;
742 | case "download":
743 | this.download();
744 | break;
745 | default:
746 | console.log("当前页无匹配功能,启动默认功能(others页)");
747 | this.others();
748 | }
749 | },
750 | popup: function() {
751 | //普通弹窗,直接无脑屏蔽
752 | var ClassArray = ["login-guide-container", "athena-survey-widget",
753 | "athena-survey-widget ltr desktop-normal theme-flgd "
754 | ];
755 | var HideNum = 0;
756 | var PopObj;
757 | for (let i = 0; i < ClassArray.length; i++) {
758 | PopObj = document.getElementsByClassName(ClassArray[i])[0];
759 | if (PopObj && PopObj.style.display !== "none") {
760 | PopObj.style.display = "none";
761 | HideNum += 1;
762 | }
763 | }
764 | //登录弹窗,不能无脑屏蔽,需要考虑情况
765 | try {
766 | PopObj = document.getElementById("login-pannel").parentElement.parentElement;
767 | } catch (e) {
768 | PopObj = false;
769 | }
770 | if (loginPopupFlag === true) {
771 | if (PopObj && PopObj.style.display !== "none") {
772 | PopObj.style.display = "none";
773 | HideNum += 1;
774 | }
775 | } else {
776 | if (PopObj && PopObj.style.display === "none") {
777 | PopObj.style.display = "";
778 | }
779 | if (!PopObj && loginPopupFlag === "wait") {
780 | loginPopupFlag = true;
781 | console.log("用户取消登录或登录成功");
782 | }
783 | }
784 | //控制台输出相关信息
785 | if (HideNum > 0) {
786 | console.log(currentPage + "页检测到" + HideNum + "个非必要弹窗,已隐藏!");
787 | }
788 | },
789 | jump: function() {
790 | var currentUA = tools.checkUA();
791 | if (pastUA !== currentUA) {
792 | pastUA = currentUA;
793 | if (currentUA === "pc") {
794 | var currentHost = location.hostname;
795 | var currentPath = location.pathname;
796 | var newUrl = "";
797 | if (currentHost.search("douyin.com") !== -1) {
798 | if (currentPath.search("/share/video/") !== -1) {
799 | newUrl = "https://www.douyin.com" + currentPath.replace("/share", "");
800 | } else if (currentPath === "/home") {
801 | newUrl = "https://www.douyin.com";
802 | }
803 | }
804 | if (newUrl !== "") {
805 | var Res = confirm("点击确认跳转PC版页面");
806 | if (Res) {
807 | location.href = newUrl;
808 | } else {
809 | console.log("用户取消跳转PC版页面");
810 | }
811 | }
812 | }
813 | }
814 | },
815 | judge: function() {
816 | switch (set.get("loginPopup")) {
817 | case "auto":
818 | if (currentPage === "follow") {
819 | loginPopupFlag = false;
820 | } else {
821 | loginPopupFlag = true;
822 | init.login();
823 | }
824 | break;
825 | case "hide":
826 | loginPopupFlag = true;
827 | break;
828 | case "display":
829 | loginPopupFlag = false;
830 | break;
831 | }
832 | }
833 | }
834 |
835 | var set = {
836 | baseData: {
837 | "video": {
838 | "fileName": "whole",
839 | "diyFileName": "",
840 | "download": "auto",
841 | "loginPopup": "auto"
842 | },
843 | "live": {
844 | "undisturbWatch": "manual",
845 | "hideList": {
846 | "searchBar": true,
847 | "liveCategory": true,
848 | "relativeLive": true,
849 | "buttomMessage": true,
850 | "chatWindow": false,
851 | "edgeTool": false
852 | },
853 | "loginPopup": "auto",
854 | "download": "default"
855 | }
856 | },
857 | baseOpt: {
858 | "video": {
859 | data: [{
860 | "name": "当前版本",
861 | "type": "text",
862 | "key": "version",
863 | "value": "v1.30"
864 | }, {
865 | "name": "视频文件名",
866 | "type": "choice",
867 | "key": "fileName",
868 | "value": [{
869 | "name": "完整(默认)",
870 | "key": "whole",
871 | "description": "文件自动重命名为:视频名@作者名.mp4"
872 | }, {
873 | "name": "仅视频名",
874 | "key": "videoName",
875 | "description": "文件自动重命名为:视频名.mp4"
876 | }, {
877 | "name": "数字ID",
878 | "key": "id",
879 | "description": "视频下载地址中,紧跟着主机名的那一串形似md5的数字为视频id。文件自动重命名为:id.mp4"
880 | }]
881 | }, {
882 | "name": "视频下载",
883 | "type": "choice",
884 | "key": "download",
885 | "value": [{
886 | "name": "自动下载",
887 | "key": "auto",
888 | "description": "脚本调用下载程序,并自动重命名"
889 | }, {
890 | "name": "手动下载",
891 | "key": "manual",
892 | "description": "需手动下载视频,且手动下载模式下,将关闭自动重命名。下载时需手动更改文件名"
893 | }]
894 | }, {
895 | "name": "登录弹窗",
896 | "type": "choice",
897 | "key": "loginPopup",
898 | "value": [{
899 | "name": "自动管理",
900 | "key": "auto",
901 | "description": "自动识别场景,根据不同场合选择是否屏蔽登录弹窗"
902 | }, {
903 | "name": "直接屏蔽",
904 | "key": "hide",
905 | "description": "遇到登录弹窗,直接屏蔽"
906 | }, {
907 | "name": "不屏蔽",
908 | "key": "display",
909 | "description": "遇到登录弹窗,不进行任何操作"
910 | }]
911 | }, {
912 | "name": "反馈建议",
913 | "type": "text",
914 | "key": "feedback",
915 | "value": "点击前往反馈"
916 | }, {
917 | "name": "更新日志",
918 | "type": "text",
919 | "key": "updateLog",
920 | "value": "点击前往查看"
921 | }]
922 | },
923 | "live": {
924 | data: [{
925 | "name": "当前版本",
926 | "type": "text",
927 | "key": "version",
928 | "value": "v1.30"
929 | }, {
930 | "name": "沉浸观看",
931 | "type": "choice",
932 | "key": "undisturbWatch",
933 | "value": [{
934 | "name": "自动启动",
935 | "key": "auto",
936 | "description": "进入直播间后自动进入沉浸式观看模式,屏蔽不必要的内容"
937 | }, {
938 | "name": "手动启动",
939 | "key": "manual",
940 | "description": "需手动点击沉浸式观看按钮,从而屏蔽不必要的内容"
941 | }]
942 | }, {
943 | "name": "屏蔽列表",
944 | "type": "check",
945 | "key": "hideList",
946 | "value": [{
947 | "name": "顶部搜索",
948 | "key": "searchBar",
949 | "description": "位于页面顶部的抖音LOGO、搜索栏、登录图标等"
950 | }, {
951 | "name": "直播分类",
952 | "key": "liveCategory",
953 | "description": "位于页面顶部的直播分类导航栏"
954 | }, {
955 | "name": "相关直播",
956 | "key": "relativeLive",
957 | "description": "位于页面底部的相关直播模块"
958 | }, {
959 | "name": "底部信息",
960 | "key": "buttomMessage",
961 | "description": "位于页面底部的网站信息、相关链接等"
962 | }, {
963 | "name": "聊天窗口",
964 | "key": "chatWindow",
965 | "description": "位于直播窗口边上的聊天窗口。隐藏聊天窗口不影响直播窗口正常播放弹幕"
966 | }, {
967 | "name": "侧边工具",
968 | "key": "edgeTool",
969 | "description": "位于页面右下角的工具栏(包括脚本设置入口)"
970 | }]
971 | }, {
972 | "name": "登录弹窗",
973 | "type": "choice",
974 | "key": "loginPopup",
975 | "value": [{
976 | "name": "自动管理",
977 | "key": "auto",
978 | "description": "自动识别场景,根据不同场合选择是否屏蔽登录弹窗"
979 | }, {
980 | "name": "直接屏蔽",
981 | "key": "hide",
982 | "description": "遇到登录弹窗,直接屏蔽"
983 | }, {
984 | "name": "不屏蔽",
985 | "key": "display",
986 | "description": "遇到登录弹窗,不进行任何操作"
987 | }]
988 | }, {
989 | "name": "提取地址",
990 | "type": "choice",
991 | "key": "download",
992 | "value": [{
993 | "name": "默认地址",
994 | "key": "default",
995 | "description": "提取当前直播间画面采用的推流地址。一般情况下,抖音直播推流都为m3u8,少部分为flv。flv延迟一般比m3u8低一点点"
996 | }, {
997 | "name": "m3u8地址",
998 | "key": "m3u8",
999 | "description": "提取m3u8格式直播原画画质的推流地址。m3u8格式视频播放比较方便,但是第三方播放器播放播放直播,一般情况下延迟比官方直播间还要长几秒"
1000 | }, {
1001 | "name": "flv地址",
1002 | "key": "flv",
1003 | "description": "提取flv格式直播原画画质的推流地址。使用第三方播放器播放直播,一般情况下延迟比官方直播间还要短几秒(ps:一般直播视频从主播到用户那里都会有延迟)"
1004 | }]
1005 | }, {
1006 | "name": "反馈建议",
1007 | "type": "text",
1008 | "key": "feedback",
1009 | "value": "点击前往反馈"
1010 | }, {
1011 | "name": "更新日志",
1012 | "type": "text",
1013 | "key": "updateLog",
1014 | "value": "点击前往查看"
1015 | }]
1016 | }
1017 | },
1018 | data: {},
1019 | opt: {},
1020 | get: function(key) {
1021 | if (key) {
1022 | return this.data[key];
1023 | } else {
1024 | return this.data;
1025 | }
1026 | },
1027 | edit: function(key, value) {
1028 | this.data[key] = value;
1029 | console.log(key + ":" + value);
1030 | },
1031 | save: function(data) {
1032 | data = JSON.stringify(data);
1033 | try {
1034 | JSON.parse(data); //确保data是JSON字符串
1035 | } catch (e) {
1036 | return false;
1037 | }
1038 | localStorage.setItem("downloaderSettingData", data);
1039 | },
1040 | init: function(type) {
1041 | if (type === "live") {
1042 | this.data = tools.cloneJSON(this.baseData.live);
1043 | this.opt = tools.cloneJSON(this.baseOpt.live);
1044 | } else {
1045 | this.data = tools.cloneJSON(this.baseData.video);
1046 | this.opt = tools.cloneJSON(this.baseOpt.video);
1047 | }
1048 | var localData = localStorage.getItem("downloaderSettingData");
1049 | var newData;
1050 | if (localData) {
1051 | localData = JSON.parse(localData);
1052 | this.data = tools.extendJSON(set.get(), localData);
1053 | } else {
1054 | this.save(this.data);
1055 | }
1056 | },
1057 | reset: function() {
1058 | var backupData;
1059 | if (/livehome|livedetail/i.test(currentPage)) {
1060 | backupData = set.baseData.live;
1061 | } else {
1062 | backupData = set.baseData.video;
1063 | }
1064 | set.data = tools.cloneJSON(backupData);
1065 | set.close();
1066 | set.create();
1067 | },
1068 | apply: function() {
1069 | var msg = "正在保存中\n应用设置需重载当前页面,是否继续应用设置?\n";
1070 | if (confirm(msg)) {
1071 | var opts = document.getElementsByClassName("downloaderSettingPage-opt");
1072 | var obj, key, value;
1073 | for (let i = 0; i < opts.length; i++) {
1074 | if (opts[i].getAttribute("opt-type") === "choice") {
1075 | obj = opts[i].getElementsByTagName("select")[0];
1076 | key = obj.parentElement.getAttribute("opt-key");
1077 | value = false;
1078 | for (let i = 0; i < obj.childElementCount; i++) {
1079 | if (obj.value === obj.children[i].value) {
1080 | value = obj.children[i].getAttribute("choice-key");
1081 | break;
1082 | }
1083 | }
1084 | if (key && value) {
1085 | set.edit(key, value);
1086 | }
1087 | } else if (opts[i].getAttribute("opt-type") === "check") {
1088 | obj = opts[i].getElementsByTagName("form")[0];
1089 | key = obj.parentElement.getAttribute("opt-key");
1090 | obj = obj.getElementsByTagName("input");
1091 | value = set.get(key);
1092 | for (let i = 0; i < obj.length; i++) {
1093 | value[obj[i].value] = obj[i].checked;
1094 | }
1095 | if (key && value) {
1096 | set.edit(key, value);
1097 | }
1098 | }
1099 | }
1100 | set.save(set.get());
1101 | location.reload();
1102 | }
1103 | },
1104 | close: function() {
1105 | document.getElementById("downloaderSettingPage").remove();
1106 | },
1107 | create: function() {
1108 | var page = document.createElement("div");
1109 | var box = document.createElement("div");
1110 | page.id = "downloaderSettingPage";
1111 | var bodyWidth = document.body.clientWidth;
1112 | var bodyHeight = document.body.clientHeight;
1113 | var pageWidth = bodyWidth - 40;
1114 | var pageHeight = bodyHeight - 40;
1115 | pageWidth = 360 < pageWidth ? 360 : pageWidth;
1116 | pageHeight = 360 < pageHeight ? 360 : pageHeight;
1117 | var pageLeft = (bodyWidth - pageWidth) / 2;
1118 | var pageTop = (bodyHeight - pageHeight) / 2;
1119 | page.style = "width:" + pageWidth + "px;height:" + pageHeight +
1120 | "px;position:fixed;left:" +
1121 | pageLeft + "px;top:" + pageTop +
1122 | "px;background:var(--color-page-bg);border-radius:20px;font-size:14px;color:var(--color-text-0-hover);border:3px solid var(--color-navigation-bg);z-index:999;";
1123 | box.style =
1124 | "width:calc(100% - 40px);height:calc(100% - 40px);margin:20px;";
1125 | box.appendChild(set.createHead());
1126 | box.appendChild(set.createBody());
1127 | box.appendChild(set.createFoot());
1128 | page.appendChild(box);
1129 | document.body.appendChild(page);
1130 | },
1131 | createHead: function() {
1132 | var head = document.createElement("div");
1133 | head.id = "downloaderSettingPage-head";
1134 | head.style =
1135 | "width:100%;height:30px;margin-bottom:20px;text-align:center;font-size:20px";
1136 | head.innerText = "抖音视频下载器脚本设置";
1137 | return head;
1138 | },
1139 | createBody: function() {
1140 | var body = document.createElement("div");
1141 | body.id = "downloaderSettingPage-body";
1142 | body.style = "width:100%;height:calc(100% - 100px);overflow:auto auto;";
1143 | var data = this.opt.data;
1144 | for (let i in data) {
1145 | body.appendChild(this.createOpt(data[i]));
1146 | }
1147 | var msg = document.createElement("div");
1148 | msg.innerHTML =
1149 | "更多功能,请关注后续版本!
欢迎大家在“油叉”或“gayhub”上反馈建议。";
1150 | msg.style = "color:red;text-decoration:none;";
1151 | body.appendChild(msg);
1152 | return body;
1153 | },
1154 | createFoot: function() {
1155 | var foot = document.createElement("div");
1156 | foot.id = "downloaderSettingPage-foot";
1157 | foot.style = "width:100%;height:30px;margin-top:20px;";
1158 | foot.appendChild(this.createBtn("reset"));
1159 | foot.appendChild(this.createBtn("apply"));
1160 | foot.appendChild(this.createBtn("close"));
1161 | return foot;
1162 | },
1163 | createOpt: function(data) {
1164 | var opt = document.createElement("div");
1165 | var title = document.createElement("div");
1166 | var content = document.createElement("div");
1167 | opt.setAttribute("class", "downloaderSettingPage-opt");
1168 | opt.setAttribute("opt-type", data.type);
1169 | title.style = "width:100px;margin:0 8px 8px 0;display:inline-block";
1170 | title.innerText = data.name;
1171 | content.style = "width:100px;margin:0 0 8px 0;display:inline-block;";
1172 | if (data.type === "text") {
1173 | content.setAttribute("opt-key", data.key);
1174 | content.innerHTML = data.value;
1175 | } else if (data.type === "choice") {
1176 | content.setAttribute("opt-key", data.key);
1177 | var choice = data.value;
1178 | var choiceValue = set.get(data.key);
1179 | var choiceHtml =
1180 | "";
1190 | content.innerHTML = choiceHtml;
1191 | } else if (data.type === "check") {
1192 | content.style.width = "100%";
1193 | content.style.display = "";
1194 | content.setAttribute("opt-key", data.key);
1195 | var check = data.value;
1196 | var checkValue = set.get(data.key);
1197 | var checkHtml =
1198 | "