├── 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 | ![推荐页](https://user-images.githubusercontent.com/87429695/130788855-0a08659d-bce2-412c-ae24-bff209fbb33d.png) 117 | ![详情页](https://user-images.githubusercontent.com/87429695/130788874-be412740-a314-4616-8a86-5e9fad8b9889.png) 118 | ![频道](https://user-images.githubusercontent.com/87429695/130845639-ad4afe36-f594-4d3b-9994-bd5e2881a7b8.png) 119 | ![移动端分享页](https://user-images.githubusercontent.com/87429695/131793226-9f7f02f6-ec68-4475-961e-eba5618987e7.png) 120 | ![直播页](https://user-images.githubusercontent.com/87429695/144091848-dedbc52a-dfcc-45c5-8beb-e6fcdac77ae7.PNG) 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 | "
"; 1199 | for (let i in check) { 1200 | checkHtml += "
"; 1206 | } 1207 | checkHtml += "
"; 1208 | content.innerHTML = checkHtml; 1209 | } else { 1210 | return null; 1211 | } 1212 | opt.appendChild(title); 1213 | opt.appendChild(content); 1214 | return opt; 1215 | }, 1216 | createBtn: function(type) { 1217 | var text, fn, btn; 1218 | switch (type) { 1219 | case "reset": 1220 | text = "重置数据"; 1221 | fn = set.reset; 1222 | break; 1223 | case "apply": 1224 | text = "保存并应用"; 1225 | fn = set.apply; 1226 | break; 1227 | case "close": 1228 | text = "关闭设置"; 1229 | fn = set.close; 1230 | break; 1231 | default: 1232 | break; 1233 | } 1234 | if (text && fn) { 1235 | btn = document.createElement("span"); 1236 | btn.id = "downloaderSettingPage-btn-" + type; 1237 | btn.setAttribute("class", "downloaderSettingPage-btn"); 1238 | var box = document.createElement("div"); 1239 | box.style = 1240 | "margin-right: 12px; padding: 0px 10px; cursor: pointer; border: thin solid var(--color-text-0-hover); border-radius: 10px;display:inline-block"; 1241 | box.innerText = text; 1242 | box.onclick = fn; 1243 | btn.appendChild(box); 1244 | } 1245 | return btn; 1246 | } 1247 | } 1248 | 1249 | var Timer = -1; 1250 | var Page = "others"; 1251 | var currentPage = "others"; 1252 | var pastUA = ""; 1253 | var loginPopupFlag = true; 1254 | var checkTimer = setInterval(function() { 1255 | currentPage = tools.identifySite(); 1256 | main.jump(); 1257 | main.popup(); 1258 | if (Page !== currentPage) { 1259 | if (Page !== "others") { 1260 | console.log("页面切换(上一页为" + Page + "页)"); 1261 | } 1262 | main.match(); 1263 | } 1264 | }, 200); 1265 | console.log("抖音视频下载器(URL监听与弹窗检测)启动,定时器(id:" + checkTimer + ")开启"); 1266 | })(); 1267 | -------------------------------------------------------------------------------- /test/抖音视频下载器.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name 抖音视频下载器 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.31 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")[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, "").replace(/^@/, ""); 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 | var VideoUrl = VideoObj.src.replace("playwm", "play"); 190 | VideoUrl = tools.downloadLink(VideoUrl, tools.videoName("share")); 191 | if (OldTittle) { 192 | var NewTittle = OldTittle.cloneNode(true); 193 | var OriginHTML = "" + NewTittle.innerHTML + ""; 194 | var BtnHtml = 195 | "点击下载"; 196 | NewTittle.innerHTML = OriginHTML + " " + BtnHtml; 197 | NewTittle.id = "NewDownloadBtn"; 198 | OldTittle.parentElement.insertBefore(NewTittle, OldTittle); 199 | OldTittle.remove(); 200 | } 201 | var downloadBtn = document.getElementById("downloadBtn"); 202 | var downloadBtnEvent = { 203 | clickFn: function() { 204 | if (VideoUrl) { 205 | location.href = VideoUrl; 206 | } else { 207 | alert("正在获取视频地址,请稍后再试"); 208 | } 209 | }, 210 | longPress: function() { 211 | set.create(); 212 | } 213 | } 214 | if (downloadBtn) { 215 | var waitTimer = -1; 216 | downloadBtn.addEventListener("touchstart", function() { 217 | waitTimer = setTimeout(function() { 218 | waitTimer = -1; 219 | downloadBtnEvent.longPress(); 220 | }, 500); 221 | }) 222 | downloadBtn.addEventListener("touchend", function() { 223 | if (waitTimer !== -1) { 224 | clearTimeout(waitTimer); 225 | waitTimer = -1; 226 | downloadBtnEvent.clickFn(); 227 | } 228 | }) 229 | } 230 | } 231 | }, 232 | list: function(a0, i) { 233 | var a01 = a0.children[1]; 234 | var a02 = document.createElement("span"); 235 | a02.innerHTML = 236 | ""; 237 | var a020 = a02.children[0]; 238 | a020.onmouseover = function() { 239 | a020.setAttribute("fill-opacity", "1"); 240 | }; 241 | a020.onmouseleave = function() { 242 | a020.setAttribute("fill-opacity", "0.4"); 243 | } 244 | if (a01 === undefined) { 245 | a02.onclick = function() { 246 | alert("当前项为直播间,暂时无法在列表中提取真实推流地址。请先进入直播间再提取地址"); 247 | } 248 | a0.appendChild(a02); 249 | } else { 250 | var videoName = tools.videoName("list", a0.parentElement); 251 | var videoUrl = tools.fetchUrl(videoName.split("@")[2]); 252 | a02.onclick = function() { 253 | open(tools.downloadLink(videoUrl, videoName)); 254 | } 255 | a0.insertBefore(a02, a01); 256 | } 257 | a0.name = "newBtn"; 258 | }, 259 | swiper: { 260 | create: function(BtnList) { 261 | var newBtn = BtnList.children[1].cloneNode(true); 262 | var pathLen = newBtn.children[0].children[0].children.length; 263 | if (pathLen > 1) { 264 | for (let i = 1; i < pathLen; i++) { 265 | newBtn.children[0].children[0].children[i].style.display = "none"; 266 | } 267 | } 268 | newBtn.children[0].children[0].children[0].setAttribute("d", 269 | "M14 9h8v8h-8z M10 17L26 17 18 26z M7 26h22v2h-22z M7 22h2v4h-2z M27 22h2v4h-2z" 270 | ); 271 | newBtn.children[1].innerHTML = "下载"; 272 | newBtn.onclick = function() { 273 | document.getElementsByTagName('video')[0].pause(); 274 | } 275 | var newBtnBox = document.createElement("div"); 276 | newBtnBox.setAttribute("class", "newBtnDownload"); 277 | newBtnBox.appendChild(newBtn); 278 | BtnList.appendChild(newBtnBox); 279 | }, 280 | change: function(BtnList, videoID, presentObj) { 281 | var newBtnBox = BtnList.getElementsByClassName("newBtnDownload")[0]; 282 | if (newBtnBox) { 283 | var newBtn = newBtnBox.children[0]; 284 | newBtn.setAttribute("data-id", videoID); 285 | newBtn.children[0].onclick = function() { 286 | alert("正在获取地址中,请稍后再试"); 287 | } 288 | newBtn.children[1].innerHTML = 289 | "下载"; 290 | var videoURL = tools.fetchUrl(videoID); 291 | var videoName = tools.videoName("swiper", presentObj); 292 | videoURL = tools.downloadLink(videoURL, videoName); 293 | newBtn.children[0].onclick = function() { 294 | open(videoURL); 295 | } 296 | newBtn.children[1].innerHTML = "下载"; 298 | } 299 | } 300 | }, 301 | video: function(BtnList) { 302 | if (!document.getElementById("newBtnDownload")) { 303 | var videoURL = document.getElementById("RENDER_DATA").innerText; 304 | videoURL = JSON.parse(decodeURIComponent(videoURL)); 305 | videoURL = videoURL.C_20.aweme.detail.video.playAddr[0].src.replace("playwm", "play"); 306 | if (videoURL) { 307 | videoURL = tools.downloadLink(videoURL, tools.videoName("video")); 308 | var newBtn = BtnList.children[2].cloneNode(true); 309 | newBtn.setAttribute("id", "newBtnDownload"); 310 | newBtn.children[0].children[0].setAttribute("d", 311 | "M12 7h8v8h-8z M8 15L24 15 16 24z M5 24h22v2h-22z M5 20h2v4h-2z M25 20h2v4h-2z"); 312 | newBtn.children[1].setAttribute("class", "iR6dOMAO"); 313 | newBtn.children[1].innerHTML = "下载"; 315 | newBtn.children[0].onclick = function() { 316 | open(videoURL); 317 | } 318 | newBtn.onclick = function() { 319 | document.getElementsByTagName('video')[0].pause(); 320 | } 321 | BtnList.appendChild(newBtn); 322 | } 323 | } 324 | }, 325 | live: { 326 | create: function(name, id, logo, event, attribute) { 327 | var btn = document.createElement("button"); 328 | btn.setAttribute("class", "VPz4-306"); 329 | btn.style.margin = "0 0 0 8px"; 330 | btn.innerHTML = logo + "" + name + ""; 331 | btn.id = id; 332 | for (let i in event) { 333 | btn.addEventListener(i, event[i]); 334 | } 335 | for (let i in attribute) { 336 | btn.setAttribute(i, attribute[i]); 337 | } 338 | return btn; 339 | }, 340 | undisturb: function() { 341 | var list = { 342 | "searchBar": { 343 | "class": "BJUkFEKo", 344 | "extraEvent": function(state) { 345 | var liveCategory = document.getElementsByClassName("xWQs9nlt KpjwjEYL")[ 346 | 0]; 347 | if (!liveCategory) { 348 | console.log("直播分类模块丢失"); 349 | return false; 350 | } 351 | if (state === "on") { 352 | liveCategory.style.top = "0"; 353 | } else { 354 | liveCategory.style.top = ""; 355 | } 356 | } 357 | }, 358 | "liveCategory": { 359 | "class": "l0I0l5H4", 360 | "extraEvent": null 361 | }, 362 | "relativeLive": { 363 | "class": "_3zMWm4HT", 364 | "extraEvent": null 365 | }, 366 | "buttomMessage": { 367 | "class": "HPcNXBOf", 368 | "extraEvent": null 369 | }, 370 | "chatWindow": { 371 | "class": "ojIOhXDJ", 372 | "extraEvent": function(state) { 373 | var livePlayer = document.getElementsByClassName("Jf1GlewW")[0]; 374 | if (!livePlayer) { 375 | console.log("找不到直播播放器"); 376 | return false; 377 | } 378 | if (state === "on") { 379 | livePlayer.style.margin = "auto"; 380 | } else { 381 | livePlayer.style.margin = ""; 382 | } 383 | } 384 | }, 385 | "edgeTool": { 386 | "class": "ohjo+Xk3", 387 | "extraEvent": null 388 | }, 389 | }; 390 | var event = { 391 | "click": function() { 392 | var state = this.getAttribute("state-data"); 393 | if (state === "off") { 394 | state = "on"; 395 | this.style.background = "var(--color-primary)"; 396 | this.style.color = "var(--color-anti-white)"; 397 | } else { 398 | state = "off"; 399 | this.style.background = ""; 400 | this.style.color = ""; 401 | } 402 | var setData = set.get("hideList"), 403 | target, extraEvent; 404 | for (let i in setData) { 405 | target = false; 406 | extraEvent = false; 407 | if (setData[i] && list[i]) { 408 | target = document.getElementsByClassName(list[i].class)[0]; 409 | extraEvent = list[i].extraEvent; 410 | if (target) { 411 | if (state === "on") { 412 | target.style.display = "none"; 413 | } else { 414 | target.style.display = ""; 415 | } 416 | } 417 | if (typeof extraEvent === "function") { 418 | extraEvent(state); 419 | } 420 | } 421 | } 422 | this.setAttribute("state-data", state); 423 | console.log("沉浸式观看:" + state); 424 | } 425 | } 426 | var attribute = { 427 | "state-data": "off", 428 | "title": "沉浸式观看模式:屏蔽不必要的窗口从而提高观看体验。按钮红色亮起表示已启动,非红色表示已关闭" 429 | } 430 | var btn = this.create("沉浸观看", "undisturbWatchBtn", "", event, attribute); 431 | return btn; 432 | }, 433 | download: function() { 434 | var logo = 435 | ""; 436 | var event = { 437 | "click": function() { 438 | var data = document.getElementById("RENDER_DATA").innerText; 439 | data = JSON.parse(decodeURIComponent(data)); 440 | data = data.initialState.roomStore.roomInfo.room.stream_url; 441 | switch (set.get("download")) { 442 | case "m3u8": 443 | data = data.hls_pull_url_map["FULL_HD1"]; 444 | break; 445 | case "flv": 446 | data = data.flv_pull_url["FULL_HD1"]; 447 | break; 448 | default: 449 | data = data.hls_pull_url; 450 | break; 451 | } 452 | if (data && typeof data === "string") { 453 | var exportBox = document.createElement("input"); 454 | exportBox.value = data; 455 | document.body.appendChild(exportBox); 456 | exportBox.select(); 457 | document.execCommand('copy'); 458 | exportBox.remove(); 459 | alert("抖音真实推流地址已导出到剪贴板"); 460 | } 461 | } 462 | } 463 | var attribute = { 464 | "title": "点击提取抖音直播真实推流地址" 465 | } 466 | var btn = this.create("提取地址", "newBtnDownload", logo, event, attribute); 467 | return btn; 468 | } 469 | }, 470 | set: function() { 471 | if (document.getElementById("downloaderSettingBtn")) { 472 | return false; 473 | } 474 | var boxClassArray = ["nxsdxGGH", "ohjo+Xk3"], 475 | box; 476 | for (let i in boxClassArray) { 477 | box = document.getElementsByClassName(boxClassArray[i])[0]; 478 | if (box) { 479 | break; 480 | } 481 | } 482 | var btn = document.createElement("div"); 483 | btn.id = "downloaderSettingBtn"; 484 | btn.style = 485 | "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;"; 486 | btn.innerHTML = 487 | ""; 488 | btn.onclick = function() { 489 | if (document.getElementById("downloaderSettingPage")) { 490 | set.close(); 491 | } else { 492 | set.create() 493 | } 494 | } 495 | btn.onmouseover = function() { 496 | btn.children[0].setAttribute("fill", "var(--color-text-1)"); 497 | } 498 | btn.onmouseleave = function() { 499 | btn.children[0].setAttribute("fill", "var(--color-text-3)"); 500 | } 501 | box.appendChild(btn); 502 | }, 503 | share:function(){ 504 | var shareBox=document.getElementsByClassName("VYAVbHvT")[0]; 505 | if(shareBox&&shareBox.name!=="newShareBox"){ 506 | var tokenBtn=shareBox.children[1]; 507 | var linkBtn=tokenBtn.cloneNode(true); 508 | var shortLink=shareBox.firstChild.innerText; 509 | shortLink=/(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/g.exec(shortLink)[0]; 510 | linkBtn.setAttribute("data-shortLink",shortLink); 511 | linkBtn.onclick=function(){ 512 | var copyBox=document.createElement("input"); 513 | copyBox.value=this.getAttribute("data-shortLink"); 514 | document.body.appendChild(copyBox); 515 | copyBox.select(); 516 | document.execCommand('copy'); 517 | copyBox.remove(); 518 | var alertMsg=document.createElement("div"); 519 | alertMsg.setAttribute("class","mwDSfjqo cqwXIZ7n"); 520 | alertMsg.innerText="分享短链已复制到剪贴板"; 521 | setTimeout(function() {alertMsg.remove()}, 3500); 522 | document.getElementsByClassName("mylaiMgB")[0].appendChild(alertMsg); 523 | } 524 | linkBtn.innerText="短链"; 525 | linkBtn.id="shortLinkShareBtn"; 526 | linkBtn.style.marginLeft="2px"; 527 | tokenBtn.innerText="口令"; 528 | var btnWidth=Math.ceil(getComputedStyle(tokenBtn).width.replace("px","")); 529 | var textWidth=Math.floor(getComputedStyle(shareBox.firstChild).width.replace("px","")); 530 | shareBox.firstChild.style.width=(textWidth-btnWidth-20)+"px"; 531 | shareBox.lastChild.style.borderRadius="0px"; 532 | shareBox.appendChild(linkBtn); 533 | shareBox.name="newShareBox"; 534 | } 535 | } 536 | }; 537 | 538 | var init = { 539 | main: function() { 540 | Page = currentPage; 541 | console.log("当前页判断为" + Page + "页"); 542 | if (Timer !== -1) { 543 | clearInterval(Timer); 544 | console.log("已释放上一定时器(ID:" + Timer + ")"); 545 | Timer = -1; 546 | } 547 | }, 548 | clickFn: function() { 549 | loginPopupFlag = "wait"; 550 | console.log("用户登录中..."); 551 | }, 552 | login: function() { 553 | var ClassArray = ["SSV0NEur", "tk3nuzSi", "wlsrobSg", "OPE8io-h", "ib4UcBI5", 554 | "q6zgm94p k-vFWw3W FDOWibym scan__button", 555 | "q6zgm94p k-vFWw3W FDOWibym video-comment-high__contain__btn" 556 | ]; 557 | var BtnArray = []; 558 | var LoginBtnArray, LoginBtn; 559 | for (let i = 0; i < ClassArray.length; i++) { 560 | LoginBtnArray = document.getElementsByClassName(ClassArray[i]); 561 | if (LoginBtnArray[0]) { 562 | BtnArray.push(ClassArray[i]); 563 | } 564 | } 565 | for (let i = 0; i < BtnArray.length; i++) { 566 | LoginBtn = document.getElementsByClassName(BtnArray[i]); 567 | for (let j = 0; j < LoginBtn.length; j++) { 568 | if (LoginBtn[j] && LoginBtn[j].name !== "newLoginBtn") { 569 | LoginBtn[j].addEventListener("click", init.clickFn); 570 | LoginBtn[j].name = "newLoginBtn"; 571 | } 572 | } 573 | } 574 | }, 575 | edge: function() { 576 | var ClassArray = ["fb2dec3549d317f2d5116f185d19bea8-scss", 577 | "_8344e6bcc8551f4c88c21183a102908e-scss" 578 | ]; 579 | var EdgeBar; 580 | for (let i = 0; i < ClassArray.length; i++) { 581 | EdgeBar = document.getElementsByClassName(ClassArray[i])[0]; 582 | if (EdgeBar) { 583 | break; 584 | } 585 | } 586 | if (EdgeBar && EdgeBar.childElementCount !== 0) { 587 | for (let i = 0; i < EdgeBar.childElementCount; i++) { 588 | var EdgeOpt = EdgeBar.children[i]; 589 | if (EdgeOpt.name !== "Displaying") { 590 | if (EdgeOpt.childElementCount > 0 && EdgeOpt.children[0].href 591 | .search("200204") === -1) { 592 | EdgeOpt.style.display = "flex"; 593 | } 594 | EdgeOpt.name = "Displaying"; 595 | } 596 | } 597 | console.log("显示" + currentPage + "页侧栏所有选项"); 598 | } 599 | }, 600 | live: function() { 601 | if (set.get("undisturbWatch") === "auto") { 602 | var btn = document.getElementById("undisturbWatchBtn"); 603 | if (btn) { 604 | btn.click(); 605 | } else { 606 | console.log("沉浸式观看按钮丢失,自动进入沉浸式观看模式失败"); 607 | } 608 | } 609 | } 610 | }; 611 | 612 | var main = { 613 | others: function() { 614 | init.main(); 615 | }, 616 | appshare: function() { 617 | init.main(); 618 | Timer = setInterval(function() { 619 | createBtn.share(); 620 | }, 200); 621 | console.log("抖音视频下载器(" + Page + "页)启动,定时器(id:" + Timer + ")开启"); 622 | setTimeout(function() { 623 | if (Timer !== -1) { 624 | clearInterval(Timer); 625 | console.log("2s超时,定时器(id:" + Timer + ")关闭"); 626 | } 627 | }, 2000); 628 | }, 629 | home: function() { 630 | init.main(); 631 | if (typeof jQuery !== "function") { 632 | var msg = "部分功能可能无法在此浏览器上使用\n桌面端建议使用edge浏览器,移动端建议使用kiwi浏览器"; 633 | console.log(msg); 634 | alert(msg); 635 | return false; 636 | } 637 | Timer = setInterval(function() { 638 | var a = document.getElementsByClassName("_2NJWgK5p"); 639 | if (a.length !== 0) { 640 | for (let i = 0; i < a.length; i++) { 641 | if (a[i].name !== "newBtn") { 642 | createBtn.list(a[i], i); 643 | } 644 | } 645 | } 646 | }, 200); 647 | console.log("抖音视频下载器(" + Page + "页)启动,定时器(id:" + Timer + ")开启"); 648 | }, 649 | recommend: function() { 650 | init.main(); 651 | if (typeof jQuery !== "function") { 652 | var msg = "部分功能可能无法在此浏览器上使用\n桌面端建议使用edge浏览器,移动端建议使用kiwi浏览器"; 653 | console.log(msg); 654 | alert(msg); 655 | return false; 656 | } 657 | var BtnList, newBtnBox, presentObj, videoURL, btnObj; 658 | Timer = setInterval(function() { 659 | BtnList = document.getElementsByClassName("TvKp5rIf")[0]; 660 | if (BtnList) { 661 | newBtnBox = BtnList.getElementsByClassName("newBtnDownload")[0]; 662 | if (!newBtnBox) { 663 | createBtn.swiper.create(BtnList); 664 | } else { 665 | btnObj = newBtnBox.children[0]; 666 | presentObj = document.getElementsByClassName( 667 | "swiper-slide _79rCAeWZ swiper-slide-active")[0]; 668 | var videoID = presentObj.getElementsByClassName( 669 | "xgplayer-icon content-wrapper hasMarginRight")[0].href; 670 | videoID = videoID.split("?")[0].split("video/")[1].replace("/", ""); 671 | if (videoID && btnObj.getAttribute("data-id") !== videoID) { 672 | createBtn.swiper.change(BtnList, videoID, presentObj); 673 | } 674 | } 675 | } 676 | createBtn.share(); 677 | }, 200); 678 | console.log("抖音视频下载器(" + Page + "页)启动,定时器(id:" + Timer + ")开启"); 679 | }, 680 | follow: function() { 681 | this.recommend(); 682 | }, 683 | hot: function() { 684 | this.home(); 685 | }, 686 | channel: function() { 687 | this.home(); 688 | }, 689 | detail: function() { 690 | init.main(); 691 | Timer = setInterval(function() { 692 | var BtnList = document.getElementsByClassName("HF-f8Lg-")[0].children[0]; 693 | if (BtnList) { 694 | if (BtnList.children[2]) { 695 | createBtn.video(BtnList); 696 | } 697 | } 698 | createBtn.share(); 699 | }, 200); 700 | console.log("抖音视频下载器(" + Page + "页)启动,定时器(id:" + Timer + ")开启"); 701 | }, 702 | search: function() { 703 | this.home(); 704 | }, 705 | livehome: function() { 706 | init.main(); 707 | }, 708 | livedetail: function() { 709 | init.main(); 710 | window.onload = function() { 711 | var beforeBtn = document.getElementsByClassName("VPz4-306")[0]; 712 | var undisturbWatchBtn = createBtn.live.undisturb(); 713 | var downloadBtn = createBtn.live.download(); 714 | beforeBtn.parentElement.insertBefore(undisturbWatchBtn, beforeBtn); 715 | beforeBtn.parentElement.insertBefore(downloadBtn, beforeBtn); 716 | init.live(); 717 | console.log("抖音视频下载器(" + Page + "页)启动"); 718 | } 719 | }, 720 | download: function() { 721 | init.main(); 722 | var data = tools.parseUrl(); 723 | if (data[1]) { 724 | var videoOBJ = document.getElementsByTagName('video')[0]; 725 | videoOBJ.pause(); 726 | var a = document.createElement("a"); 727 | a.href = videoOBJ.children[0].src; 728 | a.download = data[0] + ".mp4"; 729 | a.click(); 730 | } 731 | }, 732 | match: function() { 733 | switch (currentPage) { 734 | case "others": 735 | this.others(); 736 | break; 737 | case "appshare": 738 | set.init("share"); 739 | this.appshare(); 740 | this.judge(); 741 | break; 742 | case "home": 743 | set.init(); 744 | createBtn.set(); 745 | init.edge(); 746 | this.home(); 747 | this.judge(); 748 | break; 749 | case "recommend": 750 | set.init(); 751 | createBtn.set(); 752 | init.edge(); 753 | this.recommend(); 754 | this.judge(); 755 | break; 756 | case "follow": 757 | set.init(); 758 | createBtn.set(); 759 | init.edge(); 760 | this.follow(); 761 | this.judge(); 762 | break; 763 | case "hot": 764 | set.init(); 765 | createBtn.set(); 766 | init.edge(); 767 | this.hot(); 768 | this.judge(); 769 | break; 770 | case "channel": 771 | set.init(); 772 | createBtn.set(); 773 | init.edge(); 774 | this.channel(); 775 | this.judge(); 776 | break; 777 | case "detail": 778 | set.init(); 779 | createBtn.set(); 780 | this.detail(); 781 | this.judge(); 782 | break; 783 | case "search": 784 | set.init(); 785 | createBtn.set(); 786 | this.search(); 787 | this.judge(); 788 | break; 789 | case "livehome": 790 | set.init("live"); 791 | createBtn.set(); 792 | init.edge(); 793 | this.livehome(); 794 | this.judge(); 795 | break; 796 | case "livedetail": 797 | set.init("live"); 798 | createBtn.set(); 799 | this.livedetail(); 800 | this.judge(); 801 | break; 802 | case "download": 803 | this.download(); 804 | break; 805 | default: 806 | console.log("当前页无匹配功能,启动默认功能(others页)"); 807 | this.others(); 808 | } 809 | }, 810 | popup: function() { 811 | //普通弹窗,直接无脑屏蔽 812 | var ClassArray = ["login-guide-container", "athena-survey-widget", 813 | "athena-survey-widget ltr desktop-normal theme-flgd " 814 | ]; 815 | var HideNum = 0; 816 | var PopObj; 817 | for (let i = 0; i < ClassArray.length; i++) { 818 | PopObj = document.getElementsByClassName(ClassArray[i])[0]; 819 | if (PopObj && PopObj.style.display !== "none") { 820 | PopObj.style.display = "none"; 821 | HideNum += 1; 822 | } 823 | } 824 | //登录弹窗,不能无脑屏蔽,需要考虑情况 825 | try { 826 | PopObj = document.getElementById("login-pannel").parentElement.parentElement; 827 | } catch (e) { 828 | PopObj = false; 829 | } 830 | if (loginPopupFlag === true) { 831 | if (PopObj && PopObj.style.display !== "none") { 832 | PopObj.style.display = "none"; 833 | HideNum += 1; 834 | } 835 | } else { 836 | if (PopObj && PopObj.style.display === "none") { 837 | PopObj.style.display = ""; 838 | } 839 | if (!PopObj && loginPopupFlag === "wait") { 840 | loginPopupFlag = true; 841 | console.log("用户取消登录或登录成功"); 842 | } 843 | } 844 | //控制台输出相关信息 845 | if (HideNum > 0) { 846 | console.log(currentPage + "页检测到" + HideNum + "个非必要弹窗,已隐藏!"); 847 | } 848 | }, 849 | jump: function() { 850 | var currentUA = tools.checkUA(); 851 | if (pastUA !== currentUA) { 852 | pastUA = currentUA; 853 | if (currentUA === "pc") { 854 | var currentHost = location.hostname; 855 | var currentPath = location.pathname; 856 | var newUrl = ""; 857 | if (currentHost.search("douyin.com") !== -1) { 858 | if (currentPath.search("/share/video/") !== -1) { 859 | newUrl = "https://www.douyin.com" + currentPath.replace("/share", ""); 860 | } else if (currentPath === "/home") { 861 | newUrl = "https://www.douyin.com"; 862 | } 863 | } 864 | if (newUrl !== "") { 865 | var Res = confirm("点击确认跳转PC版页面"); 866 | if (Res) { 867 | location.href = newUrl; 868 | } else { 869 | console.log("用户取消跳转PC版页面"); 870 | } 871 | } 872 | } 873 | } 874 | }, 875 | judge: function() { 876 | switch (set.get("loginPopup")) { 877 | case "auto": 878 | if (currentPage === "follow") { 879 | loginPopupFlag = false; 880 | } else { 881 | loginPopupFlag = true; 882 | init.login(); 883 | } 884 | break; 885 | case "hide": 886 | loginPopupFlag = true; 887 | break; 888 | case "display": 889 | loginPopupFlag = false; 890 | break; 891 | } 892 | } 893 | } 894 | 895 | var set = { 896 | baseData: { 897 | "video": { 898 | "fileName": "whole", 899 | "diyFileName": "", 900 | "download": "auto", 901 | "loginPopup": "auto" 902 | }, 903 | "live": { 904 | "undisturbWatch": "manual", 905 | "hideList": { 906 | "searchBar": true, 907 | "liveCategory": true, 908 | "relativeLive": true, 909 | "buttomMessage": true, 910 | "chatWindow": false, 911 | "edgeTool": false 912 | }, 913 | "loginPopup": "auto", 914 | "download": "default" 915 | } 916 | }, 917 | baseOpt: { 918 | "video": { 919 | data: [{ 920 | "name": "当前版本", 921 | "type": "text", 922 | "key": "version", 923 | "value": "v1.31" 924 | }, { 925 | "name": "视频文件名", 926 | "type": "choice", 927 | "key": "fileName", 928 | "value": [{ 929 | "name": "完整(默认)", 930 | "key": "whole", 931 | "description": "文件自动重命名为:视频名@作者名.mp4" 932 | }, { 933 | "name": "仅视频名", 934 | "key": "videoName", 935 | "description": "文件自动重命名为:视频名.mp4" 936 | }, { 937 | "name": "数字ID", 938 | "key": "id", 939 | "description": "视频下载地址中,紧跟着主机名的那一串形似md5的数字为视频id。文件自动重命名为:id.mp4" 940 | }] 941 | }, { 942 | "name": "视频下载", 943 | "type": "choice", 944 | "key": "download", 945 | "value": [{ 946 | "name": "自动下载", 947 | "key": "auto", 948 | "description": "脚本调用下载程序,并自动重命名" 949 | }, { 950 | "name": "手动下载", 951 | "key": "manual", 952 | "description": "需手动下载视频,且手动下载模式下,将关闭自动重命名。下载时需手动更改文件名" 953 | }] 954 | }, { 955 | "name": "登录弹窗", 956 | "type": "choice", 957 | "key": "loginPopup", 958 | "value": [{ 959 | "name": "自动管理", 960 | "key": "auto", 961 | "description": "自动识别场景,根据不同场合选择是否屏蔽登录弹窗" 962 | }, { 963 | "name": "直接屏蔽", 964 | "key": "hide", 965 | "description": "遇到登录弹窗,直接屏蔽" 966 | }, { 967 | "name": "不屏蔽", 968 | "key": "display", 969 | "description": "遇到登录弹窗,不进行任何操作" 970 | }] 971 | }, { 972 | "name": "反馈建议", 973 | "type": "text", 974 | "key": "feedback", 975 | "value": "点击前往反馈" 976 | }, { 977 | "name": "更新日志", 978 | "type": "text", 979 | "key": "updateLog", 980 | "value": "点击前往查看" 981 | }] 982 | }, 983 | "live": { 984 | data: [{ 985 | "name": "当前版本", 986 | "type": "text", 987 | "key": "version", 988 | "value": "v1.31" 989 | }, { 990 | "name": "沉浸观看", 991 | "type": "choice", 992 | "key": "undisturbWatch", 993 | "value": [{ 994 | "name": "自动启动", 995 | "key": "auto", 996 | "description": "进入直播间后自动进入沉浸式观看模式,屏蔽不必要的内容" 997 | }, { 998 | "name": "手动启动", 999 | "key": "manual", 1000 | "description": "需手动点击沉浸式观看按钮,从而屏蔽不必要的内容" 1001 | }] 1002 | }, { 1003 | "name": "屏蔽列表", 1004 | "type": "check", 1005 | "key": "hideList", 1006 | "value": [{ 1007 | "name": "顶部搜索", 1008 | "key": "searchBar", 1009 | "description": "位于页面顶部的抖音LOGO、搜索栏、登录图标等" 1010 | }, { 1011 | "name": "直播分类", 1012 | "key": "liveCategory", 1013 | "description": "位于页面顶部的直播分类导航栏" 1014 | }, { 1015 | "name": "相关直播", 1016 | "key": "relativeLive", 1017 | "description": "位于页面底部的相关直播模块" 1018 | }, { 1019 | "name": "底部信息", 1020 | "key": "buttomMessage", 1021 | "description": "位于页面底部的网站信息、相关链接等" 1022 | }, { 1023 | "name": "聊天窗口", 1024 | "key": "chatWindow", 1025 | "description": "位于直播窗口边上的聊天窗口。隐藏聊天窗口不影响直播窗口正常播放弹幕" 1026 | }, { 1027 | "name": "侧边工具", 1028 | "key": "edgeTool", 1029 | "description": "位于页面右下角的工具栏(包括脚本设置入口)" 1030 | }] 1031 | }, { 1032 | "name": "登录弹窗", 1033 | "type": "choice", 1034 | "key": "loginPopup", 1035 | "value": [{ 1036 | "name": "自动管理", 1037 | "key": "auto", 1038 | "description": "自动识别场景,根据不同场合选择是否屏蔽登录弹窗" 1039 | }, { 1040 | "name": "直接屏蔽", 1041 | "key": "hide", 1042 | "description": "遇到登录弹窗,直接屏蔽" 1043 | }, { 1044 | "name": "不屏蔽", 1045 | "key": "display", 1046 | "description": "遇到登录弹窗,不进行任何操作" 1047 | }] 1048 | }, { 1049 | "name": "提取地址", 1050 | "type": "choice", 1051 | "key": "download", 1052 | "value": [{ 1053 | "name": "默认地址", 1054 | "key": "default", 1055 | "description": "提取当前直播间画面采用的推流地址。一般情况下,抖音直播推流都为m3u8,少部分为flv。flv延迟一般比m3u8低一点点" 1056 | }, { 1057 | "name": "m3u8地址", 1058 | "key": "m3u8", 1059 | "description": "提取m3u8格式直播原画画质的推流地址。m3u8格式视频播放比较方便,但是第三方播放器播放播放直播,一般情况下延迟比官方直播间还要长几秒" 1060 | }, { 1061 | "name": "flv地址", 1062 | "key": "flv", 1063 | "description": "提取flv格式直播原画画质的推流地址。使用第三方播放器播放直播,一般情况下延迟比官方直播间还要短几秒(ps:一般直播视频从主播到用户那里都会有延迟)" 1064 | }] 1065 | }, { 1066 | "name": "反馈建议", 1067 | "type": "text", 1068 | "key": "feedback", 1069 | "value": "点击前往反馈" 1070 | }, { 1071 | "name": "更新日志", 1072 | "type": "text", 1073 | "key": "updateLog", 1074 | "value": "点击前往查看" 1075 | }] 1076 | } 1077 | }, 1078 | data: {}, 1079 | opt: {}, 1080 | styleData: { 1081 | "background": "var(--color-page-bg)", 1082 | "border": "var(--color-navigation-bg)", 1083 | "color": "var(--color-text-0-hover)", 1084 | io: function(key, value) { 1085 | if (value) { 1086 | this[key] = value; 1087 | } else { 1088 | return this[key]; 1089 | } 1090 | } 1091 | }, 1092 | get: function(key) { 1093 | if (key) { 1094 | return this.data[key]; 1095 | } else { 1096 | return this.data; 1097 | } 1098 | }, 1099 | edit: function(key, value) { 1100 | this.data[key] = value; 1101 | console.log(key + ":" + value); 1102 | }, 1103 | save: function(data) { 1104 | data = JSON.stringify(data); 1105 | try { 1106 | JSON.parse(data); //确保data是JSON字符串 1107 | } catch (e) { 1108 | return false; 1109 | } 1110 | localStorage.setItem("downloaderSettingData", data); 1111 | }, 1112 | init: function(type) { 1113 | switch (type) { 1114 | case "live": 1115 | this.data = tools.cloneJSON(this.baseData.live); 1116 | this.opt = tools.cloneJSON(this.baseOpt.live); 1117 | break; 1118 | case "share": 1119 | this.styleData.io("background", "grey"); 1120 | this.styleData.io("border", "grey"); 1121 | this.styleData.io("color", "black"); 1122 | default: 1123 | this.data = tools.cloneJSON(this.baseData.video); 1124 | this.opt = tools.cloneJSON(this.baseOpt.video); 1125 | break; 1126 | } 1127 | var localData = localStorage.getItem("downloaderSettingData"); 1128 | var newData; 1129 | if (localData) { 1130 | localData = JSON.parse(localData); 1131 | this.data = tools.extendJSON(set.get(), localData); 1132 | } else { 1133 | this.save(this.data); 1134 | } 1135 | }, 1136 | reset: function() { 1137 | var backupData; 1138 | if (/livehome|livedetail/i.test(currentPage)) { 1139 | backupData = set.baseData.live; 1140 | } else { 1141 | backupData = set.baseData.video; 1142 | } 1143 | set.data = tools.cloneJSON(backupData); 1144 | set.close(); 1145 | set.create(); 1146 | }, 1147 | apply: function() { 1148 | var msg = "正在保存中\n应用设置需重载当前页面,是否继续应用设置?\n"; 1149 | if (confirm(msg)) { 1150 | var opts = document.getElementsByClassName("downloaderSettingPage-opt"); 1151 | var obj, key, value; 1152 | for (let i = 0; i < opts.length; i++) { 1153 | if (opts[i].getAttribute("opt-type") === "choice") { 1154 | obj = opts[i].getElementsByTagName("select")[0]; 1155 | key = obj.parentElement.getAttribute("opt-key"); 1156 | value = false; 1157 | for (let i = 0; i < obj.childElementCount; i++) { 1158 | if (obj.value === obj.children[i].value) { 1159 | value = obj.children[i].getAttribute("choice-key"); 1160 | break; 1161 | } 1162 | } 1163 | if (key && value) { 1164 | set.edit(key, value); 1165 | } 1166 | } else if (opts[i].getAttribute("opt-type") === "check") { 1167 | obj = opts[i].getElementsByTagName("form")[0]; 1168 | key = obj.parentElement.getAttribute("opt-key"); 1169 | obj = obj.getElementsByTagName("input"); 1170 | value = set.get(key); 1171 | for (let i = 0; i < obj.length; i++) { 1172 | value[obj[i].value] = obj[i].checked; 1173 | } 1174 | if (key && value) { 1175 | set.edit(key, value); 1176 | } 1177 | } 1178 | } 1179 | set.save(set.get()); 1180 | location.reload(); 1181 | } 1182 | }, 1183 | close: function() { 1184 | document.getElementById("downloaderSettingPage").remove(); 1185 | }, 1186 | create: function() { 1187 | var page = document.createElement("div"); 1188 | var box = document.createElement("div"); 1189 | page.id = "downloaderSettingPage"; 1190 | var bodyWidth = document.body.clientWidth; 1191 | var bodyHeight = document.body.clientHeight; 1192 | var pageWidth = bodyWidth - 40; 1193 | var pageHeight = bodyHeight - 40; 1194 | pageWidth = 360 < pageWidth ? 360 : pageWidth; 1195 | pageHeight = 360 < pageHeight ? 360 : pageHeight; 1196 | var pageLeft = (bodyWidth - pageWidth) / 2; 1197 | var pageTop = (bodyHeight - pageHeight) / 2; 1198 | page.style = "width:" + pageWidth + "px;height:" + pageHeight + 1199 | "px;position:fixed;left:" + 1200 | pageLeft + "px;top:" + pageTop + 1201 | "px;background:" + this.styleData.io("background") + 1202 | ";border-radius:20px;font-size:14px;color:" + this.styleData.io("color") + 1203 | ";border:3px solid " + this.styleData.io("border") + ";z-index:999;"; 1204 | box.style = 1205 | "width:calc(100% - 40px);height:calc(100% - 40px);margin:20px;"; 1206 | box.appendChild(set.createHead()); 1207 | box.appendChild(set.createBody()); 1208 | box.appendChild(set.createFoot()); 1209 | page.appendChild(box); 1210 | document.body.appendChild(page); 1211 | }, 1212 | createHead: function() { 1213 | var head = document.createElement("div"); 1214 | head.id = "downloaderSettingPage-head"; 1215 | head.style = 1216 | "width:100%;height:30px;margin-bottom:20px;text-align:center;font-size:20px"; 1217 | head.innerText = "抖音视频下载器脚本设置"; 1218 | return head; 1219 | }, 1220 | createBody: function() { 1221 | var body = document.createElement("div"); 1222 | body.id = "downloaderSettingPage-body"; 1223 | body.style = "width:100%;height:calc(100% - 100px);overflow:auto auto;"; 1224 | var data = this.opt.data; 1225 | for (let i in data) { 1226 | body.appendChild(this.createOpt(data[i])); 1227 | } 1228 | var msg = document.createElement("div"); 1229 | msg.innerHTML = 1230 | "更多功能,请关注后续版本!
欢迎大家在“油叉”或“gayhub”上反馈建议。"; 1231 | msg.style = "color:red;"; 1232 | body.appendChild(msg); 1233 | return body; 1234 | }, 1235 | createFoot: function() { 1236 | var foot = document.createElement("div"); 1237 | foot.id = "downloaderSettingPage-foot"; 1238 | foot.style = "width:100%;height:30px;margin-top:20px;"; 1239 | foot.appendChild(this.createBtn("reset")); 1240 | foot.appendChild(this.createBtn("apply")); 1241 | foot.appendChild(this.createBtn("close")); 1242 | return foot; 1243 | }, 1244 | createOpt: function(data) { 1245 | var opt = document.createElement("div"); 1246 | var title = document.createElement("div"); 1247 | var content = document.createElement("div"); 1248 | var aTemp; 1249 | opt.setAttribute("class", "downloaderSettingPage-opt"); 1250 | opt.setAttribute("opt-type", data.type); 1251 | title.style = "width:100px;margin:0 8px 8px 0;display:inline-block"; 1252 | title.innerText = data.name; 1253 | content.style = "width:100px;margin:0 0 8px 0;display:inline-block;"; 1254 | if (data.type === "text") { 1255 | content.setAttribute("opt-key", data.key); 1256 | content.innerHTML = data.value; 1257 | aTemp = content.getElementsByTagName("a"); 1258 | for (let i = 0; i < aTemp.length; i++) { 1259 | aTemp[i].style.color = this.styleData.io("color"); 1260 | } 1261 | } else if (data.type === "choice") { 1262 | content.setAttribute("opt-key", data.key); 1263 | var choice = data.value; 1264 | var choiceValue = set.get(data.key); 1265 | var choiceHtml = 1266 | ""; 1277 | content.innerHTML = choiceHtml; 1278 | } else if (data.type === "check") { 1279 | content.style.width = "100%"; 1280 | content.style.display = ""; 1281 | content.setAttribute("opt-key", data.key); 1282 | var check = data.value; 1283 | var checkValue = set.get(data.key); 1284 | var checkHtml = 1285 | "
"; 1286 | for (let i in check) { 1287 | checkHtml += "
"; 1293 | } 1294 | checkHtml += "
"; 1295 | content.innerHTML = checkHtml; 1296 | } else { 1297 | return null; 1298 | } 1299 | opt.appendChild(title); 1300 | opt.appendChild(content); 1301 | return opt; 1302 | }, 1303 | createBtn: function(type) { 1304 | var text, fn, btn; 1305 | switch (type) { 1306 | case "reset": 1307 | text = "重置数据"; 1308 | fn = set.reset; 1309 | break; 1310 | case "apply": 1311 | text = "保存并应用"; 1312 | fn = set.apply; 1313 | break; 1314 | case "close": 1315 | text = "关闭设置"; 1316 | fn = set.close; 1317 | break; 1318 | default: 1319 | break; 1320 | } 1321 | if (text && fn) { 1322 | btn = document.createElement("span"); 1323 | btn.id = "downloaderSettingPage-btn-" + type; 1324 | btn.setAttribute("class", "downloaderSettingPage-btn"); 1325 | var box = document.createElement("div"); 1326 | box.style = 1327 | "margin-right: 12px; padding: 0px 10px; cursor: pointer; border: thin solid " + this 1328 | .styleData.io("color") + "; border-radius: 10px;display:inline-block"; 1329 | box.innerText = text; 1330 | box.onclick = fn; 1331 | btn.appendChild(box); 1332 | } 1333 | return btn; 1334 | } 1335 | } 1336 | 1337 | var Timer = -1; 1338 | var Page = "others"; 1339 | var currentPage = "others"; 1340 | var pastUA = ""; 1341 | var loginPopupFlag = true; 1342 | var checkTimer = setInterval(function() { 1343 | currentPage = tools.identifySite(); 1344 | main.jump(); 1345 | main.popup(); 1346 | if (Page !== currentPage) { 1347 | if (Page !== "others") { 1348 | console.log("页面切换(上一页为" + Page + "页)"); 1349 | } 1350 | main.match(); 1351 | } 1352 | }, 200); 1353 | console.log("抖音视频下载器(URL监听与弹窗检测)启动,定时器(id:" + checkTimer + ")开启"); 1354 | })(); 1355 | --------------------------------------------------------------------------------