Greasy Fork

Greasy Fork is available in English.

115 mediainfo fetcher

115网盘在线获取mediainfo

目前为 2021-05-20 提交的版本,查看 最新版本

// ==UserScript==
// @author       original author T3rry, modified by JackSlow
// @name         115 mediainfo fetcher
// @description  115网盘在线获取mediainfo
// @namespace    https://115.com/MediaInfo
// @version      3.1.7
// @match        https://115.com/*
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @grant        GM_log
// @connect      proapi.115.com
// @connect      115.com
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/forge.min.js
// @require      https://unpkg.com/mediainfo.js/dist/mediainfo.min.js
// ==/UserScript==

;(function () {
  'use strict'

  const pub_key = `-----BEGIN PUBLIC KEY-----\
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDR3rWmeYnRClwLBB0Rq0dlm8Mr\
PmWpL5I23SzCFAoNpJX6Dn74dfb6y02YH15eO6XmeBHdc7ekEFJUIi+swganTokR\
IVRRr/z16/3oh7ya22dcAqg191y+d6YDr4IGg/Q5587UKJMj35yQVXaeFXmLlFPo\
kFiz4uPxhrB7BGqZbQIDAQAB\
-----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 priv = window.forge.pki.privateKeyFromPem(private_key)
  const pub = window.forge.pki.publicKeyFromPem(pub_key)
  const g_key_l = [0x42, 0xda, 0x13, 0xba, 0x78, 0x76, 0x8d, 0x37, 0xe8, 0xee, 0x04, 0x91]
  const g_key_s = [0x29, 0x23, 0x21, 0x5e]
  const g_kts = [0xf0, 0xe5, 0x69, 0xae, 0xbf, 0xdc, 0xbf, 0x5a, 0x1a, 0x45, 0xe8, 0xbe, 0x7d, 0xa6, 0x73, 0x88, 0xde, 0x8f, 0xe7, 0xc4, 0x45, 0xda, 0x86, 0x94, 0x9b, 0x69, 0x92, 0x0b, 0x6a, 0xb8, 0xf1, 0x7a, 0x38, 0x06, 0x3c, 0x95, 0x26, 0x6d, 0x2c, 0x56, 0x00, 0x70, 0x56, 0x9c, 0x36, 0x38, 0x62, 0x76, 0x2f, 0x9b, 0x5f, 0x0f, 0xf2, 0xfe, 0xfd, 0x2d, 0x70, 0x9c, 0x86, 0x44, 0x8f, 0x3d, 0x14, 0x27, 0x71, 0x93, 0x8a, 0xe4, 0x0e, 0xc1, 0x48, 0xae, 0xdc, 0x34, 0x7f, 0xcf, 0xfe, 0xb2, 0x7f, 0xf6, 0x55, 0x9a, 0x46, 0xc8, 0xeb, 0x37, 0x77, 0xa4, 0xe0, 0x6b, 0x72, 0x93, 0x7e, 0x51, 0xcb, 0xf1, 0x37, 0xef, 0xad, 0x2a, 0xde, 0xee, 0xf9, 0xc9, 0x39, 0x6b, 0x32, 0xa1, 0xba, 0x35, 0xb1, 0xb8, 0xbe, 0xda, 0x78, 0x73, 0xf8, 0x20, 0xd5, 0x27, 0x04, 0x5a, 0x6f, 0xfd, 0x5e, 0x72, 0x39, 0xcf, 0x3b, 0x9c, 0x2b, 0x57, 0x5c, 0xf9, 0x7c, 0x4b, 0x7b, 0xd2, 0x12, 0x66, 0xcc, 0x77, 0x09, 0xa6]
  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 += priv.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({
      format: 'text',
      chunkSize: 1024 * 1024 * 3,
      locateFile: () => 'https://unpkg.com/mediainfo.js/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
  }
})()