MissAv m3u8 finder

try fun ~

  1. // ==UserScript==
  2. // @name MissAv m3u8 finder
  3. // @namespace https://greasyfork.org/zh-CN/scripts/502821-missav-m3u8-finder
  4. // @version 2024-08-08-2
  5. // @description try fun ~
  6. // @author Luuuucus
  7. // @match *://missav.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14.  
  15. async function fetchVideoList(url) {
  16. console.log('fetch url :' ,url)
  17. const response = await fetch(url);
  18. const text = await response.text();
  19. return text;
  20. }
  21.  
  22. async function fetchVideoPart(url){
  23. console.log('fetch item url :' ,url)
  24. const response = await fetch(url);
  25. const blob = await response.blob();
  26. return blob;
  27. }
  28.  
  29. async function mergeBlobs(mediaUrls) {
  30. const res = await Promise.all(mediaUrls.map(async (mediaUrl) => {
  31. return await fetchVideoPart(mediaUrl.url);
  32. })).then((blobs) => {
  33. const mergedBlob = new Blob(blobs, { type: 'video/mp4' });
  34. return mergedBlob;
  35. });
  36.  
  37. return res;
  38. }
  39.  
  40. async function mergeBatch(mediaUrls,target) {
  41. const blobs = [];
  42. let total = mediaUrls.length;
  43. for(var i=0;i<mediaUrls.length;i=i+100){
  44. var tmp = await Promise.all(mediaUrls.slice(i, i+100).map(async (mediaUrl) => {
  45. return fetch(mediaUrl.url).then(resp=>{return resp.blob()})
  46. })).then((b) => {
  47. const mergedBlob = new Blob(b, { type: 'video/mp4' });
  48. return mergedBlob;
  49. });
  50. //计算百分比
  51. var current = i+100
  52. if(i+100>total){
  53. current = total
  54. }
  55. target.parentElement.querySelector('.progress-download').style.width = (current/total)*100+'%'
  56. blobs.push(tmp)
  57. }
  58.  
  59. return new Blob(blobs, { type: 'video/mp4' });
  60. }
  61.  
  62.  
  63. function createUrlElement(fileInfo){
  64. // 创建外层div元素
  65. const flexDiv = document.createElement('div');
  66. flexDiv.className = 'flex';
  67. flexDiv.style.height='30px'
  68.  
  69. // 创建a元素并设置样式
  70. const link = document.createElement('a');
  71. link.style.color = 'lightgreen';
  72. link.style.fontWeight = 'bold';
  73. link.style.border = '2px solid lightgreen';
  74. link.textContent = fileInfo.display;
  75.  
  76. const textDiv = document.createElement('div');
  77. textDiv.className = 'flex flex-col justify-center';
  78.  
  79.  
  80. // 创建span元素作为text,并设置样式(HTML中没有text标签)
  81. const text = document.createElement('span');
  82. text.style.marginLeft = '10px';
  83. text.style.fontStyle = 'italic';
  84. text.style.alignSelf = 'baseline'; // 注意:alignSelf是Flexbox属性,应该设置在flex容器上
  85. text.style.borderBottom = '2px groove lightgrey';
  86. text.style.color='white'
  87. text.style.fontSize = '12px'
  88. text.style.height='100%'
  89. text.style.alignContent ='end'
  90. text.textContent = fileInfo.url; // 这里假设是文本内容
  91. // 创建外部进度条容器div元素
  92. var progressOuterDiv = document.createElement('div');
  93. progressOuterDiv.style.height = '15%';
  94. progressOuterDiv.style.width = '95%';
  95. progressOuterDiv.style.backgroundColor = 'white';
  96. progressOuterDiv.style.marginLeft = '10px';
  97.  
  98. // 创建内部进度条填充div元素
  99. var progressDiv = document.createElement('div');
  100. progressDiv.className='progress-download'
  101. progressDiv.style.backgroundColor = 'lightgreen';
  102. progressDiv.style.width = '0%';
  103. progressDiv.style.height = '100%';
  104.  
  105. // 将内部进度条填充div添加到外部进度条容器div
  106. progressOuterDiv.appendChild(progressDiv);
  107.  
  108. textDiv.appendChild(text);
  109. textDiv.appendChild(progressOuterDiv);
  110.  
  111. // 创建button元素并设置样式
  112. const button = document.createElement('button');
  113. button.style.marginLeft = '10px';
  114. button.style.backgroundColor = 'lightgray';
  115. button.style.color = 'black';
  116. button.textContent = '复制';
  117.  
  118. // 创建button元素并设置样式
  119. const downloadBtn = document.createElement('button');
  120. downloadBtn.style.marginLeft = '10px';
  121. downloadBtn.style.backgroundColor = 'lightgray';
  122. downloadBtn.style.color = 'black';
  123. downloadBtn.textContent = '下载';
  124. downloadBtn.setAttribute('data-ref', fileInfo.url)
  125. downloadBtn.setAttribute('data-prefix', fileInfo.prefix)
  126. downloadBtn.setAttribute('file-name','test.mp4')
  127.  
  128. downloadBtn.addEventListener('click',async function(event){
  129. var target = event.target
  130. target.disabled = true
  131. target.textContent = '下载中...'
  132. target.style.backgroundColor = 'lightgreen';
  133.  
  134. var url = target.getAttribute('data-ref')
  135. var urlPrefix = target.getAttribute('data-prefix')
  136.  
  137. const text = await fetchVideoList(url);
  138. const lines = text.split('\n');
  139. const mediaUrls = [];
  140. // 遍历每一行,解析媒体URL
  141. lines.forEach(line => {
  142. // 忽略空行和注释行
  143. if (line.trim() &&!line.startsWith('#')) {
  144. // 媒体URL通常在EXTINF标签之后
  145. let name = line.trim().split('\\.')[0]
  146. //提取name中的数字
  147. const index = parseInt(name.match(/\d+/)[0]);
  148.  
  149. const fileInfo = {
  150. index: index,
  151. url: urlPrefix+line.trim()
  152. }
  153. mediaUrls.push(fileInfo);
  154. }
  155. });
  156. console.log(mediaUrls)
  157. const blobs =[]
  158. //多线程遍历mediaUrls,获取每个视频的blob
  159. const blob = await mergeBatch(mediaUrls,target);
  160. //创建blob下载链接
  161. var blobUrl = URL.createObjectURL(blob);
  162. var a = document.createElement('a');
  163. a.href = blobUrl;
  164. a.download = fileInfo.filename;
  165. a.click();
  166. setTimeout(()=>{
  167. target.disabled = false
  168. target.textContent = '下载'
  169. target.style.backgroundColor = 'lightgray';
  170. },1000)
  171. })
  172.  
  173. button.addEventListener('click', function() {
  174. // 使用navigator.clipboard API复制文本
  175. navigator.clipboard.writeText(text.textContent).then(() => {
  176. // 复制成功的操作
  177. console.log('已复制文本:', text.textContent);
  178. alert('链接已复制到剪贴板');
  179. }).catch(err => {
  180. // 复制失败的操作
  181. console.error('复制文本时出错:', err);
  182. alert('复制失败,请稍后重试');
  183. });
  184. });
  185.  
  186.  
  187. // 将子元素添加到flexDiv中
  188. flexDiv.appendChild(link);
  189. flexDiv.appendChild(textDiv);
  190. flexDiv.appendChild(button);
  191. flexDiv.appendChild(downloadBtn);
  192. return flexDiv
  193. }
  194.  
  195. 'use strict';
  196. console.log("my first monkey script",this)
  197. window.addEventListener("load", () => {
  198. var tools = document.querySelector('.order-first .mt-4')
  199. const flexDiv = document.createElement('div');
  200. const filename = document.querySelector('.order-first .mt-4 h1').textContent.trim()
  201.  
  202. // 添加类名
  203. flexDiv.className = 'flex justify-center space-x-4 md:space-x-6 py-8 rounded-md shadow-sm';
  204.  
  205. // 设置内联样式
  206. flexDiv.style.flexDirection = 'column';
  207. flexDiv.style.alignItems='baseline'
  208. tools.removeChild(tools.children[1])
  209. tools.appendChild(flexDiv)
  210.  
  211. var videoDoc = document.querySelector('.order-first')
  212. // 判断是否播放视频页面
  213. if (videoDoc != undefined) {
  214. var prefix = 'https://surrit.com/'
  215. var suffix = '/playlist.m3u8'
  216. //获取播放列表m3u8
  217. // 使用evaluate执行XPath查询
  218. var nodeValue = document.evaluate(
  219. '/html/body/script[5]/text()', // XPath表达式
  220. document, // 要执行查询的节点
  221. null, // 解析的文档
  222. XPathResult.FIRST_ORDERED_NODE_TYPE, // 只获取第一个匹配的节点
  223. null
  224. ).singleNodeValue.textContent
  225. var index = nodeValue.indexOf("seek");
  226.  
  227. // 确保 "seek" 存在于字符串中,并且截取的位置不会是负数
  228. if (index !== -1 && index - 32 >= 0) {
  229. // 截取 "seek" 前的32个字符
  230. var first32Chars = nodeValue.substring(index - 38, index-2);
  231. var url =prefix+first32Chars+suffix
  232. console.log('the m3u8 url is:' ,url);
  233. fetch(url).then(resp=>{
  234. return resp.text()
  235. }).then(text => {
  236. // 将响应文本按行分割
  237. const lines = text.split('\n');
  238. const mediaUrls = [];
  239.  
  240. // 遍历每一行,解析媒体URL
  241. lines.forEach(line => {
  242. // 忽略空行和注释行
  243. if (line.trim() && !line.startsWith('#')) {
  244. // 媒体URL通常在EXTINF标签之后
  245. var fileInfo = {
  246. filename: filename,
  247. prefix: prefix+first32Chars+'/'+line.trim().split('/')[0] + '/',
  248. display: line.trim().split('/')[0],
  249. url: prefix+first32Chars+'/'+line.trim()
  250. }
  251. flexDiv.appendChild(createUrlElement(fileInfo))
  252. }
  253. });
  254.  
  255. // 输出解析出的媒体URL列表
  256. console.log(mediaUrls);
  257. })
  258. } else {
  259. console.log("'seek' 不在字符串中,或不足以截取32个字符。");
  260. }
  261. }
  262. });
  263. })();