8686// @match *://*.pornhub.com/view_video.php?viewkey=*
8787// @match *://*.pornhubpremium.com/view_video.php?viewkey=*
8888// @require https://update.greasyfork.org/scripts/498897/1404834/Toastnew.js
89- // @require https://code.jquery.com/jquery-3.7.1.min.js
90- // @author liuwanlin,heckles,人民的勤务员 <china.qinwuyuan@gmail.com>
89+ // @author liuwanlin,heckles,MatthewXY,人民的勤务员 <china.qinwuyuan@gmail.com>
9190// @namespace https://github.com/ChinaGodMan/UserScripts
9291// @supportURL https://github.com/ChinaGodMan/UserScripts/issues
9392// @homepageURL https://github.com/ChinaGodMan/UserScripts
101100// @compatible opera
102101// @compatible safari
103102// @compatible kiwi
104- // @version 2025.05.01.0128
103+ // @version 2026.3.31.1
105104// @created 2025-03-05 01:45:13
106105// @modified 2025-03-05 01:45:13
107106// ==/UserScript==
108107
109108//!人民的勤务员修改自以下脚本 感谢heckles和liuwanlin
110109/*https://greasyfork.org/zh-CN/scripts/491333
111110https://greasyfork.org/zh-CN/scripts/491329
111+ https://greasyfork.org/zh-CN/scripts/551131
112112 */
113- GM_addStyle ( `
114- .download-urls ul { padding: 10px; font-weight: bold; line-height: 1.5; }
115- .download-urls ul li { display: flex; align-items: center; height: 20px; max-width:400px; }
116- .download-url-label { /* width: 100px; */ text-align: right; }
117- .download-url-copy { flex: 1; }
118- .download-url-mp4 { flex: 1; }
119- .download-url-input { flex: 3; font-size: 12px; padding: 0 5px; border: 1px solid #ffff; margin: 0 5px; }
120- ` ) ;
121113
122114( function ( ) {
123115 'use strict'
@@ -131,9 +123,9 @@ GM_addStyle(`
131123 downloaderror : 'Error downloading video, please check the console for details' ,
132124 downloadfailed : 'Download failed' ,
133125 downloadfailed_nosize : 'Unable to retrieve file size' ,
134- copydownloadbtn : 'Copy address ' ,
126+ copydownloadbtn : 'Copy' ,
135127 copysuccess : 'Copy successful' ,
136- downloadbtn : 'Download video ' ,
128+ downloadbtn : 'Download' ,
137129 linkTip : 'Video download URL:'
138130 } ,
139131 'zh-CN,zh,zh-SG' : {
@@ -144,9 +136,9 @@ GM_addStyle(`
144136 downloaderror : '下载视频时出错,请到控制台查看详细信息' ,
145137 downloadfailed : '下载失败' ,
146138 downloadfailed_nosize : '无法获取文件大小' ,
147- copydownloadbtn : '复制地址 ' ,
139+ copydownloadbtn : '复制 ' ,
148140 copysuccess : '复制成功' ,
149- downloadbtn : '下载视频 ' ,
141+ downloadbtn : '下载 ' ,
150142 linkTip : '视频下载地址:'
151143 } ,
152144 'zh-TW,zh-HK,zh-MO' : {
@@ -157,9 +149,9 @@ GM_addStyle(`
157149 downloaderror : '下載視頻時出錯,請到控制台查看詳細信息' ,
158150 downloadfailed : '下載失敗' ,
159151 downloadfailed_nosize : '無法獲取文件大小' ,
160- copydownloadbtn : '複製地址 ' ,
152+ copydownloadbtn : '複製 ' ,
161153 copysuccess : '複製成功' ,
162- downloadbtn : '下載視頻 ' ,
154+ downloadbtn : '下載 ' ,
163155 linkTip : '視頻下載地址:'
164156 } ,
165157 'ja' : {
@@ -246,30 +238,22 @@ GM_addStyle(`
246238 unsafeWindow . VideoParsing . init ( )
247239 } , 200 )
248240 } )
249- //PC和非PC,注意判断条件里不能用$简写,不知为问什么
250- let playerdiv //let可以先不赋值,用在这里
251- if ( document . querySelector ( '#player' ) ) {
252- playerdiv = document . querySelector ( '#player' )
253- }
254- else {
255- console . log ( '安卓' )
256- playerdiv = document . querySelector ( '.playerWrapper' )
257- }
258- const playerDom = playerdiv
259- //
260-
261- if ( playerDom ) {
262- mutationObserver . observe ( playerDom , {
263- childList : true ,
264- subtree : true
265- } )
266- } else {
267- Toast ( translate ( 'finderror' ) , 3000 , 'rgb(219, 18, 35)' , '#ffffff' , 'top' )
268- }
269- } ) ( ) ;
270241
271- ( function ( ) {
272242 class VideoParsing {
243+ static addStyle ( ) {
244+ GM_addStyle ( `
245+ .download-urls { margin: 15px 0px; padding: 12px; background: #000; border: 1px solid #6f6f6f; border-radius: 5px; font-size: 14px; max-width: 600px; }
246+ .download-urls h3 { margin-bottom: 8px; font-size: 16px; font-weight: bold; color: #333; }
247+ .download-urls ul { padding: 0; margin: 0; list-style: none; }
248+ .download-urls ul li { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
249+ .download-url-label { flex: 0 0 90px; font-weight: bold; color: #FFF; }
250+ .download-url-input { flex: 1; font-size: 12px; padding: 3px 6px; border: 1px solid #ccc; border-radius: 5px; background: #fff; color: #000; }
251+ .download-url-copy, .download-url-mp4 { padding: 4px 10px; border-radius: 5px; border: none; cursor: pointer; font-size: 12px; }
252+ .download-url-copy { background: #eee; color: #333; }
253+ .download-url-mp4 { background: #ff9000; color: #fff; }
254+ .download-url-copy:hover { background: #ddd; }
255+ ` )
256+ }
273257 // 根据 key 开头字母获取对象中的值,返回数组
274258 static getObjectValueByStartsWithChar ( obj , char ) {
275259 const vars = [ ]
@@ -289,17 +273,18 @@ GM_addStyle(`
289273 const flashvars = this . getObjectValueByStartsWithChar ( unsafeWindow , 'flashvars_' )
290274 if ( ! flashvars . length ) {
291275 Toast ( translate ( 'fetcherror' ) , 3000 , 'rgb(219, 18, 35)' , '#ffffff' , 'top' )
276+ console . warn ( translate . finderror )
292277 return
293278 }
294279 let videosInfo = [ ]
295280 try {
296281 videosInfo = flashvars [ 0 ] [ 'value' ] [ 'mediaDefinitions' ]
297282 } catch ( e ) {
298283 Toast ( translate ( 'fetcherror' ) , 3000 , 'rgb(219, 18, 35)' , '#ffffff' , 'top' )
299- console . error ( translate ( ' fetcherror' ) , e , flashvars )
284+ console . error ( translate . fetcherror , e )
300285 return
301286 }
302- let remoteAddress = undefined
287+ let remoteAddress
303288 let urlInfo = [ ]
304289 for ( let i = 0 ; i < videosInfo . length ; i ++ ) {
305290 if ( videosInfo [ i ] [ 'remote' ] ) {
@@ -308,59 +293,103 @@ GM_addStyle(`
308293 }
309294 }
310295
311- // MP4 信息
312296 if ( remoteAddress ) {
313- $ . ajax ( {
314- url : remoteAddress ,
315- async : false ,
316- success : ( data ) => {
317- if ( data && data . length ) {
318- urlInfo = urlInfo . concat ( data . map ( item => ( {
319- quality : item . quality + '.' + item . format ,
320- url : item . videoUrl
321- } ) ) )
297+ const xhr = new XMLHttpRequest ( )
298+ xhr . open ( 'GET' , remoteAddress , false )
299+ xhr . onload = function ( ) {
300+ if ( xhr . status === 200 ) {
301+ try {
302+ const data = JSON . parse ( xhr . responseText )
303+ if ( Array . isArray ( data ) ) {
304+ urlInfo = urlInfo . concat ( data . map ( item => ( {
305+ quality : item . quality + '.' + item . format ,
306+ url : item . videoUrl
307+ } ) ) )
308+ }
309+ } catch ( err ) {
310+ console . error ( err )
322311 }
323312 }
324- } )
313+ }
314+ xhr . send ( )
325315 }
326- console . log ( videosInfo )
327316 return urlInfo
328317 }
329318
330319 // 注入到下载面板
331320 static injectUrls2Dom ( urlInfo ) {
332- const li = [ ]
333- urlInfo . forEach ( item => {
334- li . push ( `
335- <li>
336- <span class="download-url-label">[ ${ item . quality } ]</span>
337- <input class="download-url-input" value="${ item . url } " style="width: 50px;" />
338- <a target="_blank" class="download-url-copy" data-href="${ item . url } " href="javascript: void(0);">${ translate ( 'copydownloadbtn' ) } </a>
339- <a target="_blank" class="download-url-mp4" data-href="${ item . url } " href="javascript: void(0);">${ translate ( 'downloadbtn' ) } </a>
340- </li>
341- ` )
342- } )
343- //pc和非PC两种情况都加上
344- $ ( '.playerWrapper' ) . after ( `<div class="download-urls"><h3>${ translate ( 'linkTip' ) } </h3><ul>${ li . join ( '' ) } </ul></div>` )
345- $ ( '#player' ) . after ( `<div class="download-urls"><h3>${ translate ( 'linkTip' ) } </h3><ul>${ li . join ( '' ) } </ul></div>` )
321+ const container = document . createElement ( 'div' )
322+ container . className = 'download-urls'
323+
324+ const title = document . createElement ( 'h3' )
325+ title . textContent = translate . linkTip
326+ container . appendChild ( title )
327+
328+ if ( urlInfo && urlInfo . length > 0 ) {
329+ const ul = document . createElement ( 'ul' )
330+ urlInfo . forEach ( item => {
331+ const li = document . createElement ( 'li' )
332+
333+ const label = document . createElement ( 'span' )
334+ label . className = 'download-url-label'
335+ label . textContent = `[ ${ item . quality } ]`
336+
337+ const input = document . createElement ( 'input' )
338+ input . className = 'download-url-input'
339+ input . value = item . url
340+ input . readOnly = true
341+
342+ const copyBtn = document . createElement ( 'button' )
343+ copyBtn . className = 'download-url-copy'
344+ copyBtn . textContent = translate . copydownloadbtn
345+ copyBtn . dataset . href = item . url
346+
347+ const dlBtn = document . createElement ( 'button' )
348+ dlBtn . className = 'download-url-mp4'
349+ dlBtn . textContent = translate . downloadbtn
350+ dlBtn . dataset . href = item . url
351+
352+ li . appendChild ( label )
353+ li . appendChild ( input )
354+ li . appendChild ( copyBtn )
355+ li . appendChild ( dlBtn )
356+ ul . appendChild ( li )
357+ } )
358+ container . appendChild ( ul )
359+ }
360+
361+ const player = document . querySelector ( '#player' )
362+ if ( player ) {
363+ player . insertAdjacentElement ( 'afterend' , container )
364+ }
365+
366+ const playerWrapper = document . querySelector ( '.playerWrapper' )
367+ if ( playerWrapper ) {
368+ playerWrapper . insertAdjacentElement ( 'afterend' , container )
369+ }
346370 }
347371
348372 // 初始化事件
349373 static initEvens ( ) {
350- // 点击下载复制到粘贴板中
351- $ ( document ) . on ( 'click' , '.download-url-copy' , function ( e ) {
352- e . preventDefault ( )
353- GM_setClipboard ( $ ( this ) . data ( 'href' ) )
354- Toast ( translate ( 'copysuccess' ) , 3000 , 'rgb(18, 219, 18)' , '#ffffff' , 'top' )
374+ document . addEventListener ( 'click' , function ( e ) {
375+ if ( e . target . classList . contains ( 'download-url-copy' ) ) {
376+ e . preventDefault ( )
377+ const url = e . target . dataset . href
378+ GM_setClipboard ( url )
379+ Toast ( translate . copysuccess , 3000 , 'rgb(18, 219, 18)' , '#ffffff' , 'top' )
380+ }
355381 } )
356382 }
357383 static initDownEvens ( ) {
358- $ ( document ) . on ( 'click' , '.download-url-mp4' , function ( e ) {
359- e . preventDefault ( )
360- downloadMp4 ( $ ( this ) . data ( 'href' ) , $ ( this ) )
384+ document . addEventListener ( 'click' , function ( e ) {
385+ if ( e . target . classList . contains ( 'download-url-mp4' ) ) {
386+ e . preventDefault ( )
387+ downloadMp4 ( e . target . dataset . href , e . target )
388+ }
361389 } )
362390 }
363391 static init ( ) {
392+ this . addStyle ( )
364393 this . injectUrls2Dom ( this . getUrlInfo ( ) )
365394 this . initEvens ( )
366395 this . initDownEvens ( )
@@ -379,47 +408,44 @@ GM_addStyle(`
379408 function sanitizeTitle ( ) {
380409 var title = document . title
381410 title = title . replace ( / - P o r n h u b \. c o m / , '' )
382- return title . replace ( / [ / : * ? " < > | ] / g, '_' )
411+ return title . replace ( / [ / : * ? " < > | ] / g, '_' ) . trim ( )
383412 }
384413
385-
386414 //下载函数
387- async function downloadMp4 ( videoUrl , targetElement ) {
415+ async function downloadMp4 ( videoUrl , element ) {
388416 try {
389417 const response = await fetch ( videoUrl )
390418 if ( ! response . ok ) {
391- $ ( targetElement ) . text ( translate ( 'downloadfailed' ) )
419+ element . textContent = translate ( 'downloadfailed' )
392420 }
393421
394422 const contentLength = response . headers . get ( 'Content-Length' )
395423 if ( ! contentLength ) {
396- $ ( targetElement ) . text ( translate ( 'downloadfailed_nosize' ) )
397-
424+ element . textContent = translate ( 'downloadfailed_nosize' )
398425 return
399426 }
400427 const reader = response . body . getReader ( )
401- const totalSize = contentLength ? parseInt ( contentLength , 10 ) : 0 // 文件总大小
402- let downloadedSize = 0 // 已下载大小
403- const chunks = [ ] // 存储数据块
428+ const totalSize = contentLength ? parseInt ( contentLength , 10 ) : 0
429+ let downloadedSize = 0
430+ const chunks = [ ]
404431 while ( true ) {
405432 const { done, value } = await reader . read ( )
406433 if ( done ) break
407- // 更新下载大小
408434 downloadedSize += value . length
409435 chunks . push ( value )
410436 if ( totalSize ) {
411437 const progress = ( ( downloadedSize / totalSize ) * 100 ) . toFixed ( 2 )
412- $ ( targetElement ) . text ( `${ translate ( 'downloading' ) } ${ progress } % (${ getHumanReadableSize ( downloadedSize ) } / ${ getHumanReadableSize ( totalSize ) } )` )
438+ element . textContent = `${ translate ( 'downloading' ) } ${ progress } % (${ getHumanReadableSize ( downloadedSize ) } / ${ getHumanReadableSize ( totalSize ) } )`
413439 } else {
414- $ ( targetElement ) . text ( `${ translate ( 'downloading' ) } ${ getHumanReadableSize ( downloadedSize ) } ` )
440+ element . textContent = `${ translate ( 'downloading' ) } ${ getHumanReadableSize ( downloadedSize ) } `
415441 }
416442 }
417443 const blob = new Blob ( chunks )
418444 const url = window . URL . createObjectURL ( blob )
419445 const a = document . createElement ( 'a' )
420446 a . style . display = 'none'
421447 a . href = url
422- a . download = sanitizeTitle ( ) . trim ( ) + '.mp4'
448+ a . download = sanitizeTitle ( ) + '.mp4'
423449 document . body . appendChild ( a )
424450 a . click ( )
425451 window . URL . revokeObjectURL ( url )
@@ -430,4 +456,5 @@ GM_addStyle(`
430456 console . error ( translate ( 'downloaderror' ) , error )
431457 }
432458 }
459+ VideoParsing . init ( )
433460} ) ( )
0 commit comments