// ==UserScript== // @author original author T3rry, modified by JackSlow, new key generated by showmethemoney2022 // @name 115 mediainfo fetcher // @description 115网盘在线获取mediainfo // @namespace https://115.com/MediaInfo // @version 3.2.2 // @match https://115.com/* // @grant GM_xmlhttpRequest // @grant unsafeWindow // @grant GM_log // @connect 115cdn.net // @connect 115.com // @require https://unpkg.com/node-forge@0.10.0/dist/forge.min.js // @require https://unpkg.com/mediainfo.js@0.3.4/dist/umd/index.min.js // @require https://unpkg.com/big-integer@1.6.51/BigInteger.min.js // @downloadURL https://update.greasyfork.icu/scripts/423190/115%20mediainfo%20fetcher.user.js // @updateURL https://update.greasyfork.icu/scripts/423190/115%20mediainfo%20fetcher.meta.js // ==/UserScript== ; (function() { 'use strict' class MyRsa { constructor() { this.n = bigInt('8686980c0f5a24c4b9d43020cd2c22703ff3f450756529058b1cf88f09b8602136477198a6e2683149659bd122c33592fdb5ad47944ad1ea4d36c6b172aad6338c3bb6ac6227502d010993ac967d1aef00f0c8e038de2e4d3bc2ec368af2e9f10a6f1eda4f7262f136420c07c331b871bf139f74f3010e3c4fe57df3afb71683', 16) this.e = bigInt('10001', 16) }; a2hex(byteArray) { var hexString = '' var nextHexByte for (var i = 0; i < byteArray.length; i++) { nextHexByte = byteArray[i].toString(16) if (nextHexByte.length < 2) { nextHexByte = '0' + nextHexByte } hexString += nextHexByte } return hexString } hex2a(hex) { var str = '' for (var i = 0; i < hex.length; i += 2) { str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)) } return str } pkcs1pad2(s, n) { if (n < s.length + 11) { return null } var ba = [] var i = s.length - 1 while (i >= 0 && n > 0) { ba[--n] = s.charCodeAt(i--) } ba[--n] = 0 while (n > 2) { // random non-zero pad ba[--n] = 0xff } ba[--n] = 2 ba[--n] = 0 var c = this.a2hex(ba) return bigInt(c, 16) } pkcs1unpad2(a) { var b = a.toString(16) if (b.length % 2 !== 0) { b = '0' + b } var c = this.hex2a(b) var i = 1 while (c.charCodeAt(i) !== 0) { i++ } return c.slice(i + 1) } encrypt(text) { var m = this.pkcs1pad2(text, 0x80) var c = m.modPow(this.e, this.n) var h = c.toString(16) while (h.length < 0x80 * 2) { h = '0' + h } return h }; decrypt(text) { var ba = [] var i = 0 while (i < text.length) { ba[i] = text.charCodeAt(i) i += 1 } var a = bigInt(this.a2hex(ba), 16) var c = a.modPow(this.e, this.n) var d = this.pkcs1unpad2(c) return d }; } const pub_key = '-----BEGIN PUBLIC KEY-----\ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCGhpgMD1okxLnUMCDNLCJwP/P0\ UHVlKQWLHPiPCbhgITZHcZim4mgxSWWb0SLDNZL9ta1HlErR6k02xrFyqtYzjDu2\ rGInUC0BCZOsln0a7wDwyOA43i5NO8LsNory6fEKbx7aT3Ji8TZCDAfDMbhxvxOf\ dPMBDjxP5X3zr7cWgwIDAQAB\ -----END PUBLIC KEY-----'; const private_key = '-----BEGIN RSA PRIVATE KEY-----\ MIICXAIBAAKBgQCMgUJLwWb0kYdW6feyLvqgNHmwgeYYlocst8UckQ1+waTOKHFC\ TVyRSb1eCKJZWaGa08mB5lEu/asruNo/HjFcKUvRF6n7nYzo5jO0li4IfGKdxso6\ FJIUtAke8rA2PLOubH7nAjd/BV7TzZP2w0IlanZVS76n8gNDe75l8tonQQIDAQAB\ AoGANwTasA2Awl5GT/t4WhbZX2iNClgjgRdYwWMI1aHbVfqADZZ6m0rt55qng63/\ 3NsjVByAuNQ2kB8XKxzMoZCyJNvnd78YuW3Zowqs6HgDUHk6T5CmRad0fvaVYi6t\ viOkxtiPIuh4QrQ7NUhsLRtbH6d9s1KLCRDKhO23pGr9vtECQQDpjKYssF+kq9iy\ A9WvXRjbY9+ca27YfarD9WVzWS2rFg8MsCbvCo9ebXcmju44QhCghQFIVXuebQ7Q\ pydvqF0lAkEAmgLnib1XonYOxjVJM2jqy5zEGe6vzg8aSwKCYec14iiJKmEYcP4z\ DSRms43hnQsp8M2ynjnsYCjyiegg+AZ87QJANuwwmAnSNDOFfjeQpPDLy6wtBeft\ 5VOIORUYiovKRZWmbGFwhn6BQL+VaafrNaezqUweBRi1PYiAF2l3yLZbUQJAf/nN\ 4Hz/pzYmzLlWnGugP5WCtnHKkJWoKZBqO2RfOBCq+hY4sxvn3BHVbXqGcXLnZPvo\ YuaK7tTXxZSoYLEzeQJBAL8Mt3AkF1Gci5HOug6jT4s4Z+qDDrUXo9BlTwSWP90v\ wlHF+mkTJpKd5Wacef0vV+xumqNorvLpIXWKwxNaoHM=\ -----END RSA PRIVATE KEY-----'; const my_rsa = new MyRsa() const priv = window.forge.pki.privateKeyFromPem(private_key) const pub = window.forge.pki.publicKeyFromPem(pub_key) const g_key_l = [120, 6, 173, 76, 51, 134, 93, 24, 76, 1, 63, 70]; const g_key_s = [0x29, 0x23, 0x21, 0x5e] const g_kts = [240, 229, 105, 174, 191, 220, 191, 138, 26, 69, 232, 190, 125, 166, 115, 184, 222, 143, 231, 196, 69, 218, 134, 196, 155, 100, 139, 20, 106, 180, 241, 170, 56, 1, 53, 158, 38, 105, 44, 134, 0, 107, 79, 165, 54, 52, 98, 166, 42, 150, 104, 24, 242, 74, 253, 189, 107, 151, 143, 77, 143, 137, 19, 183, 108, 142, 147, 237, 14, 13, 72, 62, 215, 47, 136, 216, 254, 254, 126, 134, 80, 149, 79, 209, 235, 131, 38, 52, 219, 102, 123, 156, 126, 157, 122, 129, 50, 234, 182, 51, 222, 58, 169, 89, 52, 102, 59, 170, 186, 129, 96, 72, 185, 213, 129, 156, 248, 108, 132, 119, 255, 84, 120, 38, 95, 190, 232, 30, 54, 159, 52, 128, 92, 69, 44, 155, 118, 213, 27, 143, 204, 195, 184, 245]; const m115_l_rnd_key = genRandom(16) let m115_s_rnd_key = [] let key_s = [] let key_l = [] function intToByte(i) { var b = i & 0xFF var c = 0 if (b >= 256) { c = b % 256 c = -1 * (256 - c) } else { c = b } return c } function stringToArray(s) { var map = Array.prototype.map var array = map.call(s, function(x) { return x.charCodeAt(0) }) return array } function arrayTostring(array) { var result = '' for (var i = 0; i < array.length; ++i) { result += (String.fromCharCode(array[i])) } return result } function m115_init() { key_s = [] key_l = [] } function m115_setkey(randkey, sk_len) { var length = sk_len * (sk_len - 1) var index = 0 var xorkey = '' if (randkey) { for (var i = 0; i < sk_len; i++) { var x = intToByte((randkey[i]) + (g_kts[index])) xorkey += String.fromCharCode(g_kts[length] ^ x) length -= sk_len index += sk_len } if (sk_len === 4) { key_s = stringToArray(xorkey) } else if (sk_len === 12) { key_l = stringToArray(xorkey) } } } function xor115_enc(src, key) { var lkey = key.length var secret = [] var num = 0 var pad = (src.length) % 4 if (pad > 0) { for (var i = 0; i < pad; i++) { secret.push((src[i]) ^ key[i]) } src = src.slice(pad) } for (i = 0; i < src.length; i++) { if (num >= lkey) { num = num % lkey } secret.push((src[i] ^ key[num])) num += 1 } return secret } function genRandom(len) { var keys = [] var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz23456789' var maxPos = chars.length for (var i = 0; i < len; i++) { keys.push(chars.charAt(Math.floor(Math.random() * maxPos)).charCodeAt(0)) } return keys } function m115_encode(plaintext) { // console.log('m115_encode:') m115_init() key_l = g_key_l m115_setkey(m115_l_rnd_key, 4) var tmp = xor115_enc(stringToArray(plaintext), key_s).reverse() var xortext = xor115_enc(tmp, key_l) var text = arrayTostring(m115_l_rnd_key) + arrayTostring(xortext) var ciphertext = pub.encrypt(text) ciphertext = encodeURIComponent(window.forge.util.encode64(ciphertext)) return ciphertext } function m115_decode(ciphertext) { // console.log('m115_decode:') var bciphertext = window.forge.util.decode64(ciphertext) var block = bciphertext.length / (128) var plaintext = '' var index = 0 for (var i = 1; i <= block; ++i) { plaintext += my_rsa.decrypt(bciphertext.slice(index, i * 128)) index += 128 } m115_s_rnd_key = stringToArray(plaintext.slice(0, 16)) plaintext = plaintext.slice(16) m115_setkey(m115_l_rnd_key, 4) m115_setkey(m115_s_rnd_key, 12) var tmp = xor115_enc(stringToArray(plaintext), key_l).reverse() plaintext = xor115_enc(tmp, key_s) return arrayTostring(plaintext) } function PostData(dict) { var k, tmp, v tmp = [] for (k in dict) { v = dict[k] tmp.push(k + '=' + v) } // console.log(tmp.join('&')) return tmp.join('&') }; waitForKeyElements('div.file-opr', AddMediaInfoBtn) function AddMediaInfoBtn(jNode) { var aclass2 = document.createElement('a') aclass2.addEventListener('click', function(e) { ispan2.innerText = '获取中...' handleMediaInfoButton(jNode.parentNode).then(() => ispan2.innerText = '获取MediaInfo') }) var iclass2 = document.createElement('i') var ispan2 = document.createElement('span') var node2 = document.createTextNode('获取MediaInfo') ispan2.appendChild(node2) aclass2.appendChild(iclass2) aclass2.appendChild(ispan2) jNode.appendChild(aclass2) } function getFileUri({ file_id, pick_code }) { return new Promise((resolve, reject) => { const data = PostData({ data: m115_encode(`{"pickcode":"${pick_code}"}`) }) // console.log('PostData:', data) GM_xmlhttpRequest({ data, method: 'POST', url: 'https://proapi.115.com/app/chrome/downurl', headers: { 'Content-Type': 'application/x-www-form-urlencoded', "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", }, responseType: 'json', onerror: reject, onabort: reject, onload: function(response) { if (response.status === 200) { let json = m115_decode(response.response.data) json = JSON.parse(json) // console.log('GetFileLink response json:', json) const fileUri = json[file_id]['url']['url'] // const cookie = DeleteCookie(response.responseHeaders) || null // resolve({fileUri, cookie}) // no need cookies to download file resolve(fileUri) } else { console.log('getFileUri response:', response) reject(new Error('获取文件直链失败')) } } }) }) } function fetchChunk({ fileUri, headers }) { headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36' return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: fileUri, headers: headers, responseType: 'arraybuffer', onerror: reject, onabort: reject, onload: function(response) { if ([206, 200].includes(response.status)) { resolve(new Uint8Array(response.response)) } else { console.log('fetchChunk response:', response) reject(new Error('获取文件内容失败')) } } }) }) } function readChunk({ totalSize, fileUri, size, offset }) { if (!size) return new Uint8Array([]) const headers = {} const start = offset const end = Math.min(offset + size, totalSize) // console.log('readChunk:', {start, end}) if (Number.isInteger(start) && Number.isInteger(end)) headers.Range = `bytes=${start}-${end}` return fetchChunk({ fileUri, headers }) } async function handleMediaInfoButton(liNode) { const file_type = liNode.getAttribute('file_type') if (file_type !== '1') return alert('请选择文件') const totalSize = parseInt(liNode.getAttribute('file_size')) if (!totalSize) return alert('文件大小缺失') const sha1 = liNode.getAttribute('sha1') // if (!sha1) return alert('文件sha1缺失') const file_id = liNode.getAttribute('file_id') if (!file_id) return alert('文件file_id缺失') if (miRunning[file_id]) return alert('正在获取此文件的mediainfo,请稍后') const pick_code = liNode.getAttribute('pick_code') if (!pick_code) return alert('文件pick_code缺失') const file_name = liNode.getAttribute('title') // console.log({file_name, totalSize, file_type, sha1, file_id}) miRunning[file_id] = true try { const fileUri = await getFileUri({ pick_code, file_id }) const info = await getMediainfo({ totalSize, fileUri, file_id, sha1 }) showMediainfo(file_name, info) } catch (e) { console.log('getMediainfo error:', e) alert('获取mediainfo失败:' + e.message) } miRunning[file_id] = false } function showMediainfo(filename, info) { const head = `FileName: ${filename}\n\n==== MediaInfo ====\n\n` const result = head + info const win = window.open('', filename, 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=800,height=900') win.document.body.innerHTML = `
${result}
` } const miCache = {} const miRunning = {} async function getMediainfo({ totalSize, fileUri, file_id, sha1 }) { const exist = miCache[sha1] || miCache[file_id] if (exist) return exist const MI = window.MediaInfo if (!MI) throw new Error('MEDIAINFO依赖脚本未加载') const mediainfo = MI && await MI.mediaInfoFactory({ format: 'text', chunkSize: 1024 * 1024 * 3, locateFile: () => 'https://unpkg.com/mediainfo.js@0.3.4/dist/MediaInfoModule.wasm' }) const info = await mediainfo.analyzeData(() => totalSize, (size, offset) => { return readChunk({ totalSize, fileUri, size, offset }) }) return miCache[file_id] = miCache[sha1] = info } // https://gist.github.com/mjblay/18d34d861e981b7785e407c3b443b99b /* --- waitForKeyElements(): A utility function, for Greasemonkey scripts, that detects and handles AJAXed content. Forked for use without JQuery. Usage example: waitForKeyElements ( "div.comments" , commentCallbackFunction ); //--- Page-specific function to do what we want when the node is found. function commentCallbackFunction (element) { element.text ("This comment changed by waitForKeyElements()."); } IMPORTANT: Without JQuery, this fork does not look into the content of iframes. */ function waitForKeyElements( selectorTxt, /* Required: The selector string that specifies the desired element(s). */ actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */ bWaitOnce /* Optional: If false, will continue to scan for new elements even after the first match is found. */ ) { var targetNodes, btargetsFound targetNodes = document.querySelectorAll(selectorTxt) if (targetNodes && targetNodes.length > 0) { btargetsFound = true /* --- Found target node(s). Go through each and act if they are new. */ targetNodes.forEach(function(element) { var alreadyFound = element.dataset.found === 'alreadyFound' ? 'alreadyFound' : false if (!alreadyFound) { // --- Call the payload function. var cancelFound = actionFunction(element) if (cancelFound) { btargetsFound = false } else { element.dataset.found = 'alreadyFound' } } }) } else { btargetsFound = false } // --- Get the timer-control variable for this selector. var controlObj = waitForKeyElements.controlObj || {} var controlKey = selectorTxt.replace(/[^\w]/g, '_') var timeControl = controlObj[controlKey] // --- Now set or clear the timer as appropriate. if (btargetsFound && bWaitOnce && timeControl) { // --- The only condition where we need to clear the timer. clearInterval(timeControl) delete controlObj[controlKey] } else { // --- Set a timer, if needed. if (!timeControl) { timeControl = setInterval(function() { waitForKeyElements(selectorTxt, actionFunction, bWaitOnce ) }, 300 ) controlObj[controlKey] = timeControl } } waitForKeyElements.controlObj = controlObj } })()