├── README.md
├── LICENSE
├── YouTube-PiP.js
├── VIP-002.js
├── Ins-download-images.js
└── VIP-001.js
/README.md:
--------------------------------------------------------------------------------
1 | # ZOE-User-Scripts
2 | User Scripts for ZOE video player pro
3 |
4 | [ZOE - Video Player PRO](https://apps.apple.com/us/app/zoe-video-player-pro/id1505277541)
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 简画大师
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 |
--------------------------------------------------------------------------------
/YouTube-PiP.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Youtube PiP Button
3 | // @author ACTCD
4 | // @version 20220311.3
5 | // @namespace https://t.me/ACTCD
6 | // @description Youtube Enable Picture-in-Picture ,not download
7 | // @match *://*.youtube.com/*
8 | // @grant none
9 | // @run-at document-start
10 | // ==/UserScript==
11 |
12 | (function () {
13 | 'use strict';
14 |
15 | // Create PiP Button
16 | const i1 = 'url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2221%22%20height%3D%2225%22%3E%3Ctitle%3Epip_reduced%401x%3C%2Ftitle%3E%3Crect%20width%3D%2221%22%20height%3D%2225%22%20fill%3D%22none%22%2F%3E%3Cpath%20d%3D%22M2.5%2C17A1.5%2C1.5%2C0%2C0%2C1%2C1%2C15.5v-9A1.5%2C1.5%2C0%2C0%2C1%2C2.5%2C5h13A1.5%2C1.5%2C0%2C0%2C1%2C17%2C6.5V10h1V6.5A2.5%2C2.5%2C0%2C0%2C0%2C15.5%2C4H2.5A2.5%2C2.5%2C0%2C0%2C0%2C0%2C6.5v9A2.5%2C2.5%2C0%2C0%2C0%2C2.5%2C18H7V17Z%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M18.5%2C11h-8A2.5%2C2.5%2C0%2C0%2C0%2C8%2C13.5v5A2.5%2C2.5%2C0%2C0%2C0%2C10.5%2C21h8A2.5%2C2.5%2C0%2C0%2C0%2C21%2C18.5v-5A2.5%2C2.5%2C0%2C0%2C0%2C18.5%2C11Z%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E")';
17 | const i2 = 'url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2221%22%20height%3D%2225%22%3E%3Ctitle%3Epip.fill_reduced%401x%3C%2Ftitle%3E%3Crect%20width%3D%2221%22%20height%3D%2225%22%20fill%3D%22none%22%2F%3E%3Cpath%20d%3D%22M18.5%2C11H18v1h.5A1.5%2C1.5%2C0%2C0%2C1%2C20%2C13.5v5A1.5%2C1.5%2C0%2C0%2C1%2C18.5%2C20h-8A1.5%2C1.5%2C0%2C0%2C1%2C9%2C18.5V18H8v.5A2.5%2C2.5%2C0%2C0%2C0%2C10.5%2C21h8A2.5%2C2.5%2C0%2C0%2C0%2C21%2C18.5v-5A2.5%2C2.5%2C0%2C0%2C0%2C18.5%2C11Z%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M14.5%2C4H2.5A2.5%2C2.5%2C0%2C0%2C0%2C0%2C6.5v8A2.5%2C2.5%2C0%2C0%2C0%2C2.5%2C17h12A2.5%2C2.5%2C0%2C0%2C0%2C17%2C14.5v-8A2.5%2C2.5%2C0%2C0%2C0%2C14.5%2C4Z%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E")';
18 | const pip_button = document.createElement("button");
19 | pip_button.title = "Picture-in-Picture";
20 | pip_button.style.width = "50px";
21 | pip_button.style.height = "50px";
22 | if (location.hostname == "m.youtube.com") {
23 | pip_button.style.setProperty("position", "absolute");
24 | pip_button.style.setProperty("z-index", "100");
25 | } else {
26 | pip_button.className = "ytp-button";
27 | }
28 | pip_button.style.setProperty("background-repeat", "no-repeat");
29 | pip_button.style.setProperty("background-position", "50% 50%");
30 | pip_button.style.setProperty("background-image", i1);
31 | const onEnterPip = e => pip_button.style.setProperty("background-image", i2);
32 | const onExitPip = e => pip_button.style.setProperty("background-image", i1);
33 | if (document.pictureInPictureEnabled) {
34 | pip_button.addEventListener("click", event => {
35 | if (document.pictureInPictureElement) {
36 | document.exitPictureInPicture();
37 | } else {
38 | document.querySelector("video[src]")?.requestPictureInPicture();
39 | }
40 | event.preventDefault();
41 | event.stopImmediatePropagation();
42 | });
43 | } else {
44 | pip_button.style.setProperty("opacity", "0.5");
45 | console.log('Your browser cannot use picture-in-picture right now');
46 | }
47 |
48 | // Insert PiP Button (desktop) // Fixed once for Safari
49 | let b = document.querySelector(".ytp-miniplayer-button");
50 | if (b) b.parentNode.insertBefore(pip_button, b);
51 |
52 | // Video element initialization
53 | const pip_init = video => {
54 | video.addEventListener('webkitpresentationmodechanged', e => e.stopPropagation(), true); // PiP Fix
55 | video.addEventListener("leavepictureinpicture", onExitPip);
56 | video.addEventListener('enterpictureinpicture', onEnterPip);
57 | }
58 | let v = document.querySelector('video[src]'); if (v) pip_init(v); // Fixed once for Safari
59 |
60 | // Dynamic adjustment
61 | new MutationObserver(mutationList => {
62 | mutationList.forEach((mutation) => {
63 | if (mutation.type == 'childList') {
64 | mutation.addedNodes.forEach(node => {
65 | if (node.nodeType != Node.ELEMENT_NODE) return;
66 | if (node.nodeName == 'VIDEO' && node.hasAttribute("src")) pip_init(node);
67 | if (node.id == "player-control-overlay") { // Insert PiP Button (mobile)
68 | new MutationObserver(() => {
69 | if (node.classList.contains("fadein")) {
70 | node.append(pip_button);
71 | }
72 | }).observe(node, { attributes: true });
73 | }
74 | if (node.classList.contains("ytp-miniplayer-button")) { // Insert PiP Button (desktop)
75 | node.parentNode.insertBefore(pip_button, node);
76 | }
77 | })
78 | }
79 | if (mutation.type == 'attributes') { // Enter video from the homepage
80 | if (mutation.target.nodeName == 'VIDEO' && mutation.attributeName == 'src' && mutation.target.hasAttribute("src")) {
81 | pip_init(mutation.target);
82 | }
83 | }
84 | });
85 | }).observe(document, { subtree: true, childList: true, attributes: true });
86 |
87 | })();
88 |
--------------------------------------------------------------------------------
/VIP-002.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name 通用VIP视频解析
3 | // @author 河丶蟹
4 | // @version 2.2.9
5 | // @description 爱奇艺、腾讯视频、优酷、乐视、芒果TV、搜狐、1905、PPTV、B站。
6 | // @include *
7 | // @match *
8 | // @createTime 2021-03-03 12:00:47
9 | // @updateTime 2021-06-28 22:25:16
10 | // ==/UserScript==
11 | (function() {
12 | /*
13 | * 自定义区域
14 | * 变量值可自行更改
15 | */
16 | // 主颜色(图标整体颜色,如方框颜色)
17 | const mianColor = "#de473c";
18 | // 副颜色(图标层次颜色,如字体颜色)
19 | const secondColor = "#f3f1e7";
20 | // 图标右边框距离
21 | const iconMarginRight = 2;
22 | // 图标上边框距离
23 | const iconMarginTop = 100;
24 | // 图标宽(最小30)
25 | var iconWidth = 45;
26 | // 图标高(图标大小)
27 | const iconHeight = 35;
28 | // 图标圆角比例(当高、宽一致时,0.5为圆圈)
29 | const iconFilletPercent = 0.3;
30 | // 解析接口菜单框展开的高度
31 | var developMenuHeight = 315;
32 | // 解析接口菜单框展开的速度(如果展开动画卡顿请设置0,单位是秒)
33 | var developMenuSecond = 0.2;
34 | // 解析接口(可多个)
35 | const parseInterfaces =["https://z1.m1907.cn/?eps=0&jx=","https://vip.parwix.com:4433/player/?url=","https://lecurl.cn/?url=","https://jx.m3u8.tv/jiexi/?url=","https://api.leduotv.com/wp-api/ifr.php?isDp=1&vid=","https://okjx.cc/?url=","https://m2090.com/?url=","http://51wujin.net/?url=","https://vip.2ktvb.com/player/?url=","https://660e.com/?url=","https://api.sigujx.com/?url=","https://jiexi.janan.net/jiexi/?url=","https://jx.618g.com/?url=","https://jx.ergan.top/?url=","https://api.147g.cc/m3u8.php?url=","http://17kyun.com/api.php?url="];
36 | /*
37 | * 非自定义区域
38 | * 以下代码勿动
39 | * 以下代码勿动
40 | * 以下代码勿动
41 | */
42 | // 视频网站(规则已定,不可随意更改)
43 | const videoSites = ["v.qq.com","tv.sohu.com","iqiyi.com","youku.com","mgtv.com","m.le.com","www.le.com","1905.com","pptv.com","bilibili.com"];
44 | const currentUrl = document.location.href;
45 | // 判断是否加载后续代码
46 | if (self != top) {
47 | return;
48 | }
49 | var result = videoSites.some(site=>{
50 | if (currentUrl.match(site)) {
51 | return true;
52 | }
53 | return false;
54 | })
55 | if(!result){
56 | return;
57 | }
58 | // 图标宽度最小值判断(小于30默认30)
59 | if(iconWidth<30){
60 | iconWidth=30;
61 | }
62 | // 解析接口框高度判断(小于30默认30)
63 | if(developMenuHeight<(iconWidth*2.6)){
64 | developMenuHeight=iconWidth*2.6;
65 | }
66 | // 判断PC、移动端
67 | var uaLogo="pc";
68 | if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) {
69 | uaLogo="mobile";
70 | }
71 | // 图标整体定位样式
72 | const globalStyle = "cursor:pointer;position:fixed;right:"+iconMarginRight+"px;top:"+iconMarginTop+"px;z-index:2147483647;";
73 | // 主图标(矩形)样式
74 | const mainIconStyle = "height:"+iconHeight+"px;width:"+iconWidth+"px;background:"+mianColor+";border-radius:"+(iconFilletPercent*iconWidth)+"px;box-sizing:border-box;box-shadow:-4px 4px 4px 0px rgba(0,0,0,0.4);";
75 | // 副图标(三角形)样式
76 | const triangleStyle = "border-left:"+(iconWidth*0.3)+"px solid "+secondColor+";border-top:"+(iconHeight*0.2)+"px solid transparent;border-bottom:"+(iconHeight*0.2)+"px solid transparent;position:absolute;right:31%;top:30%;";
77 | // 副图标(正方形)样式
78 | const squareStyle = "background:"+secondColor+";width:"+(iconWidth*0.26)+"px;height:"+(iconWidth*0.26)+"px;position:absolute;right:37%;top:37%;";
79 | // 菜单框外层样式
80 | const inMenuBoxStyle = "width:115%;height:100%;overflow-y:scroll;overflow-x:hidden;";
81 | // 菜单框里层样式
82 | const outMenuBoxStyle = "background:"+mianColor+";height:0px;overflow:hidden;font-size:"+(iconWidth*0.4)+"px;width:"+(iconWidth*2.4)+"px;position:absolute;right:0px;top:"+iconHeight+"px;box-shadow:-4px 4px 4px 0px rgba(0,0,0,0.4);border-radius:13px 0 1px 13px;transition:height "+developMenuSecond+"s;-moz-transition:height "+developMenuSecond+"s;-webkit-transition:height "+developMenuSecond+"s;-o-transition:height "+developMenuSecond+"s;";
83 | // 菜单项样式
84 | const MenuItemsStyle = "color:"+secondColor+";display: block;padding:"+(iconWidth*0.12)+"px "+(iconWidth*0.12)+"px "+(iconWidth*0.12)+"px "+(iconWidth*0.2)+"px ;width:"+(iconWidth*3)+"px;";
85 | // Iframe样式
86 | const IframeStyle = "frameborder='no' width='100%' height='100%' allowfullscreen='true' allowtransparency='true' frameborder='0' scrolling='no';";
87 | // 视频播放框类ID
88 | var classAndIDMap = {"pc":{"v.qq.com":"mod_player","iqiyi.com":"flashbox","youku.com":"ykPlayer","mgtv.com":"mgtv-player-wrap","sohu.com":"x-player","le.com":"fla_box","1905.com":"player","pptv.com":"pplive-player","bilibili.com":"bilibili-player-video-wrap|player-limit-mask"},"mobile":{"v.qq.com":"mod_player","iqiyi.com":"m-box","youku.com":"h5-detail-player","mgtv.com":"video-area","sohu.com":"player-view","le.com":"playB","1905.com":"player","pptv.com":"pp-details-video","bilibili.com":"bilibiliPlayer|player-wrapper"}};
89 | // 创建图标
90 | createIcon();
91 | // 判断页面加载完成以后图标是否存在
92 | document.onreadystatechange = function(){
93 | if(document.readyState == 'complete'){
94 | if(!document.getElementById("mainIcon")){
95 | createIcon();
96 | }
97 | }
98 | }
99 | function createIcon(){
100 | try{
101 | var div = document.createElement("div");
102 | div.style.cssText = globalStyle;
103 | div.setAttribute("id","mainIcon");
104 | var html = "
";
105 | for(var i in parseInterfaces){
106 | if(i==parseInterfaces.length-1){
107 | html += "线路接口"+(parseInt(i)+1)+"";
108 | }else{
109 | html += "线路接口"+(parseInt(i)+1)+"";
110 | }
111 | }
112 | html += "
";
113 | div.innerHTML = html;
114 | document.body.insertBefore(div,document.body.firstChild);
115 | div.onclick = function() {
116 | var dropDownBox = document.getElementById("dropDownBox").style.height;
117 | var mainButton = document.getElementById("mainButton");
118 | var triangle = document.getElementById("triangle");
119 | if(dropDownBox == "0px"){
120 | mainButton.style.borderRadius = (iconFilletPercent*iconWidth)+"px "+(iconFilletPercent*iconWidth)+"px 0 0";
121 | triangle.removeAttribute("style");
122 | triangle.setAttribute("style",squareStyle);
123 | document.getElementById("dropDownBox").style.height = developMenuHeight+"px";
124 | }else{
125 | document.getElementById("dropDownBox").style.height = "0px";
126 | triangle.removeAttribute("style");
127 | triangle.setAttribute("style",triangleStyle);
128 | mainButton.style.borderRadius = (iconFilletPercent*iconWidth)+"px";
129 | }
130 | }
131 | var elements = document.getElementsByClassName("spanStyle");
132 | for(var j in elements){
133 | elements[j].onmouseover = function(){
134 | this.style.background = secondColor;
135 | this.style.color = mianColor;
136 | }
137 | elements[j].onmouseout = function(){
138 | this.style.background = mianColor;
139 | this.style.color = secondColor;
140 | }
141 | elements[j].onclick=function(){
142 | var parseInterface = this.getAttribute("url");
143 | for(let key in classAndIDMap[uaLogo]){
144 | if (document.location.href.match(key)) {
145 | var values = classAndIDMap[uaLogo][key].split("|");
146 | var labelType = "";
147 | var class_id = "";
148 | for(let value in values){
149 | if(document.getElementById(values[value])){
150 | class_id = values[value];
151 | labelType = "id";
152 | break;
153 | }
154 | if(document.getElementsByClassName(values[value]).length>0){
155 | class_id = values[value];
156 | labelType = "class";
157 | break;
158 | }
159 | }
160 | if(labelType!=""&&class_id!=""){
161 | var iframe = "";
162 | if(labelType=="id"){
163 | document.getElementById(class_id).innerHTML="";
164 | document.getElementById(class_id).innerHTML=iframe;
165 | }else{
166 | document.getElementsByClassName(class_id)[0].innerHTML="";
167 | if(uaLogo=="mobile"){
168 | document.getElementsByClassName(class_id)[0].style.height="225px";
169 | }
170 | document.getElementsByClassName(class_id)[0].innerHTML=iframe;
171 | }
172 | return;
173 | }
174 | }
175 | }
176 | document.getElementById("dropDownBox").style.display = "none";
177 | }
178 | }
179 | }catch(error){
180 | // exception handling
181 | }
182 | }
183 | })();
184 |
--------------------------------------------------------------------------------
/Ins-download-images.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Instagram Download Button
3 | // @name:zh-TW Instagram 下載器
4 | // @name:zh-CN Instagram 下载器
5 | // @name:ja Instagram ダウンローダー
6 | // @name:ko Instagram 다운로더
7 | // @name:es Descargador de Instagram
8 | // @name:fr Téléchargeur Instagram
9 | // @name:hi इंस्टाग्राम डाउनलोडर
10 | // @name:ru Загрузчик Instagram
11 | // @namespace https://github.com/y252328/Instagram_Download_Button
12 | // @version 1.9.7
13 | // @compatible chrome
14 | // @compatible firefox
15 | // @compatible edge
16 | // @description Add the download button and the open button to download or open profile picture and media in the posts, stories, and highlights in Instagram
17 | // @description:zh-TW 在Instagram頁面加入下載按鈕與開啟按鈕,透過這些按鈕可以下載或開啟大頭貼與貼文、限時動態、Highlight中的照片或影片
18 | // @description:zh-CN 在Instagram页面加入下载按钮与开启按钮,透过这些按钮可以下载或开启大头贴与贴文、限时动态、Highlight中的照片或影片
19 | // @description:ja メディアをダウンロードまたは開くためのボタンを追加します
20 | // @description:ko 미디어를 다운로드하거나 여는 버튼을 추가합니다
21 | // @description:es Agregue botones para descargar o abrir medios
22 | // @description:fr Ajoutez des boutons pour télécharger ou ouvrir des médias
23 | // @description:hi मीडिया को डाउनलोड या खोलने के लिए बटन जोड़ें।
24 | // @description:ru Добавьте кнопки для загрузки или открытия медиа
25 | // @author ZhiYu
26 | // @match https://www.instagram.com/*
27 | // @grant none
28 | // @license MIT
29 | // ==/UserScript==
30 |
31 | (function () {
32 | 'use strict';
33 | // =================
34 | // = Options =
35 | // =================
36 | const attachLink = true; // add link into the button elements
37 | const postFilenameTemplate = "%id%-%datetime%-%medianame%.%ext%";
38 | const storyFilenameTemplate = postFilenameTemplate;
39 |
40 | // ==================
41 |
42 | function yyyymmdd(date) {
43 | // ref: https://stackoverflow.com/questions/3066586/get-string-in-yyyymmdd-format-from-js-date-object?page=1&tab=votes#tab-top
44 | var mm = date.getMonth() + 1; // getMonth() is zero-based
45 | var dd = date.getDate();
46 |
47 | return [date.getFullYear(),
48 | (mm > 9 ? '' : '0') + mm,
49 | (dd > 9 ? '' : '0') + dd
50 | ].join('');
51 | }
52 |
53 | var svgDownloadBtn =
54 | ``;
68 |
69 | var svgNewtabBtn =
70 | ``;
73 |
74 | document.addEventListener('keydown', keyDownHandler);
75 |
76 | function keyDownHandler(event) {
77 | if (window.location.href === 'https://www.instagram.com/') return;
78 |
79 | if (event.altKey && event.key === 'k') {
80 | let buttons = document.getElementsByClassName('download-btn');
81 | if (buttons.length > 0) {
82 | let mockEvent = { currentTarget: buttons[buttons.length - 1] };
83 | if (attachLink) onMouseInHandler(mockEvent);
84 | onClickHandler(mockEvent);
85 | }
86 | }
87 | if (event.altKey && event.key === 'i') {
88 | let buttons = document.getElementsByClassName('newtab-btn');
89 | if (buttons.length > 0) {
90 | let mockEvent = { currentTarget: buttons[buttons.length - 1] };
91 | if (attachLink) onMouseInHandler(mockEvent);
92 | onClickHandler(mockEvent);
93 | }
94 | }
95 |
96 | if (event.altKey && event.key === 'l') {
97 | // right arrow
98 | let buttons = document.getElementsByClassName('coreSpriteRightChevron');
99 | if (buttons.length > 0) {
100 | buttons[0].click();
101 | }
102 | }
103 |
104 | if (event.altKey && event.key === 'j') {
105 | // left arrow
106 | let buttons = document.getElementsByClassName('coreSpriteLeftChevron');
107 | if (buttons.length > 0) {
108 | buttons[0].click();
109 | }
110 | }
111 | }
112 |
113 | var checkExistTimer = setInterval(function () {
114 | let sharePostSelector = "article section span button";
115 | let storySeletor = "header button > div";
116 | let profileSelector = "header section svg circle";
117 |
118 | // check profile
119 | if (document.getElementsByClassName("custom-btn").length === 0) {
120 | if (document.querySelector(profileSelector)) {
121 | addCustomBtn(document.querySelector(profileSelector), "black", append2Header);
122 | }
123 | }
124 |
125 | // check post
126 | let articleList = document.querySelectorAll("article");
127 | for (let i = 0; i < articleList.length; i++) {
128 | if (articleList[i].querySelector(sharePostSelector) &&
129 | articleList[i].getElementsByClassName("custom-btn").length === 0) {
130 | addCustomBtn(articleList[i].querySelector(sharePostSelector), "black", append2Post);
131 | }
132 | }
133 |
134 | // check story
135 | if (document.getElementsByClassName("custom-btn").length === 0) {
136 | if (document.querySelector(storySeletor)) {
137 | addCustomBtn(document.querySelector(storySeletor), "white", append2Post);
138 | }
139 | }
140 | }, 500);
141 |
142 | function append2Header(node, btn) {
143 | node.parentNode.parentNode.parentNode.appendChild(btn, node.parentNode.parentNode);
144 | }
145 |
146 | function append2Post(node, btn) {
147 | node.parentNode.parentNode.appendChild(btn);
148 | }
149 |
150 | function addCustomBtn(node, iconColor, appendNode) {
151 | // add download button and set onclick handler
152 | // add newtab button
153 | let newtabBtn = createCustomBtn(svgNewtabBtn, iconColor, "newtab-btn", "16px");
154 | appendNode(node, newtabBtn);
155 |
156 | // add download button
157 | let downloadBtn = createCustomBtn(svgDownloadBtn, iconColor, "download-btn", "14px");
158 | appendNode(node, downloadBtn);
159 | }
160 |
161 | function createCustomBtn(svg, iconColor, className, marginLeft) {
162 | let newBtn = document.createElement("a");
163 | newBtn.innerHTML = svg.replace('%color', iconColor);
164 | newBtn.setAttribute("class", "custom-btn " + className);
165 | newBtn.setAttribute("target", "_blank");
166 | newBtn.setAttribute("style", "cursor: pointer;margin-left: " + marginLeft + ";margin-top: 8px;");
167 | newBtn.onclick = onClickHandler;
168 | if (attachLink) newBtn.onmouseenter = onMouseInHandler;
169 | if (className.includes("newtab")) {
170 | newBtn.setAttribute("title", "Open in new tab");
171 | } else {
172 | newBtn.setAttribute("title", "Download");
173 | }
174 | return newBtn;
175 | }
176 |
177 | function onClickHandler(e) {
178 | // handle button click
179 | let target = e.currentTarget;
180 | e.stopPropagation();
181 | e.preventDefault();
182 | if (window.location.pathname.includes('stories')) {
183 | storyOnClicked(target);
184 | } else if (document.querySelector('header') &&
185 | document.querySelector('header').contains(target)) {
186 | profileOnClicked(target);
187 | } else {
188 | postOnClicked(target);
189 | }
190 | }
191 |
192 | function onMouseInHandler(e) {
193 | if (!attachLink) return;
194 | let target = e.currentTarget;
195 | if (window.location.pathname.includes('stories')) {
196 | storyOnMouseIn(target);
197 | } else if (document.querySelector('header') &&
198 | document.querySelector('header').contains(target)) {
199 | profileOnMouseIn(target);
200 | } else {
201 | postOnMouseIn(target);
202 | }
203 | }
204 |
205 | function profileOnMouseIn(target) {
206 | let url = profileGetUrl(target);
207 | target.setAttribute("href", url);
208 | }
209 |
210 | function profileOnClicked(target) {
211 | // extract profile picture url and download or open it
212 | let url = profileGetUrl(target);
213 | let filename = '.png';
214 |
215 | if (url.length > 0) {
216 | // check url
217 | if (target.getAttribute("class").includes("download-btn")) {
218 | // generate filename
219 | let posterName = document.querySelector('header h2').textContent;
220 | filename = posterName + filename;
221 | downloadResource(url, filename);
222 | } else {
223 | // open url in new tab
224 | openResource(url);
225 | }
226 | }
227 | }
228 |
229 | function profileGetUrl(target) {
230 | let img = document.querySelector('header img');
231 | let url = img.getAttribute('src');
232 | return url;
233 | }
234 |
235 | async function postOnMouseIn(target) {
236 | let articleNode = postGetArticleNode(target);
237 | let url = await postGetUrl(target, articleNode);
238 | target.setAttribute("href", url);
239 | }
240 |
241 | async function postOnClicked(target) {
242 | // extract url from target post and download or open it
243 | let articleNode = postGetArticleNode(target);
244 | let url = await postGetUrl(target, articleNode);
245 |
246 | // ==============================
247 | // = download or open media url =
248 | // ==============================
249 | if (url.length > 0) {
250 | // check url
251 | if (target.getAttribute("class").includes("download-btn")) {
252 | let mediaName = url.split('?')[0].split('\\').pop().split('/').pop();
253 | let ext = mediaName.substr(mediaName.lastIndexOf('.') + 1);
254 | mediaName = mediaName.substring(0, mediaName.lastIndexOf('.'));
255 | let datetime = new Date(articleNode.querySelector('time').getAttribute('datetime'));
256 | datetime = yyyymmdd(datetime) + '_' + datetime.toTimeString().split(' ')[0].replace(/:/g, '');
257 | let posterName = articleNode.querySelector('header a').getAttribute('href').replace(/\//g, '');
258 | let filename = filenameFormat(postFilenameTemplate, posterName, datetime, mediaName, ext);
259 | downloadResource(url, filename);
260 | } else {
261 | // open url in new tab
262 | openResource(url);
263 | }
264 | }
265 | }
266 |
267 | function postGetArticleNode(target) {
268 | let articleNode = target;
269 | while (articleNode && articleNode.tagName !== "ARTICLE") {
270 | articleNode = articleNode.parentNode;
271 | }
272 | return articleNode;
273 | }
274 |
275 | async function postGetUrl(target, articleNode) {
276 | // meta[property="og:video"]
277 | let list = articleNode.querySelectorAll('li[style][class]');
278 | let url = "";
279 | if (list.length === 0) {
280 | // single img or video
281 | if (articleNode.querySelector('article div > video')) {
282 | let videoElem = articleNode.querySelector('article div > video');
283 | url = videoElem.getAttribute('src');
284 | if (videoElem.hasAttribute('videoURL')) {
285 | url = videoElem.getAttribute('videoURL');
286 | } else if (url === null || url.includes('blob')) {
287 | url = await fetchVideoURL(articleNode, videoElem);
288 | }
289 | } else if (articleNode.querySelector('article div[role] div > img')) {
290 | url = articleNode.querySelector('article div[role] div > img').getAttribute('src');
291 | } else {
292 | console.log("Err: not find media at handle post single");
293 | }
294 | } else {
295 | // multiple imgs or videos
296 | let idx = 0;
297 | // check current index
298 | if (!articleNode.querySelector('.coreSpriteLeftChevron')) {
299 | idx = 0;
300 | } else if (!articleNode.querySelector('.coreSpriteRightChevron')) {
301 | idx = list.length - 1;
302 | } else idx = 1;
303 |
304 | let node = list[idx];
305 | if (node.querySelector('video')) {
306 | let videoElem = node.querySelector('video');
307 | url = videoElem.getAttribute('src');
308 | if (videoElem.hasAttribute('videoURL')) {
309 | url = videoElem.getAttribute('videoURL');
310 | } else if (url === null || url.includes('blob')) {
311 | url = await fetchVideoURL(articleNode, videoElem);
312 | }
313 | } else if (node.querySelector('img')) {
314 | url = node.querySelector('img').getAttribute('src');
315 | }
316 | }
317 | return url
318 | }
319 |
320 | async function fetchVideoURL(articleNode, videoElem) {
321 | let poster = videoElem.getAttribute('poster');
322 | let timeNodes = articleNode.querySelectorAll('time');
323 | // special thanks 孙年忠 (https://greasyfork.org/en/scripts/406535-instagram-download-button/discussions/120159)
324 | let posterUrl = timeNodes[timeNodes.length - 1].parentNode.parentNode.href;
325 | let posterPattern = /\/([^\/?]*)\?/;
326 | let posterMatch = poster.match(posterPattern);
327 | let postFileName = posterMatch[1];
328 | // special thanks to 孙年忠 for the pattern (https://greasyfork.org/zh-TW/scripts/406535-instagram-download-button/discussions/116675)
329 | let pattern = new RegExp(`${postFileName}.*?video_versions.*?url":("[^"]*")`, 's');
330 | let resp = await fetch(posterUrl);
331 | let content = await resp.text();
332 | let match = content.match(pattern);
333 | let videoUrl = JSON.parse(match[1]);
334 | videoUrl = videoUrl.replace(/^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/?\n]+)/g, 'https://scontent.cdninstagram.com');
335 | videoElem.setAttribute('videoURL', videoUrl);
336 | return videoUrl;
337 | }
338 |
339 | function storyOnMouseIn(target) {
340 | let sectionNode = storyGetSectionNode(target);
341 | let url = storyGetUrl(target, sectionNode);
342 | target.setAttribute('href', url);
343 | }
344 |
345 | function storyOnClicked(target) {
346 | // extract url from target story and download or open it
347 | let sectionNode = storyGetSectionNode(target);
348 | let url = storyGetUrl(target, sectionNode);
349 |
350 | // ==============================
351 | // = download or open media url =
352 | // ==============================
353 | if (target.getAttribute("class").includes("download-btn")) {
354 | let mediaName = url.split('?')[0].split('\\').pop().split('/').pop();
355 | let ext = mediaName.substr(mediaName.lastIndexOf('.') + 1);
356 | mediaName = mediaName.substring(0, mediaName.lastIndexOf('.'));
357 | let datetime = new Date(sectionNode.querySelector('time').getAttribute('datetime'));
358 | datetime = yyyymmdd(datetime) + '_' + datetime.toTimeString().split(' ')[0].replace(/:/g, '');
359 | let posterName = sectionNode.querySelector('header a').getAttribute('href').replace(/\//g, '');
360 |
361 | let filename = filenameFormat(storyFilenameTemplate, posterName, datetime, mediaName, ext);
362 | downloadResource(url, filename);
363 | } else {
364 | // open url in new tab
365 | openResource(url);
366 | }
367 | }
368 |
369 | function storyGetSectionNode(target) {
370 | let sectionNode = target;
371 | while (sectionNode && sectionNode.tagName !== "SECTION") {
372 | sectionNode = sectionNode.parentNode;
373 | }
374 | return sectionNode;
375 | }
376 |
377 | function storyGetUrl(target, sectionNode) {
378 | let url = "";
379 | if (sectionNode.querySelector('video > source')) {
380 | url = sectionNode.querySelector('video > source').getAttribute('src');
381 | } else if (sectionNode.querySelector('img[decoding="sync"]')) {
382 | let img = sectionNode.querySelector('img[decoding="sync"]');
383 | url = img.srcset.split(/ \d+w/g)[0].trim(); // extract first src from srcset attr. of img
384 | if (url.length > 0) {
385 | return url;
386 | }
387 | url = sectionNode.querySelector('img[decoding="sync"]').getAttribute('src');
388 | }
389 | return url;
390 | }
391 |
392 | function filenameFormat(template, id, datetime, medianame, ext) {
393 | let filename = template;
394 | filename = filename.replaceAll("%id%", id);
395 | filename = filename.replaceAll("%datetime%", datetime);
396 | filename = filename.replaceAll("%medianame%", medianame);
397 | filename = filename.replaceAll("%ext%", ext);
398 | return filename;
399 | }
400 |
401 | function openResource(url) {
402 | // open url in new tab
403 | var a = document.createElement('a');
404 | a.href = url;
405 | a.setAttribute("target", "_blank");
406 | document.body.appendChild(a);
407 | a.click();
408 | a.remove();
409 | }
410 |
411 | function forceDownload(blob, filename) {
412 | // ref: https://stackoverflow.com/questions/49474775/chrome-65-blocks-cross-origin-a-download-client-side-workaround-to-force-down
413 | var a = document.createElement('a');
414 | a.download = filename;
415 | a.href = blob;
416 | // For Firefox https://stackoverflow.com/a/32226068
417 | document.body.appendChild(a);
418 | a.click();
419 | a.remove();
420 | }
421 |
422 | // Current blob size limit is around 500MB for browsers
423 | function downloadResource(url, filename) {
424 | // ref: https://stackoverflow.com/questions/49474775/chrome-65-blocks-cross-origin-a-download-client-side-workaround-to-force-down
425 | if (!filename) filename = url.split('\\').pop().split('/').pop();
426 | fetch(url, {
427 | headers: new Headers({
428 | 'Origin': location.origin
429 | }),
430 | mode: 'cors'
431 | })
432 | .then(response => response.blob())
433 | .then(blob => {
434 | let blobUrl = window.URL.createObjectURL(blob);
435 | forceDownload(blobUrl, filename);
436 | })
437 | .catch(e => console.error(e));
438 | }
439 | })();
440 |
--------------------------------------------------------------------------------
/VIP-001.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name 全网VIP视频自动解析蓝光播放器(已适配苹果,安卓,PC端)
3 | // @namespace https://www.tampermonkey.net/
4 | // @version 1.1.8.8
5 | // @license AGPL-3.0
6 | // @description 无需跳转新网址,打开官网直接看,蓝光 4k 无广告 随机去水印。支持:腾讯,爱奇艺,优酷,哔哩哔哩,咪咕,乐视,搜狐,芒果,西瓜,PPTV,1905电影网,华数。支持解析失败自动切换推荐解析源。适配各种浏览器。
7 | // @description 新增加视频嗅探,m3u8视频下载 视频下载转mp4(该功能目前只试配PC端)
8 | // @author Tenfond
9 | // @match *://v.qq.com/*
10 | // @match *://m.v.qq.com/*
11 | // @match *://www.iqiyi.com/*
12 | // @match *://m.iqiyi.com/*
13 | // @match *://*.youku.com/*
14 | // @match *://*.bilibili.com/*
15 | // @match *://*.miguvideo.com/*
16 | // @match *://*.le.com/*
17 | // @match *://tv.sohu.com/*
18 | // @match *://film.sohu.com/*
19 | // @match *://m.tv.sohu.com/*
20 | // @match *://*.mgtv.com/*
21 | // @match *://*.ixigua.com/*
22 | // @match *://*.pptv.com/*
23 | // @match *://vip.1905.com/*
24 | // @match *://www.wasu.cn/*
25 | // @include *://*.*/*url=http*://*.*.*/*
26 | // @include *://*.*:*/*url=http*://*.*.*/*
27 | // @include *://*.*/*v=http*://*.*.*/*
28 | // @include *://*.*:*/*v=http*://*.*.*/*
29 | // @match *://api.leduotv.com/*?vid=*
30 | // @grant GM_getValue
31 | // @grant GM_setValue
32 | // @run-at document-body
33 | // ==/UserScript==
34 | /**
35 | * 任务清单:
36 | */
37 |
38 | // 读取配置后执行
39 | function readSettings() {
40 | // 获取框架循环时间,CPU性能好的可以设置为100,CPU性能不好的可以设置为1000
41 | settings.getElementTimes = 500;
42 | // log输出字体布局
43 | settings.fontStyle = {
44 | error: "font-family: 微软雅黑,黑体,Droid Serif,Arial,sans-serif; color: #f00;",
45 | warn: "font-family: 微软雅黑,黑体,Droid Serif,Arial,sans-serif; color: #ff0;",
46 | ok: "font-family: 微软雅黑,黑体,Droid Serif,Arial,sans-serif; color: #0f0;",
47 | maxTip: "font-family: 微软雅黑,黑体,Droid Serif,Arial,sans-serif; font-size: 30px; background-color: #222; text-shadow: 0px 0px 12px #fff; color: #fff;"
48 | };
49 |
50 | if (window === top) {
51 | /*
52 | * (): 小括号括住的表示推荐解析 画质高 速度快
53 | * : 无括号的表示视频带水印 或 原页面画质
54 | * []: 方括号表示标清画质 不推荐
55 | */
56 | settings.NoAD解析 = { // TODO by 17kyun.com/api.php?url= // TODO by tv.hzwdd.cn
57 | // 你可以在这里定义自己的解析接口,脚本会自动适配。格式如下:
58 | // "解析名称": "解析接口的链接", // TODO 注意 : 和 " 以及 , 都是英文的符号。
59 | "PPJ蓝光解析": "https://bf.ppjbk.cn/?url=", // TODO 腾讯 (芒果)
60 | "PPJ解析": "https://jx.ppjbk.cn/?url=", // TODO 腾讯 (芒果) (B站)
61 | "PPJ弹幕解析2": "https://bf.ppjbk.cn/bf/?url=", // TODO 腾讯 (芒果)
62 | "OK解析": "https://api.okjx.cc:3389/jx.php?url=" || "https://okjx.cc/?url=" || "https://m2090.com/?url=", // TODO 优质: 腾讯 (爱奇艺) 优酷 乐视 芒果 PPTV (华数)
63 | "全民解析": "https://jx.quanmingjiexi.com/?url=" || "https://chaxun.truechat365.com/?url=", // TODO (腾讯)
64 | "云解析": "https://jx.aidouer.net/?url=" || "https://jx.ppflv.com/?url=", // TODO 腾讯 [爱奇艺] 优酷 (乐视) 芒果 (1905电影网) [华数]
65 | "天翼解析": "https://jsap.attakids.com/?url=" || "https://www.qianyicp.com/vip/vip_g.php?url=", // _4K解析: "", // TODO 腾讯 爱奇艺 (优酷) 咪咕 乐视 (芒果) 西瓜 PPTV
66 | "虾米解析": "https://jx.xmflv.com/?url=", // TODO (土豆) (咪咕) 搜狐 (芒果)
67 | "九八看解析": "https://jx.ap2p.cn/?url=", // (Parwix解析: "https://jx.parwix.com:4433/player/analysis.php?v=" || "https://vip.parwix.com:4433/player/?url=" || ) // TODO (西瓜) (B站) (搜狐) (芒果)
68 | "夜幕解析": "https://www.yemu.xyz/?url=", // TODO 土豆 华数 芒果:可能有广告 // by www.yunboys.cn
69 | "new1": "https://jx.xl6.top/cache.php?url=",
70 | "new2": "https://jx.xl6.top/fp/?url=",
71 | "new3": "https://jx.xl6.top/dp/?url=",
72 | "new4": "https://dm.xl6.top/?url=",
73 | "new5": "https://vip.parwix.com:4433/player/analysis.php?v=",
74 | "new6": "https://jx.fulitang365.com/?url=",
75 | "new7": "https://www.nxflv.com/?url=",
76 | "new8": "https://jsap.attakids.com/?url=",
77 | "new9": "https://jx.xiaoxiong.me/?url="
78 | };
79 | settings.AD解析 = { // TODO (有赌博广告,请勿相信,这么简单的骗术不会有人上当吧)
80 | "z1解析": "https://z1.m1907.cn?jx=",
81 | "乐多解析": "https://api.leduotv.com/wp-api/ifr.php?isDp=1&vid=", // TODO (B站)
82 | "Parwix解析": "https://jx.parwix.com:4433/player/?url=" // TODO 无水印(但不稳定): 腾讯 爱奇艺 优酷 乐视 [芒果] (PPTV) (华数)
83 | };
84 | start();
85 | } else if (settings.isParse) {
86 | top.postMessage(key.ENCRYPT("宝塔镇河妖\x00给予\x000\x00" + location.href, settings.k1, settings.k2), "*");
87 | Object.defineProperty(console, "clear", {
88 | value: () => console.log("%c禁止清除控制台", settings.fontStyle.error), writable: false
89 | });
90 | settings.parseDB = new Promise(resolve => {
91 | window.addEventListener("message", event => {
92 | if (event.source !== window) {
93 | try {
94 | let sql = key.DECRYPT(event.data, settings.k1, settings.k2).split("\x00");
95 | if (sql[0] === "天王盖地虎") {
96 | if (sql[1] === "给予") {
97 | if (sql[2] === "用户数据库") {
98 | resolve(JSON.parse(sql[3]));
99 | }
100 | }
101 | }
102 | } catch (e) {
103 | // 排除 下标越界错误 及 指令处理错误
104 | }
105 | }
106 | });
107 | });
108 | // 需要先监听再发送数据
109 | top.postMessage(key.ENCRYPT("宝塔镇河妖\x00请求\x00用户数据库", settings.k1, settings.k2), "*");
110 | start();
111 | }
112 | }
113 |
114 | // 未经许可禁止抄袭算法,可以私用。
115 | const key = (() => {
116 | // 新方案定义私有变量。修复部分浏览器不支持 # 定义private私有变量
117 | const password = Symbol();
118 | const randomkey = Symbol();
119 |
120 | // 自定义加密算法,以防数据包被破解。
121 | class key {
122 | // _pwd; // 火狐v68版本貌似不支持这种方式声明变量。
123 |
124 | constructor(pwd = null) {
125 | // num,密码偏移量
126 | // key,排列长度偏移量
127 | // charCode,防止内存频繁运动,定义在外部
128 | let num = 7, charCode, key = 7;
129 | if (pwd) {
130 | if (typeof pwd === "string") {
131 | for (let i = 0; i < pwd.length; i++) {
132 | // 将pwd中 每个字 转换成 十进制ASCII值
133 | charCode = pwd[i].charCodeAt();
134 | // 将每个 ASCII值 中 第一个值加入到 排列长度偏移量
135 | key += parseInt(charCode.toString()[0]);
136 | // 用 hash算法 将 ASCII值 减去 31 作为新的值加入到num中
137 | // 以下方法是为了防止出现负数,ASCII字符集一共有65536个字符
138 | // num += charCode - 31 => (charCode + 65505) % 65536
139 | num = 31 * num + (charCode + 65505) % 65536;
140 | }
141 | } else if (typeof pwd === "number") {
142 | // 如果密码是数值型就使用此方法作为 密码偏移量 和 排列长度偏移量
143 | num = key = 7 + parseInt(Math.abs(pwd));
144 | } else {
145 | // 如果类型不匹配就直接提出错误
146 | console.error("Unsupported type '" + (typeof pwd) + "'. It only supports 'string' or 'number'.")
147 | }
148 | }
149 | // 让排列长度偏移量的转换成8进制,取第一个数字。加上密码转换成字符字符串的方式。数值+字符串 会将数值自动转换为字符串
150 | this[password] = key.toString(8)[0] + (num % 65536).toString();
151 | }
152 |
153 | encrypt(string) {
154 | if (string) {
155 | // subStart 排列长度的起始位置。subIndex 排列长度。 encryptPool 加密池,即去除的排列长度存放在这里。result 加密后的结果。
156 | let subStart = string.length, subIndex = parseInt(this[password][0]) + 3, encryptPool = [], result = "";
157 | // stringKey 加密字符偏移量的散列值的的个数。
158 | let stringKey = subStart + parseInt(this[password].substring(1));
159 | // stringKeyFloat 加密字符偏移量的偏移单位。stringKeyIndex 加密字符偏移量的位置
160 | let stringKeyFloat = 65536 / stringKey, stringKeyIndex = stringKey - 1;
161 | // 获取加密池。
162 | while (subStart > subIndex) {
163 | subStart -= subIndex;
164 | encryptPool.push(string.substr(subStart, subIndex));
165 | }
166 | encryptPool.push(string.substring(0, subStart));
167 | // 对加密池进行加密,并将加密的字符的结果放入 result 中。
168 | for (let i = 0, j; i < subIndex; i++) {
169 | for (j = 0; j < encryptPool.length; j++) {
170 | let char = encryptPool[j][i];
171 | if (char) {
172 | // 将字符的 ASCII值 偏移 指定量
173 | result += String.fromCharCode((char.charCodeAt() + parseInt(stringKeyFloat * (stringKeyIndex % 2 === 0 ? stringKeyIndex : stringKey - stringKeyIndex))) % 65536);
174 | stringKeyIndex = stringKeyIndex === 0 ? stringKey - 1 : stringKeyIndex - 1;
175 | } else {
176 | break;
177 | }
178 | }
179 | }
180 | // 返回加密结果
181 | return result;
182 | } else {
183 | // 如果加密字符串不存在就返回空字符串
184 | return "";
185 | }
186 | }
187 |
188 | /* 假设有7个字符
189 |
190 | 加密前 - 排列
191 | ( 1 ) ( 2 3 ) ( 4 5 ) ( 6 7 )
192 |
193 | 加密中 - 排列
194 | ︵ ︵ ︵ ︵
195 | 6 4 2 1
196 | 7 5 3 ︶
197 | ︶ ︶ ︶
198 | 加密后 - 排列
199 | ( 6 4 2 1 ) ( 7 5 3 )
200 |
201 | 解密中 - 排列
202 | ︵ ︵
203 | 6 7
204 | 4 5
205 | 2 3
206 | 1 ︶
207 | ︶
208 | 解密后 - 排列
209 | 1 2 3 4 5 6 7 8
210 | */
211 |
212 | decrypt(string) {
213 | if (string) {
214 | // subStart 排列长度的起始位置。desubIndex 反向取加密池的长度。 decryptPool 解密池。
215 | let subStart = 0, desubIndex = Math.ceil(string.length / (parseInt(this[password][0]) + 3)),
216 | //NullCount 加密池中最后一个元素的元素空位。 desubIndex 反向取加密池的长度。 decryptPool 解密池,result 解密后的结果。
217 | NullCount = string.length % (parseInt(this[password][0]) + 3), decryptPool = [], result = "";
218 | // stringKey 加密字符偏移量的散列值的的个数。
219 | let stringKey = string.length + parseInt(this[password].substring(1)), // stringKeyFloat 加密字符偏移量的偏移单位。stringKeyIndex 加密字符偏移量的位置
220 | stringKeyFloat = 65536 / stringKey, stringKeyIndex;
221 | // 获取解密池
222 | while (string.length - subStart > desubIndex) {
223 | decryptPool.push(string.substr(subStart, desubIndex));
224 | subStart += desubIndex;
225 | if (decryptPool.length === NullCount) {
226 | desubIndex--;
227 | }
228 | }
229 | decryptPool.push(string.substring(subStart));
230 | // 对解密池进行解密 并将解密结果 加入到 result(结果) 中
231 | for (let j = decryptPool[0].length - 1, i; j > -1; j--) {
232 | // 为节省NullCount的空间利用,使用NullCount用来辅助计算 stringKeyIndex(加密字符偏移量的位置) 的位置
233 | // 画图排列就知道为什么要这样做了,上面做了一个加密到解密的排列的草图
234 | NullCount = 0;
235 | for (i = 0; i < decryptPool.length; i++) {
236 | let char = decryptPool[i][j];
237 | if (char) {
238 | NullCount += decryptPool[i].length;
239 | // 计算得到当前字符的位置
240 | stringKeyIndex = NullCount - decryptPool[i].length + j + 1;
241 | // 计算得到 stringKeyIndex(加密字符偏移量的位置) 的位置
242 | stringKeyIndex = (stringKey - stringKeyIndex % stringKey) % stringKey;
243 | // 让加密后的偏移量,偏移回去,得到原先的字符串
244 | result += String.fromCharCode((char.charCodeAt() - parseInt(stringKeyFloat * (stringKeyIndex % 2 === 0 ? stringKeyIndex : stringKey - stringKeyIndex)) + 65536) % 65536);
245 | }
246 | }
247 | }
248 | // 返回解密结果
249 | return result;
250 | } else {
251 | // 如果解密字符串不存在就返回空字符串
252 | return "";
253 | }
254 | }
255 |
256 | // encrypt 既可以用作加密 也可以用作解密,decrypt 既可以用作解密 也可以用作加密。
257 |
258 | static ENCRYPT() {
259 | if (arguments) {
260 | let strings = [], keys = [];
261 | for (let i = 0; i < arguments.length; i++) {
262 | // JavaScript如何用最简单的方法获取任意对象的类名?(包括自定义类) 判断对象类型?
263 | // 欢迎支持我的原创文档 https://blog.csdn.net/qq_37759464/article/details/121764755
264 | if (arguments[i].constructor.name === "key") {
265 | keys.push(arguments[i]);
266 | } else if (arguments[i].constructor.name === "String") {
267 | strings.push(arguments[i]);
268 | }
269 | }
270 | if (strings) {
271 | if (keys) {
272 | for (let i = 0, j; i < strings.length; i++) {
273 | for (j = 0; j < keys.length; j++) {
274 | strings[i] = i % 2 === 0 ? keys[j].encrypt(strings[i]) : keys[j].decrypt(strings[i]);
275 | }
276 | }
277 | return strings.length === 1 ? strings[0] : strings;
278 | } else {
279 | if (!this[randomkey]) {
280 | this[randomkey] = new key(parseInt(Math.random() * 1024) + 3);
281 | }
282 | // 为了使加密方法不一样,这里反过来
283 | strings[i] = this[randomkey].decrypt(this[randomkey].decrypt(strings[i]));
284 | return strings.length === 1 ? strings[0] : strings;
285 | }
286 | }
287 | }
288 | }
289 |
290 | static DECRYPT() {
291 | if (arguments) {
292 | let strings = [], keys = [];
293 | for (let i = 0; i < arguments.length; i++) {
294 | if (arguments[i].constructor.name === "key") {
295 | keys.push(arguments[i]);
296 | } else if (arguments[i].constructor.name === "String") {
297 | strings.push(arguments[i]);
298 | }
299 | }
300 | if (strings) {
301 | if (keys) {
302 | for (let i = 0, j; i < strings.length; i++) {
303 | for (j = keys.length - 1; j > -1; j--) {
304 | strings[i] = i % 2 === 0 ? keys[j].decrypt(strings[i]) : keys[j].encrypt(strings[i]);
305 | }
306 | }
307 | return strings.length === 1 ? strings[0] : strings;
308 | } else {
309 | if (!this[randomkey]) {
310 | this[randomkey] = new key(parseInt(Math.random() * 1024) + 3);
311 | }
312 | strings[i] = this[randomkey].encrypt(this[randomkey].encrypt(strings[i]));
313 | return strings.length === 1 ? strings[0] : strings;
314 | }
315 | }
316 | }
317 | }
318 | }
319 |
320 | return key;
321 | })();
322 |
323 | // 有人反馈苹果端不能看,尝试修改UA解决
324 | if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/iPod/i)) {
325 | // 修改手机端UA,似乎改完这个UA还是没有效果。说明苹果端页面从数据请求就开始检测UA了。请手动修改浏览器UA。
326 | Object.defineProperty(navigator, 'userAgent', {
327 | // 这个UA会屏蔽百度搜索的广告?
328 | value: "Mozilla/5.0 (Linux; Android 8.0; MI 6 Build/OPR1.170623.027; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/48.0.2564.116 Mobile Safari/537.36 T7/10.3 SearchCraft/2.6.3 (Baidu; P1 8.0.0)",
329 | writable: false
330 | });
331 | }
332 |
333 | let settings = {};
334 | const root = document.querySelector(":root");
335 | // 核心驱动代码
336 | (() => {
337 | if (location.search.match(/[?&]url=https?:\/\/.+\..+\..+\/.+/i) ||
338 | location.search.match(/[?&]v=https?:\/\/.+\..+\..+\/.+/i) ||
339 | location.href.match(/https?:\/\/api\.leduotv\.com\/.+?vid=/i)) {
340 | settings.isParse = true;
341 | }
342 | if (settings.isParse || location.host.indexOf("v.qq.com") !== -1 || location.host.indexOf("iqiyi.com") !== -1 || location.host.indexOf("youku.com") !== -1 || location.host.indexOf("bilibili.com") !== -1 || location.host.indexOf("miguvideo.com") !== -1 || location.host.indexOf("le.com") !== -1 || location.host.indexOf("tv.sohu.com") !== -1 || location.host.indexOf("film.sohu.com") !== -1 || location.host.indexOf("mgtv.com") !== -1 || location.host.indexOf("ixigua.com") !== -1 || location.host.indexOf("pptv.com") !== -1 || location.host.indexOf("vip.1905.com") !== -1 || location.host.indexOf("www.wasu.com") !== -1) {
343 | // 对符合条件的域名执行脚本
344 | // 调用自写加密算法,生成实例类
345 | settings.k1 = new key("Tenfond");
346 | settings.k2 = new key(" 腾 风 ");
347 | if (window === top) {
348 | settings.parseDB = {
349 | 解析开关: "\x01", 弹幕开关: "\x01", DIY解析栏: "\x01"
350 | };
351 | for (let name in settings.parseDB) {
352 | let data = localStorage.getItem("解析数据库." + name);
353 | if (data !== null) {
354 | settings.parseDB[name] = data;
355 | }
356 | }
357 | // 油猴可以实现跨域存储
358 | try {
359 | let data = GM_getValue("弹幕开关");
360 | if (data !== null) settings.parseDB.弹幕开关 = data;
361 | } catch (e) {
362 | // 排除无效函数异常
363 | }
364 |
365 | if (settings.parseDB.解析开关) {
366 | if (!sessionStorage.getItem("tip设置")) {
367 | showTip("右下角可以编辑 设置");
368 | sessionStorage.setItem("tip设置", "\x01");
369 | }
370 | readSettings();
371 | }
372 | settings.parseDBFuntions = {
373 | 解析开关: () => {
374 | if (!settings.parseDB.解析开关) {
375 | location.reload();
376 | // window 刷新时会自动清除缓存
377 | } else {
378 | readSettings();
379 | settings.parseDB.解析开关 = "\x01";
380 | }
381 | },
382 | 弹幕开关: () => {
383 | try {
384 | GM_setValue("弹幕开关", settings.parseDB.弹幕开关)
385 | } catch (error) {
386 | // 排除无效函数异常
387 | }
388 | showTip("刷新页面即可生效");
389 | },
390 | DIY解析栏: () => {
391 | if (typeof settings.DIY_iframeFunction === "function") {
392 | settings.DIY_iframeFunction();
393 | } else {
394 | showTip("设置已生效");
395 | }
396 | }
397 | };
398 | let toolsBar = document.createElement("toolsbar");
399 | toolsBar.style = "display: block !important; visibility: visible !important; position: fixed; z-index: 2147483647 !important; left:0; bottom: 0; width: 100%; height: 0; font-family: 微软雅黑,黑体,Droid Serif,Arial,sans-serif; font-size: 15px; color: #000;";
400 | toolsBar.innerHTML = "\n" + "" +
410 | " \n" +
413 | " \n" +
415 | "";
416 | let SettingsBlock = toolsBar.querySelector("settings>button+ul");
417 | let parseDBKeys = Object.keys(settings.parseDB);
418 | for (let i = 0; i < parseDBKeys.length; i++) {
419 | SettingsBlock.innerHTML += "" + parseDBKeys[i] + "\n";
420 | }
421 | let SettingBlockSwitchs = SettingsBlock.querySelectorAll("li>label.parse-switch");
422 | for (let i = 0; i < SettingBlockSwitchs.length; i++) {
423 | let checkBox = SettingBlockSwitchs[i].querySelector("input[type=checkbox]");
424 | checkBox.checked = Boolean(settings.parseDB[parseDBKeys[i]]);
425 | SettingBlockSwitchs[i].querySelector("bg").addEventListener("transitionend", () => {
426 | if (checkBox.checked !== Boolean(settings.parseDB[parseDBKeys[i]])) {
427 | // 如果有变化才会执行,否则会重复执行,因为动画会有延迟,刚打开网页时也会触发此监听事件
428 | if (checkBox.checked) {
429 | settings.parseDB[parseDBKeys[i]] = "\x01";
430 | } else {
431 | settings.parseDB[parseDBKeys[i]] = "";
432 | }
433 | localStorage.setItem("解析数据库." + parseDBKeys[i], settings.parseDB[parseDBKeys[i]]);
434 | settings.parseDBFuntions[parseDBKeys[i]]();
435 | }
436 | }, false);
437 | }
438 | let SettingsBtn = toolsBar.querySelector("settings>button");
439 | SettingsBtn.onclick = () => {
440 | if (SettingsBlock.style.opacity === "0") {
441 | SettingsBtn.innerText = "关闭";
442 | SettingsBlock.style.opacity = "1";
443 | SettingsBlock.style.width = "200px";
444 | } else {
445 | SettingsBtn.innerText = "设置";
446 | SettingsBlock.style.opacity = "0";
447 | SettingsBlock.style.width = "0";
448 | }
449 | }
450 | root.append(toolsBar);
451 | } else {
452 | readSettings();
453 | }
454 | }
455 | })();
456 |
457 | // 启动解析代码
458 | function start() {
459 | console.log("脚本运行在 " + location.href);
460 |
461 | function detectMobile() {
462 | return (navigator.userAgent.match(/Android/i) ||
463 | navigator.userAgent.match(/webOS/i) ||
464 | navigator.userAgent.match(/Windows Phone/i) ||
465 | navigator.userAgent.match(/Symbian/i) ||
466 | navigator.userAgent.match(/BlackBerry/i) ||
467 | navigator.userAgent.match(/hpwOS/i));
468 | }
469 |
470 | function doElement(cssString, doFunction, waitMS = 0) {
471 | let Element = document.querySelector(cssString);
472 | if (Element && Element.nodeType === 1) {
473 | doFunction(Element);
474 | console.log("%c已为 " + cssString + " 进行了操作", settings.fontStyle.ok);
475 | } else if (document.readyState !== "complete" || waitMS > 0) {
476 | console.log("正在查找 " + cssString); // TODO 10毫秒约函数执行时间
477 | setTimeout(() => doElement(cssString, doFunction, document.readyState !== "complete" ? waitMS : waitMS - 10 - settings.getElementTimes), settings.getElementTimes);
478 | } else {
479 | console.log("%c未找到 " + cssString, settings.fontStyle.error);
480 | }
481 | }
482 |
483 | function doElements(cssString, doFunction, waitMS = 0, index = 0) {
484 | let Elements = document.querySelectorAll(cssString);
485 | if (Elements[index] && Elements[index].nodeType === 1) {
486 | doFunction(Elements);
487 | console.log("%c已为 All[" + index + "] " + cssString + " 进行了操作", settings.fontStyle.ok);
488 | } else if (document.readyState !== "complete" || waitMS > 0) {
489 | console.log("正在查找 All[" + index + "] " + cssString); // TODO 10毫秒约函数执行时间
490 | setTimeout(() => doElements(cssString, doFunction, document.readyState !== "complete" ? waitMS : waitMS - 10 - settings.getElementTimes, index), settings.getElementTimes);
491 | } else {
492 | console.log("%c未找到 All[" + index + "] " + cssString, settings.fontStyle.error);
493 | }
494 | }
495 |
496 | function forElements(cssString, doFunction, waitMS = 0, failFunction = null) {
497 | let forElementInterval = setInterval(() => {
498 | if (document.readyState !== "complete" || waitMS > 0) {
499 | let Elements = document.querySelectorAll(cssString);
500 | if (Elements && Elements.length > 0 && Elements[0].nodeType === 1) {
501 | doFunction(Elements, forElementInterval);
502 | console.log("%cforElements已为 " + cssString + " 进行了操作", settings.fontStyle.ok);
503 | }
504 | if (document.readyState === "complete") {
505 | waitMS = waitMS - 10 - settings.getElementTimes;
506 | }
507 | } else {
508 | if (failFunction) {
509 | failFunction();
510 | }
511 | console.log("已清除 forElements Interval计时器")
512 | clearInterval(forElementInterval);
513 | }
514 | }, settings.getElementTimes);
515 | }
516 |
517 | function removeElements(ElementsStrings) {
518 | console.log("正在检测并移除 " + ElementsStrings);
519 | let removeElementsInterval = setInterval(() => {
520 | if (ElementsStrings.length > 0) {
521 | for (let i in ElementsStrings) {
522 | try {
523 | let Elements = eval(ElementsStrings[i]);
524 | console.log(Elements);
525 | if (Elements && Elements.nodeType === 1) {
526 | console.log("%cremoveElemets 执行了移除 " + ElementsStrings[i], settings.fontStyle.ok);
527 | Elements.parentNode.removeChild(Elements);
528 | ElementsStrings.splice(i, 1);
529 | } else if (Elements[0] && Elements[0].nodeType === 1) {
530 | console.log("%cremoveElemets 执行了移除 " + ElementsStrings[i], settings.fontStyle.ok);
531 | for (let Element of Elements) {
532 | Element.parentNode.removeChild(Element);
533 | }
534 | ElementsStrings.splice(i, 1);
535 | }
536 | } catch (e) {
537 | // 排除 null值未找到方法 错误
538 | }
539 | }
540 | if (document.readyState === "complete") {
541 | console.log("%cremoveElemets 移除失败 " + ElementsStrings, settings.fontStyle.error);
542 | clearInterval(removeElementsInterval);
543 | }
544 | } else {
545 | clearInterval(removeElementsInterval);
546 | console.log("Elements 移除完毕");
547 | }
548 | }, 200);
549 | }
550 |
551 | if (window === top) {
552 | // 自定义remove方法,暂时用不着了,新方案代替了
553 | // Array.prototype.remove = value => {
554 | // for (let i = 0; i < this.length; i++) {
555 | // if (this[i] === value) {
556 | // this.splice(i, 1);
557 | // return i;
558 | // }
559 | // }
560 | // return -1;
561 | // }
562 | top.addEventListener("message", event => {
563 | if (event.source !== window) {
564 | try {
565 | let sql = key.DECRYPT(event.data, settings.k1, settings.k2).split("\x00");
566 | if (sql[0] === "宝塔镇河妖") {
567 | if (sql[1] === "函数") {
568 | console.log("top执行了函数: " + sql[2]);
569 | eval(sql[2]);
570 | } else if (sql[1] === "请求") {
571 | if (sql[2] === "用户数据库") {
572 | event.source.postMessage(key.ENCRYPT("天王盖地虎\x00给予\x00用户数据库\x00" + JSON.stringify(settings.parseDB), settings.k1, settings.k2), "*");
573 | }
574 | } else if (sql[1] === "给予") {
575 | if (sql[2] === "0") {
576 | if (settings.address !== null) {
577 | settings.address = sql[3];
578 | }
579 | console.log("%c" + settings.address, settings.fontStyle.warn);
580 | } else if (sql[2] === "-1") {
581 | if (settings.address === sql[3]) {
582 | showTip("解析失败,正在更换解析源");
583 | settings.randomSeleceParse();
584 | }
585 | }
586 | } else if (sql[1] === "按下Enter获取焦点") {
587 | onkeydown = e => {
588 | if (e.key === 'Enter') {
589 | event.source.focus();
590 | }
591 | }
592 | }
593 | }
594 | } catch (e) {
595 | console.log("%c" + e, settings.fontStyle.error);
596 | }
597 | }
598 | });
599 |
600 | if (!detectMobile()) {
601 | if (location.host.indexOf("v.qq.com") !== -1) {
602 | readyPlayerBox("已进入腾讯视频", ["#mask_layer", ".mod_vip_popup", "#mask_layer"], [settings.NoAD解析["PPJ蓝光解析"], settings.NoAD解析["OK解析"], settings.NoAD解析["天翼解析"], settings.NoAD解析["夜幕解析"]], "div#mod_player", null);
603 | } else if (location.host.indexOf("iqiyi.com") !== -1) {
604 | readyPlayerBox("已进入爱奇艺", ["#playerPopup", "div[class^=qy-header-login-pop]"], [settings.NoAD解析["PPJ蓝光解析"], settings.NoAD解析["OK解析"]], "iqpdiv.iqp-player[data-player-hook$=er]", null);
605 | } else if (location.host.indexOf("youku.com") !== -1) {
606 | readyPlayerBox("已进入优酷视频", ["#iframaWrapper"], [settings.NoAD解析["PPJ蓝光解析"]], "div#player", null);
607 | } else if (location.host.indexOf("bilibili.com") !== -1) {
608 | doElements("div[role=tooltip]:not([class*=popover-])", loginTip => displayNone(["#" + loginTip[6].id]), 1000, 6);
609 | doElement("svg[aria-hidden=true]", () => readyPlayerBox("已进入哔哩哔哩", null, [settings.NoAD解析["夜幕解析"], settings.NoAD解析["PPJ蓝光解析"]], "div.bpx-player-video-area,div.mask-container", null)); // TODO || document.getElementById("bilibiliPlayer") || document.getElementById("live-player-ctnr")
610 | } else if (location.host.indexOf("miguvideo.com") !== -1) {
611 | readyPlayerBox("已进入咪咕视频", null, [], "section#mod-player", null);
612 | } else if (location.host.indexOf("le.com") !== -1) {
613 | readyPlayerBox("已进入乐视TV", null, [settings.NoAD解析["PPJ蓝光解析"]], "#le_playbox", null);
614 | } else if (location.host.match(/(tv|film).sohu.com/)) {
615 | readyPlayerBox("已进入搜狐视频", null, [settings.NoAD解析["夜幕解析"]], "#player,#sohuplayer,.player-view", null);
616 | } else if (location.host.indexOf("mgtv.com") !== -1) {
617 | readyPlayerBox("已进入芒果TV", null, [settings.NoAD解析["PPJ解析"], settings.NoAD解析["PPJ蓝光解析"], settings.NoAD解析["OK解析"], settings.NoAD解析["虾米解析"], settings.NoAD解析["夜幕解析"]], "#mgtv-player-wrap", null);
618 | } else if (location.host.indexOf("ixigua.com") !== -1) {
619 | readyPlayerBox("已进入西瓜视频", null, [settings.NoAD解析["夜幕解析"]], "div.teleplayPage__playerSection", null);
620 | } else if (location.host.indexOf("pptv.com") !== -1) {
621 | readyPlayerBox("已进入PPTV", null, [settings.NoAD解析["PPJ蓝光解析"]], "div.w-video", null);
622 | } else if (location.host.indexOf("vip.1905.com") !== -1) {
623 | readyPlayerBox("已进入1905电影网", null, [settings.NoAD解析["云解析"]], "div#playBox", null);
624 | } else if (location.host.indexOf("www.wasu.cn") !== -1) {
625 | readyPlayerBox("已进入华数TV", null, [settings.NoAD解析["PPJ蓝光解析"]], "div#pcplayer", null);
626 | }
627 | } else {
628 | if (location.host.indexOf("v.qq.com") !== -1) {
629 | readyPlayerBox("已进入腾讯视频", [".mod_vip_popup", "[class^=app_],[class^=app-],[class*=_app_],[class*=-app-],[class$=_app],[class$=-app]", "div[dt-eid=open_app_bottom]", "div.video_function.video_function_new", "a[open-app]", "section.mod_source", "section.mod_box.mod_sideslip_h.mod_multi_figures_h,section.mod_sideslip_privileges,section.mod_game_rec"], [settings.NoAD解析["PPJ蓝光解析"], settings.NoAD解析["OK解析"]], "div.mod_play:not([style*='display: none;']) section.mod_player", null, href => {
630 | let location = hrefToLocation(href);
631 | href = searchToJSON(location.search);
632 | if (href) {
633 | if (href["cid"]) {
634 | if (href["id"]) {
635 | return location.protocol + '//v.qq.com/detail/' + href["cid"][0] + '/' + href["cid"] + '.html';
636 | } else if (href["vid"]) {
637 | return location.protocol + '//v.qq.com/x/cover/' + href["cid"] + '/' + href["vid"] + '.html';
638 | } else {
639 | return location.protocol + '//v.qq.com/x/cover/' + href["cid"] + '.html';
640 | }
641 | } else if (href["vid"]) {
642 | return location.protocol + '//v.qq.com/x/page/' + href["vid"] + '.html';
643 | } else if (href["lid"]) {
644 | return location.protocol + '//v.qq.com/detail/' + href["lid"][0] + '/' + href["lid"] + '.html';
645 | } else {
646 | return null;
647 | }
648 | } else {
649 | return null;
650 | }
651 | });
652 | } else if (location.host.indexOf("iqiyi.com") !== -1) {
653 | readyPlayerBox("已进入爱奇艺", ["div.m-iqyGuide-layer", "a[down-app-android-url]", "[name=m-extendBar]", "[class*=ChannelHomeBanner]", "section.m-hotWords-bottom"], [settings.NoAD解析["PPJ蓝光解析"], settings.NoAD解析["OK解析"]], "section.m-video-player", null);
654 | } else if (location.host.indexOf("youku.com") !== -1) {
655 | readyPlayerBox("已进入优酷视频", ["#iframaWrapper", ".ad-banner-wrapper", ".h5-detail-guide,.h5-detail-vip-guide", ".brief-btm"], [], "#player", null);
656 | } else if (location.host.indexOf("bilibili.com") !== -1) {
657 | readyPlayerBox("已进入哔哩哔哩", ["div.fe-ui-open-app-btn,div.recom-wrapper,open-app-btn", "[class*=openapp]"], [settings.NoAD解析["PPJ蓝光解析"], settings.NoAD解析["夜幕解析"]], "div#app.main-container div.player-wrapper", null, href => href.replace("m.bilibili.com", "www.bilibili.com"));
658 | } else if (location.host.indexOf("miguvideo.com") !== -1) {
659 | readyPlayerBox("已进入咪咕视频", ["[class^=app_],[class^=app-],[class*=_app_],[class*=-app-],[class$=_app],[class$=-app]", ".openClient", "div.group-item.programgroup .data-rate-01,div.group-item.programgroup .max-rate-01,div.group-item.programgroup .p-common"], [], "section#mod-player", null, href => href.replace("m.miguvideo.com", "www.miguvideo.com").replace("msite", "website"));
660 | } else if (location.host.indexOf("le.com") !== -1) {
661 | (block_show => {
662 | block_show.innerHTML = "div.layout{visibility: visible !important; display:block !important;}div.layout>*:not(style,script,#j-vote,#j-follow){visibility: visible !important; display: block !important;}";
663 | document.head.insertBefore(block_show, document.head.firstChild);
664 | })(document.createElement("style"));
665 | doElement("a.j-close-gdt", jump_over => {
666 | jump_over.click();
667 | return false;
668 | });
669 | readyPlayerBox("已进入乐视TV", ["a.leapp_btn", "div.full_gdt_bits[id^=full][data-url]", "[class*=Daoliu],[class*=daoliu],[class*=game]", "div.m-start", "[class*=icon_user]"], [settings.NoAD解析["云解析"]], "div.column.play", null);
670 | } else if (location.host.indexOf("m.tv.sohu.com") !== -1) {
671 | readyPlayerBox("已进入搜狐视频", ["div[class^=banner]", "div.js-oper-pos", "div[id^=ad],div[id^=ad] *", "[id*=login],[class*=login]", "[class$=-app]", "div.app-vbox.ph-vbox,div.app-vbox.app-guess-vbox", "div.twinfo_iconwrap", "div[class$=banner],div[id$=banner]"], [settings.NoAD解析["夜幕解析"]], "#player,#sohuplayer,.player-view", null, async href => {
672 | return new Promise(resolve => {
673 | xmlHttpRequest({
674 | url: href, success: data => {
675 | let result = data.responseText.match(/var videoData = \{[^\x00]+tvUrl:"(http.+)",[\r\n]/)[1];
676 | resolve(result);
677 | }, error: () => resolve(href)
678 | });
679 | });
680 | });
681 | } else if (location.host.indexOf("mgtv.com") !== -1) {
682 | readyPlayerBox("已进入芒果TV", ["div.adFixedContain,div.ad-banner,div.m-list-graphicxcy.fstp-mark", "div[class^=mg-app],div#comment-id.video-comment div.ft,div.bd.clearfix,div.v-follower-info", "div.ht.mgui-btn.mgui-btn-nowelt", "div.personal", "div[data-v-41c9a64e]"], [settings.NoAD解析["PPJ解析"], settings.NoAD解析["PPJ蓝光解析"], settings.NoAD解析["云解析"], settings.NoAD解析["虾米解析"], settings.NoAD解析["夜幕解析"]], "div.video-poster,div.video-area", null);
683 | } else if (location.host.indexOf("ixigua.com") !== -1) {
684 | readyPlayerBox("已进入西瓜视频", ["div.xigua-download", "div.xigua-guide-button", "div.c-long-video-recommend.c-long-video-recommend-unfold"], [settings.NoAD解析["夜幕解析"]], "div.xigua-detailvideo-video", null);
685 | } else if (location.host.indexOf("pptv.com") !== -1) {
686 | readyPlayerBox("已进入PPTV", ["[data-darkreader-inline-bgimage][data-darkreader-inline-bgcolor]", "div[class^=pp-m-diversion]", "section#ppmob-detail-picswiper", "section.layout.layout_ads", "div.foot_app", "div[modulename=导流位]", "a[class*=user]", "div.mod_video_info div.video_func"], [settings.NoAD解析["PPJ蓝光解析"]], "section.pp-details-video", null, href => href.replace("m.pptv.com", "v.pptv.com"));
687 | } else if (location.host.indexOf("vip.1905.com") !== -1) {
688 | (movie_info => {
689 | movie_info.innerHTML = "section#movie_info{padding-top: 20px !important;}";
690 | document.head.appendChild(movie_info);
691 | })(document.createElement("style"));
692 | readyPlayerBox("已进入1905电影网", ["a.new_downLoad[target=_blank]", "iframe[srcdoc^='
" + "" + "";
811 | let DIY_iframe_text = DIY_iframe.querySelector("input[type=text]");
812 |
813 | async function DIY_iframe_src() {
814 | let newiframe = document.querySelector("iframe[id*=player]");
815 | if (newiframe) {
816 | iframe = newiframe;
817 | }
818 | settings.src.set(DIY_iframe_select.options[DIY_iframe_select.selectedIndex].value, DIY_iframe_text.value ? DIY_iframe_text.value : location.href);
819 | // 预先设置历史解析源(用于适配不支持iframe执行脚本的浏览器)
820 | localStorage.setItem('historyParse', settings.src.get());
821 | }
822 |
823 | let DIY_iframe_select = DIY_iframe.querySelector("select");
824 | for (let name in settings.NoAD解析) {
825 | DIY_iframe_select.innerHTML += "";
826 | }
827 | for (let name in settings.AD解析) {
828 | DIY_iframe_select.innerHTML += "";
829 | }
830 | DIY_iframe.querySelector("option[value='" + settings.src.get() + "']").selected = true;
831 | settings.randomSeleceParse = async () => {
832 | // arguments 代表输入的所有参数,看不懂可以百度搜索 “js 参数 arguments”
833 | // return arguments ? arguments[Math.floor(Math.random() * arguments.length)] : null;
834 | if (srcs.length > 0) {
835 | let random = Math.floor(Math.random() * srcs.length);
836 | settings.src.set(srcs[random]);
837 | srcs.splice(random, 1);
838 | DIY_iframe.querySelector("option[value='" + settings.src.get() + "']").selected = true;
839 | } else {
840 | if (tested) {
841 | showTip("该视频可能无法解析\n请尝试使用z1解析\n如有疑问请反馈");
842 | return false;
843 | } else {
844 | showTip("解析失败,正在尝试其他解析源");
845 | srcs = others;
846 | others = null;
847 | tested = true;
848 | let random = Math.floor(Math.random() * srcs.length);
849 | settings.src.set(srcs[random]);
850 | srcs.splice(random, 1);
851 | }
852 | }
853 | settings.address = {"0": 0, "-1": 1};
854 | }
855 | DIY_iframe.querySelector("button").onclick = () => DIY_iframe_src();
856 | DIY_iframe_text.onkeydown = DIY_iframe_select.onkeydown = event => {
857 | if (event.key === "Enter") {
858 | DIY_iframe_src();
859 | }
860 | }
861 | doElement("toolsbar", toolsBar => {
862 | toolsBar.appendChild(DIY_iframe);
863 | });
864 | } else if (DIY_iframe.style.visibility === "hidden") {
865 | DIY_iframe.style.display = "flex";
866 | DIY_iframe.style.visibility = "";
867 | }
868 | } else if (DIY_iframe && DIY_iframe.constructor.name === "HTMLDivElement" && DIY_iframe.style.visibility === "") {
869 | DIY_iframe.style.display = "none";
870 | DIY_iframe.style.visibility = "hidden";
871 | }
872 | }
873 | settings.DIY_iframeFunction();
874 |
875 | (waiter => {
876 | waiter /= settings.getElementTimes;
877 | let resetPlayerBoxInterval = setInterval(() => {
878 | let newPlayerBox = document.querySelector(cssString);
879 | if (newPlayerBox !== playerBox && newPlayerBox !== null || newPlayerBox !== null && newPlayerBox.querySelector("iframe[src='" + iframe.src + "']") === null) {
880 | console.log("playerBox重新建立连接");
881 | let src = iframe.src;
882 | iframe.src = "";
883 | iframe = iframe.cloneNode(true);
884 | iframe.src = src;
885 | newPlayerBox.style.zIndex = "1";
886 | newPlayerBox.appendChild(iframe);
887 | clearInterval(resetPlayerBoxInterval);
888 | } else if (document.readyState === "complete" && waiter-- <= 0) {
889 | clearInterval(resetPlayerBoxInterval);
890 | }
891 | }, settings.getElementTimes)
892 | })(0);
893 |
894 | if (doFunction) {
895 | doFunction(playerBox, iframe);
896 | }
897 | setInterval(() => {
898 | for (let video of document.getElementsByTagName("video")) {
899 | if (video.src) {
900 | video.removeAttribute("src");
901 | video.load();
902 | video.muted = true;
903 | }
904 | }
905 | }, settings.getElementTimes);
906 |
907 | ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange'].forEach(item => {
908 | window.addEventListener(item, () => {
909 | let toolsBar = document.querySelector("toolsbar");
910 | if (document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen) {
911 | toolsBar.style.display = 'none';
912 | } else {
913 | toolsBar.style.display = 'block';
914 | }
915 | }, true);
916 | });
917 | });
918 | }
919 |
920 | function hrefToLocation(href) {
921 | let location = {href: href}, c = 0, start = 0, search;
922 | for (let i = 0, port; i < href.length; i++) {
923 | if (href[i] === "/") {
924 | if (++c === 1) {
925 | location.protocol = href.substring(start, i);
926 | } else if (c === 3) {
927 | location.host = href.substring(start + 1, i);
928 | if (port) {
929 | location.port = href.substring(port + 1, i);
930 | } else {
931 | location.hostname = location.host;
932 | location.port = "";
933 | }
934 | }
935 | if (c <= 3) {
936 | start = i;
937 | }
938 | } else if (href[i] === ":" && c === 2) {
939 | location.hostname = href.substring(start + 1, i);
940 | port = i;
941 | } else if (href[i] === "?" && !search) {
942 | location.pathname = href.substring(start, i);
943 | search = i;
944 | } else if (href[i] === "#" && !location.hash) {
945 | location.hash = href.substr(i);
946 | if (location.pathname === undefined) {
947 | location.pathname = href.substring(c, i);
948 | } else if (search) {
949 | location.search = href.substring(search, i);
950 | }
951 | break;
952 | }
953 | }
954 | if (location.pathname === undefined) {
955 | location.pathname = c === 3 ? href.substr(start) : "";
956 | location.search = location.hash = "";
957 | }
958 | if (location.search === undefined) {
959 | if (search) {
960 | location.search = href.substr(search);
961 | } else {
962 | location.search = "";
963 | }
964 | }
965 | if (location.hash === undefined) {
966 | location.hash = "";
967 | }
968 | return location;
969 | }
970 | } else {
971 | function setParseVideo() {
972 | forElements("video", async (videos, thisInterval) => {
973 | for (const video of videos) {
974 | if (video.poster) video.removeAttribute("poster");
975 | if (video.src) {
976 | // 解析成功,清空解析列表缓存
977 | top.postMessage(key.ENCRYPT("宝塔镇河妖\x00函数\x00settings.address = null", settings.k1, settings.k2), "*");
978 | // 设置历史解析
979 | top.postMessage(key.ENCRYPT("宝塔镇河妖\x00函数\x00localStorage.setItem('historyParse', settings.src.get())", settings.k1, settings.k2), "*");
980 |
981 | // 移除广告模块
982 | removeElements(['document.getElementById("ADplayer")', 'document.getElementById("ADtip")']);
983 | // 等待数据得到响应,移除弹幕模块
984 | await settings.parseDB;
985 | if (!settings.parseDB.弹幕开关) {
986 | console.log("正在移除弹幕功能");
987 | removeElements(['document.querySelector("div[class$=player-video-wrap]").getElementsByTagName("div")', 'document.querySelector("div[class$=player-danmu]")', 'document.querySelector("div[class$=player-danmaku]")', 'document.querySelector("div[class*=player-comment-box]")', 'document.querySelector("div[class*=player-controller-mask]")', 'document.querySelector("[class*=player-list-icon]")', 'document.querySelector("div[class$=player-menu]")']);
988 | }
989 |
990 | // 判断是否移动端
991 | if (!detectMobile()) {
992 | let fullscreen_btn = document.querySelector("[class*=fullscreen],[class$=player-full] button[class$=full-icon]");
993 | if (fullscreen_btn && fullscreen_btn.nodeType === 1) {
994 | // 阻止事件冒泡
995 | fullscreen_btn.parentNode.parentNode.onkeyup = event => event.stopPropagation();
996 | }
997 | video.onkeyup = event => event.stopPropagation();
998 | top.postMessage(key.ENCRYPT("宝塔镇河妖\x00按下Enter获取焦点", settings.k1, settings.k2), "*")
999 | onkeyup = video.onkeyup = event => {
1000 | if (event.key === "Enter") {
1001 | if (video.paused) {
1002 | video.play();
1003 | }
1004 | if (fullscreen_btn && fullscreen_btn.nodeType === 1) {
1005 | fullscreen_btn.click();
1006 | return false;
1007 | } else if (video.webkitDisplayingFullscreen) {
1008 | if (video.webkitExitFullScreen) {
1009 | video.webkitExitFullScreen();
1010 | } else if (video.webkitExitFullscreen) {
1011 | video.webkitExitFullscreen();
1012 | }
1013 | } else {
1014 | if (video.webkitEnterFullScreen) {
1015 | video.webkitEnterFullScreen();
1016 | } else if (video.webkitEnterFullscreen) {
1017 | video.webkitEnterFullscreen();
1018 | }
1019 | }
1020 | }
1021 | }
1022 | focus();
1023 | showTip("按下Enter回车键,进入全屏 并 自动播放");
1024 | video.addEventListener("pause", () => {
1025 | if ((video.currentTime - video.duration) > -5) {
1026 | console.log("视频播放结束了");
1027 | if (fullscreen_btn && fullscreen_btn.nodeType === 1 && ((video.clientWidth || video.scrollWidth) === screen.width) || ((video.clientHeight || video.scrollHeight) === screen.height)) {
1028 | fullscreen_btn.click();
1029 | return false;
1030 | } else if (video.webkitExitFullScreen) {
1031 | video.webkitExitFullScreen();
1032 | } else if (video.webkitExitFullscreen) {
1033 | video.webkitExitFullscreen();
1034 | } else {
1035 | console.log("不支持退出全屏");
1036 | }
1037 | }
1038 | }, false);
1039 | } else {
1040 | // showTip("解析成功");
1041 | }
1042 |
1043 | // 清除监听video计时器
1044 | clearInterval(thisInterval);
1045 | }
1046 | }
1047 | }, 3000, function () {
1048 | top.postMessage(key.ENCRYPT("宝塔镇河妖\x00给予\x00-1\x00" + location.href, settings.k1, settings.k2), "*");
1049 | });
1050 | }
1051 |
1052 | if (location.host.indexOf("jiexi.t7g.cn") !== -1) {
1053 | // 移除PPJ解析p2p提示
1054 | displayNone(["body>div#stats"]);
1055 | setParseVideo();
1056 | } else if (location.host.indexOf("api.okjx.cc:3389") !== -1) {
1057 | // 删除OK解析线路选择功能
1058 | (style => {
1059 | style.innerHTML = ".slide,.panel,.slide *,.panel *{width: 0 !important; max-width: 0 !important; opacity: 0 !important;}";
1060 | document.head.appendChild(style);
1061 | })(document.createElement("style"));
1062 | setParseVideo();
1063 | } else if (location.host.indexOf("api.jiubojx.com") !== -1) {
1064 | displayNone("div.adv_wrap_hh");
1065 | setParseVideo();
1066 | } else if (location.host.indexOf("yemu.xyz") !== -1) {
1067 | if (location.pathname.indexOf("jx.php") === -1) {
1068 | if (location.host.indexOf("www.yemu.xyz") !== -1) {
1069 | // 删除夜幕解析线路选择功能
1070 | (style => {
1071 | style.innerHTML = ".slide,.panel,.slide *,.panel *{width: 0 !important; max-width: 0 !important; opacity: 0 !important;}";
1072 | document.head.appendChild(style);
1073 | })(document.createElement("style"));
1074 | } else if (location.host.indexOf("jx.yemu.xyz") !== -1) {
1075 | // 移除视频分类提示 及 解析框架处理
1076 | displayNone(["div.advisory"]);
1077 | setParseVideo();
1078 | }
1079 | } else {
1080 | // 移除背景图片
1081 | doElement("div[style*='width:100%;height:100%;'][style*='.jpg']", background => {
1082 | background.style = "width:100%;height:100%;position:relative;z-index:2147483647987;";
1083 | }, 5000);
1084 | }
1085 | } else if (location.host.indexOf('www.mtosz.com') !== -1) {
1086 | displayNone([".video-panel-blur-image"]); // 似乎不管用?
1087 | doElement(".video-panel-blur-image", element => {
1088 | element.style = "display: none; height: 0; width: 0;";
1089 | });
1090 | setParseVideo();
1091 | } else if (location.host.indexOf('v.superchen.top:3389') !== -1) {
1092 | setParseVideo();
1093 | } else if (location.host.indexOf('jx.parwix.com:4433') !== -1) {
1094 | setParseVideo();
1095 | } else if (location.pathname) {
1096 | setParseVideo();
1097 | }
1098 | }
1099 |
1100 |
1101 | function displayNone(Tags) {
1102 | let style = document.createElement("style");
1103 | style.innerHTML = "\n";
1104 | for (let i = 0; i < Tags.length; i++) {
1105 | style.innerHTML += Tags[i] + "{display: none !important; height: 0 !important; width: 0 !important; visibility: hidden !important; max-height: 0 !important; max-width: 0 !important; opacity: 0 !important;}\n";
1106 | }
1107 | document.head.insertBefore(style, document.head.firstChild);
1108 | }
1109 |
1110 | function onLocationChange(handler) {
1111 | let url = top.location.pathname;
1112 | let onLocationChangeInterval = setInterval(() => {
1113 | let href = top.location.pathname;
1114 | if (href.indexOf(url) === -1) {
1115 | handler();
1116 | clearInterval(onLocationChangeInterval);
1117 | } else {
1118 | url = href;
1119 | }
1120 | }, settings.getElementTimes);
1121 | }
1122 |
1123 | function onFirstLoad(doFunction) {
1124 | if (document.readyState === "complete") {
1125 | if (doFunction) {
1126 | doFunction();
1127 | }
1128 | } else {
1129 | setTimeout(() => {
1130 | onFirstLoad(doFunction);
1131 | }, settings.getElementTimes);
1132 | }
1133 | }
1134 |
1135 | function searchToJSON(search) {
1136 | if (search) {
1137 | return JSON.parse("{\"" + decodeURIComponent(search.substring(1)
1138 | .replace(/"/g, '\\"')
1139 | .replace(/&/g, '","')
1140 | .replace(/=/g, '":"')) + "\"}");
1141 | } else {
1142 | return null;
1143 | }
1144 | }
1145 | }
1146 |
1147 | function showTip(msg, style = "") {
1148 | // 该函数需要在top内运行,否则可能显示异常
1149 | if (window === top) {
1150 | let tip = document.querySelector(":root>tip");
1151 | if (tip && tip.nodeType === 1) {
1152 | // 防止中途新的showTip事件创建多个tip造成卡顿
1153 | root.removeChild(tip);
1154 | }
1155 | tip = document.createElement("tip");
1156 | // pointer-events: none; 禁用鼠标事件,input标签使用 disabled='disabled' 禁用input标签
1157 | tip.style = style + "pointer-events: none; opacity: 0; background-color: #222a; color: #fff; font-family: 微软雅黑,黑体,Droid Serif,Arial,sans-serif; font-size: 20px; text-align: center; padding: 6px; border-radius: 16px; position: fixed; transform: translate(-50%, -50%); left: 50%; bottom: 15%; z-index: 2147483647;";
1158 | tip.innerHTML = "\n" + msg;
1159 | let time = msg.replace(/\s/, "").length / 2; // TODO 2个字/秒
1160 | // cubic-bezier(起始点, 起始点偏移量, 结束点偏移量, 结束点),这里的 cubic-bezier函数 表示动画速度的变化规律
1161 | tip.style.animation = "showTip " + (time > 2 ? time : 2) + "s cubic-bezier(0," + ((time - 1) > 0 ? (time - 1) / time : 0) + "," + (1 - ((time - 1) > 0 ? (time - 1) / time : 0)) + ",1) 1 normal";
1162 | root.appendChild(tip);
1163 | setTimeout(() => {
1164 | try {
1165 | root.removeChild(tip);
1166 | } catch (e) {
1167 | // 排除root没有找到tip
1168 | }
1169 | }, time * 1000);
1170 | } else {
1171 | top.postMessage(key.ENCRYPT("宝塔镇河妖\x00函数\x00showTip('" + msg + "')", settings.k1, settings.k2), "*");
1172 | }
1173 | }
1174 |
1175 | function xmlHttpRequest(settings) {
1176 | let request = new XMLHttpRequest();
1177 | if (settings.success) {
1178 | request.onload = event => {
1179 | // 加载成功
1180 | settings.success(request, event);
1181 | }
1182 | }
1183 | if (settings.error) {
1184 | request.onerror = event => {
1185 | // 加载失败
1186 | settings.error(request, event);
1187 | }
1188 | }
1189 | if (settings.loadend) {
1190 | request.onloadend = event => {
1191 | // 加载结束
1192 | settings.loadend(request, event);
1193 | }
1194 | }
1195 | if (settings.timeout) {
1196 | request.ontimeout = event => {
1197 | // 加载超时
1198 | settings.timeout(request, event);
1199 | }
1200 | }
1201 | request.open(settings.method ? settings.method : "GET", settings.url ? settings.url : location.href, settings.async ? settings.async : true, settings.username ? settings.username : null, settings.password ? settings.password : null);
1202 | if (settings.headers) {
1203 | for (let header in settings.headers) {
1204 | request.setRequestHeader(header, settings[header]);
1205 | }
1206 | }
1207 | if (settings.dataType) {
1208 | request.responseType = settings.dataType;
1209 | }
1210 | if (settings.data) {
1211 | let data = "";
1212 | for (let key in settings.data) {
1213 | data += key + "=" + settings.data[key] + "&";
1214 | }
1215 | data = data.substr(0, data.length - 1);
1216 | request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
1217 | request.send(data);
1218 | } else {
1219 | request.send();
1220 | }
1221 | return request;
1222 | }
1223 | ////m3u8视频下载
1224 | ////**********************************************
1225 | (function() {
1226 | 'use strict';
1227 | var m3u8Target = ''
1228 | var originXHR = window.XMLHttpRequest
1229 |
1230 | function ajax(options) {
1231 | options = options || {};
1232 | let xhr = new originXHR();
1233 | if (options.type === 'file') {
1234 | xhr.responseType = 'arraybuffer';
1235 | }
1236 |
1237 | xhr.onreadystatechange = function() {
1238 | if (xhr.readyState === 4) {
1239 | let status = xhr.status;
1240 | if (status >= 200 && status < 300) {
1241 | options.success && options.success(xhr.response);
1242 | } else {
1243 | options.fail && options.fail(status);
1244 | }
1245 | }
1246 | };
1247 |
1248 | xhr.open("GET", options.url, true);
1249 | xhr.send(null);
1250 | }
1251 |
1252 | // 检测 m3u8 链接的有效性
1253 | function checkM3u8Url(url) {
1254 | ajax({
1255 | url,
1256 | success: (fileStr) => {
1257 | if (fileStr.indexOf('.ts') > -1) {
1258 | appendDom()
1259 | m3u8Target = url
1260 | console.log('【m3u8】----------------------------------------')
1261 | console.log(url)
1262 | console.log('http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html?source=' + url)
1263 | }
1264 | }
1265 | })
1266 | }
1267 |
1268 | function resetAjax() {
1269 | if (window._hadResetAjax) { // 如果已经重置过,则不再进入。解决开发时局部刷新导致重新加载问题
1270 | return
1271 | }
1272 | window._hadResetAjax = true
1273 |
1274 | var originOpen = originXHR.prototype.open
1275 | window.XMLHttpRequest = function() {
1276 | var realXHR = new originXHR()
1277 | realXHR.open = function(method, url) {
1278 | url.indexOf('.m3u8') > 0 && checkM3u8Url(url)
1279 | originOpen.call(realXHR, method, url)
1280 | }
1281 | return realXHR
1282 | }
1283 | }
1284 |
1285 | function appendDom() {
1286 | if (document.getElementById('m3u8-download-dom')) {
1287 | return
1288 | }
1289 | var domStr = `
1290 | 跳转下载
1300 | 注入下载
1310 |
1319 |

1324 |
1325 | `
1326 | var $section = document.createElement('section')
1327 | $section.id = 'm3u8-download-dom'
1328 | $section.style.position = 'fixed'
1329 | $section.style.zIndex = '9999'
1330 | $section.style.bottom = '20px'
1331 | $section.style.right = '20px'
1332 | $section.style.textAlign = 'center'
1333 | $section.innerHTML = domStr
1334 | document.body.appendChild($section);
1335 |
1336 | var m3u8Jump = document.getElementById('m3u8-jump')
1337 | var m3u8Close = document.getElementById('m3u8-close')
1338 | var m3u8Append = document.getElementById('m3u8-append')
1339 |
1340 | m3u8Close.addEventListener('click', function() {
1341 | $section.remove()
1342 | })
1343 |
1344 | m3u8Jump.addEventListener('click', function() {
1345 | window.open('http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html?source=' + m3u8Target)
1346 | })
1347 |
1348 | m3u8Append.addEventListener('click', function() {
1349 | var _hmt = _hmt || [];
1350 | (function() {
1351 | var hm = document.createElement("script");
1352 | hm.src = "https://hm.baidu.com/hm.js?1f12b0865d866ae1b93514870d93ce89";
1353 | var s = document.getElementsByTagName("script")[0];
1354 | s.parentNode.insertBefore(hm, s);
1355 | })();
1356 | ajax({
1357 | url: 'https://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html',
1358 | success: (fileStr) => {
1359 | let fileList = fileStr.split(`