// ==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/[email protected]/dist/forge.min.js
// @require https://unpkg.com/[email protected]/dist/umd/index.min.js
// @require https://unpkg.com/[email protected]/BigInteger.min.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 = `<pre style="white-space: pre-wrap;">${result}</pre>`
}
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/[email protected]/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
}
})()