diff --git a/package-lock.json b/package-lock.json index af47714d..6ef81d1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "unblockneteasemusic", + "name": "@nondanee/unblockneteasemusic", "version": "0.23.1", "lockfileVersion": 1 } diff --git a/src/crypto.js b/src/crypto.js index be5ce859..1b08c010 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -50,7 +50,7 @@ module.exports = { } }, miguapi: { - encrypt: object => { + encryptBody: object => { const text = JSON.stringify(object) const derive = (password, salt, keyLength, ivSize) => { // EVP_BytesToKey salt = salt || Buffer.alloc(0) @@ -97,4 +97,6 @@ module.exports = { .once('finish', () => resolve(digest.read())) }) } -} \ No newline at end of file +} + +try {module.exports.kuwoapi = require('./kwDES')} catch(e) {} \ No newline at end of file diff --git a/src/kwDES.js b/src/kwDES.js new file mode 100644 index 00000000..ffe1834d --- /dev/null +++ b/src/kwDES.js @@ -0,0 +1,291 @@ +/* + Thanks to + https://github.com/XuShaohua/kwplayer/blob/master/kuwo/DES.py + https://github.com/Levi233/MusicPlayer/blob/master/app/src/main/java/com/chenhao/musicplayer/utils/crypt/KuwoDES.java +*/ + +const Long = ( + typeof(BigInt) === 'function' // BigInt support in Node 10+ + ? n => (n = BigInt(n), ({ + low: Number(n), + valueOf: () => n.valueOf(), + toString: () => n.toString(), + not: () => Long(~n), + isNegative: () => n < 0, + or: x => Long(n | BigInt(x)), + and: x => Long(n & BigInt(x)), + xor: x => Long(n ^ BigInt(x)), + equals: x => n === BigInt(x), + multiply: x => Long(n * BigInt(x)), + shiftLeft: x => Long(n << BigInt(x)), + shiftRight: x => Long(n >> BigInt(x)), + })) + : (...args) => new (require('long'))(...args) +) + +const range = n => Array.from(new Array(n).keys()) +const power = (base, index) => Array(index).fill().reduce((result) => result.multiply(base), Long(1)) +const LongArray = (...array) => array.map(n => n === -1 ? Long(-1, -1) : Long(n)) + +// EXPANSION +const arrayE = LongArray( + 31, 0, 1, 2, 3, 4, -1, -1, + 3, 4, 5, 6, 7, 8, -1, -1, + 7, 8, 9, 10, 11, 12, -1, -1, + 11, 12, 13, 14, 15, 16, -1, -1, + 15, 16, 17, 18, 19, 20, -1, -1, + 19, 20, 21, 22, 23, 24, -1, -1, + 23, 24, 25, 26, 27, 28, -1, -1, + 27, 28, 29, 30, 31, 30, -1, -1, +) + +// INITIAL_PERMUTATION +const arrayIP = LongArray( + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7, + 56, 48, 40, 32, 24, 16, 8, 0, + 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, +) + +// INVERSE_PERMUTATION +const arrayIP_1 = LongArray( + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25, + 32, 0, 40, 8, 48, 16, 56, 24, +) + +// ROTATES +const arrayLs = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1] +const arrayLsMask = LongArray(0, 0x100001, 0x300003) +const arrayMask = range(64).map(n => power(2, n)) +arrayMask[arrayMask.length - 1] = arrayMask[arrayMask.length - 1].multiply(-1) + +// PERMUTATION +const arrayP = LongArray( + 15, 6, 19, 20, 28, 11, 27, 16, + 0, 14, 22, 25, 4, 17, 30, 9, + 1, 7, 23, 13, 31, 26, 2, 8, + 18, 12, 29, 5, 21, 10, 3, 24, +) + +// PERMUTED_CHOICE1 +const arrayPC_1 = LongArray( + 56, 48, 40, 32, 24, 16, 8, 0, + 57, 49, 41, 33, 25, 17, 9, 1, + 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 62, 54, 46, 38, + 30, 22, 14, 6, 61, 53, 45, 37, + 29, 21, 13, 5, 60, 52, 44, 36, + 28, 20, 12, 4, 27, 19, 11, 3, +) + +// PERMUTED_CHOICE2 +const arrayPC_2 = LongArray( + 13, 16, 10, 23, 0, 4, -1, -1, + 2, 27, 14, 5, 20, 9, -1, -1, + 22, 18, 11, 3, 25, 7, -1, -1, + 15, 6, 26, 19, 12, 1, -1, -1, + 40, 51, 30, 36, 46, 54, -1, -1, + 29, 39, 50, 44, 32, 47, -1, -1, + 43, 48, 38, 55, 33, 52, -1, -1, + 45, 41, 49, 35, 28, 31, -1, -1, +) + +const matrixNSBox = [[ + 14, 4, 3, 15, 2, 13, 5, 3, + 13, 14, 6, 9, 11, 2, 0, 5, + 4, 1, 10, 12, 15, 6, 9, 10, + 1, 8, 12, 7, 8, 11, 7, 0, + 0, 15, 10, 5, 14, 4, 9, 10, + 7, 8, 12, 3, 13, 1, 3, 6, + 15, 12, 6, 11, 2, 9, 5, 0, + 4, 2, 11, 14, 1, 7, 8, 13, ], [ + 15, 0, 9, 5, 6, 10, 12, 9, + 8, 7, 2, 12, 3, 13, 5, 2, + 1, 14, 7, 8, 11, 4, 0, 3, + 14, 11, 13, 6, 4, 1, 10, 15, + 3, 13, 12, 11, 15, 3, 6, 0, + 4, 10, 1, 7, 8, 4, 11, 14, + 13, 8, 0, 6, 2, 15, 9, 5, + 7, 1, 10, 12, 14, 2, 5, 9, ], [ + 10, 13, 1, 11, 6, 8, 11, 5, + 9, 4, 12, 2, 15, 3, 2, 14, + 0, 6, 13, 1, 3, 15, 4, 10, + 14, 9, 7, 12, 5, 0, 8, 7, + 13, 1, 2, 4, 3, 6, 12, 11, + 0, 13, 5, 14, 6, 8, 15, 2, + 7, 10, 8, 15, 4, 9, 11, 5, + 9, 0, 14, 3, 10, 7, 1, 12, ], [ + 7, 10, 1, 15, 0, 12, 11, 5, + 14, 9, 8, 3, 9, 7, 4, 8, + 13, 6, 2, 1, 6, 11, 12, 2, + 3, 0, 5, 14, 10, 13, 15, 4, + 13, 3, 4, 9, 6, 10, 1, 12, + 11, 0, 2, 5, 0, 13, 14, 2, + 8, 15, 7, 4, 15, 1, 10, 7, + 5, 6, 12, 11, 3, 8, 9, 14, ], [ + 2, 4, 8, 15, 7, 10, 13, 6, + 4, 1, 3, 12, 11, 7, 14, 0, + 12, 2, 5, 9, 10, 13, 0, 3, + 1, 11, 15, 5, 6, 8, 9, 14, + 14, 11, 5, 6, 4, 1, 3, 10, + 2, 12, 15, 0, 13, 2, 8, 5, + 11, 8, 0, 15, 7, 14, 9, 4, + 12, 7, 10, 9, 1, 13, 6, 3, ], [ + 12, 9, 0, 7, 9, 2, 14, 1, + 10, 15, 3, 4, 6, 12, 5, 11, + 1, 14, 13, 0, 2, 8, 7, 13, + 15, 5, 4, 10, 8, 3, 11, 6, + 10, 4, 6, 11, 7, 9, 0, 6, + 4, 2, 13, 1, 9, 15, 3, 8, + 15, 3, 1, 14, 12, 5, 11, 0, + 2, 12, 14, 7, 5, 10, 8, 13, ], [ + 4, 1, 3, 10, 15, 12, 5, 0, + 2, 11, 9, 6, 8, 7, 6, 9, + 11, 4, 12, 15, 0, 3, 10, 5, + 14, 13, 7, 8, 13, 14, 1, 2, + 13, 6, 14, 9, 4, 1, 2, 14, + 11, 13, 5, 0, 1, 10, 8, 3, + 0, 11, 3, 5, 9, 4, 15, 2, + 7, 8, 12, 15, 10, 7, 6, 12, ], [ + 13, 7, 10, 0, 6, 9, 5, 15, + 8, 4, 3, 10, 11, 14, 12, 5, + 2, 11, 9, 6, 15, 12, 0, 3, + 4, 1, 14, 13, 1, 2, 7, 8, + 1, 2, 12, 15, 10, 4, 0, 3, + 13, 14, 6, 9, 7, 8, 9, 6, + 15, 1, 5, 12, 3, 10, 14, 5, + 8, 7, 11, 0, 4, 13, 2, 11, ], +] + +const bitTransform = (arrInt, n, l) => { // int[], int, long : long + let l2 = Long(0) + range(n).forEach(i => { + if (arrInt[i].isNegative() || (l.and(arrayMask[arrInt[i].low]).equals(0))) + return + l2 = l2.or(arrayMask[i]) + }) + return l2 +} + +const DES64 = (longs, l) => { // long[], long + let out = Long(0) + let SOut = Long(0) + const pR = range(8).map(() => Long(0)) + const pSource = [Long(0), Long(0)] + let L = Long(0) + let R = Long(0) + out = bitTransform(arrayIP, 64, l) + pSource[0] = out.and(0xFFFFFFFF) + pSource[1] = out.and(-4294967296).shiftRight(32) + + range(16).forEach(i => { + R = Long(pSource[1]) + R = bitTransform(arrayE, 64, R) + R = R.xor(longs[i]) + range(8).forEach(j => { + pR[j] = R.shiftRight(j * 8).and(255) + }) + SOut = Long(0) + range(8).reverse().forEach(sbi => { + SOut = SOut.shiftLeft(4).or(matrixNSBox[sbi][pR[sbi]]) + }) + R = bitTransform(arrayP, 32, SOut) + L = Long(pSource[0]) + pSource[0] = Long(pSource[1]) + pSource[1] = L.xor(R) + }) + pSource.reverse() + out = pSource[1].shiftLeft(32).and(-4294967296).or( + pSource[0].and(0xFFFFFFFF) + ) + out = bitTransform(arrayIP_1, 64, out) + return out +} + + +const subKeys = (l, longs, n) => { // long, long[], int + let l2 = bitTransform(arrayPC_1, 56, l) + range(16).forEach(i => { + l2 = ( + l2.and(arrayLsMask[arrayLs[i]]).shiftLeft(28 - arrayLs[i]).or( + l2.and(arrayLsMask[arrayLs[i]].not()).shiftRight(arrayLs[i]) + ) + ) + longs[i] = bitTransform(arrayPC_2, 64, l2) + }) + if (n === 1) { + range(8).forEach(j => { + [longs[j], longs[15 - j]] = [longs[15 - j], longs[j]] + }) + } +} + +const crypt = (msg, key, mode) => { + // 处理密钥块 + let l = Long(0) + range(8).forEach(i => { + l = Long(key[i]).shiftLeft(i * 8).or(l) + }) + + const j = Math.floor(msg.length / 8) + // arrLong1 存放的是转换后的密钥块, 在解密时只需要把这个密钥块反转就行了 + + const arrLong1 = range(16).map(() => Long(0)) + subKeys(l, arrLong1, mode) + + // arrLong2 存放的是前部分的明文 + const arrLong2 = range(j).map(() => Long(0)) + + range(j).forEach(m => { + range(8).forEach(n => { + arrLong2[m] = Long(msg[n + m * 8]).shiftLeft(n * 8).or(arrLong2[m]) + }) + }) + + // 用于存放密文 + const arrLong3 = range(Math.floor((1 + 8 * (j + 1)) / 8)).map(() => Long(0)) + + // 计算前部的数据块(除了最后一部分) + range(j).forEach(i1 => { + arrLong3[i1] = DES64(arrLong1, arrLong2[i1]) + }) + + // 保存多出来的字节 + const arrByte1 = msg.slice(j * 8) + let l2 = Long(0) + + range(msg.length % 8).forEach(i1 => { + l2 = Long(arrByte1[i1]).shiftLeft(i1 * 8).or(l2) + }) + + // 计算多出的那一位(最后一位) + if (arrByte1.length || mode === 0) arrLong3[j] = DES64(arrLong1, l2) // 解密不需要 + + // 将密文转为字节型 + const arrByte2 = range(8 * arrLong3.length).map(() => 0) + let i4 = 0 + arrLong3.forEach(l3 => { + range(8).forEach(i6 => { + arrByte2[i4] = l3.shiftRight(i6 * 8).and(255).low + i4 += 1 + }) + }) + return Buffer.from(arrByte2) +} + +const SECRET_KEY = Buffer.from('ylzsxkwm') +const encrypt = msg => crypt(msg, SECRET_KEY, 0) +const decrypt = msg => crypt(msg, SECRET_KEY, 1) +const encryptQuery = query => encrypt(Buffer.from(query)).toString('base64') + +module.exports = {encrypt, decrypt, encryptQuery} \ No newline at end of file diff --git a/src/provider/kuwo.js b/src/provider/kuwo.js index 470e315a..3f9d61d3 100644 --- a/src/provider/kuwo.js +++ b/src/provider/kuwo.js @@ -1,6 +1,7 @@ const cache = require('../cache') const insure = require('./insure') const select = require('./select') +const crypto = require('../crypto') const request = require('../request') const format = song => ({ @@ -54,19 +55,19 @@ const search = info => { } const track = id => { - const url = - 'http://antiserver.kuwo.cn/anti.s?' + - 'type=convert_url&format=mp3&response=url&rid=MUSIC_' + id - // 'http://www.kuwo.cn/url?' + - // 'format=mp3&response=url&type=convert_url3&br=320kmp3&rid=' + id + const url = (crypto.kuwoapi + ? 'http://mobi.kuwo.cn/mobi.s?f=kuwo&q=' + crypto.kuwoapi.encryptQuery( + 'corp=kuwo&p2p=1&type=convert_url2&sig=0&format=' + ['flac', 'mp3'].slice(1).join('|') + '&rid=' + id + ) + : 'http://antiserver.kuwo.cn/anti.s?type=convert_url&format=mp3&response=url&rid=MUSIC_' + id // flac refuse + // : 'http://www.kuwo.cn/url?format=mp3&response=url&type=convert_url3&br=320kmp3&rid=' + id // flac refuse + ) - return request('GET', url) + return request('GET', url, {'user-agent': 'okhttp/3.10.0'}) .then(response => response.body()) .then(body => { - if (body.startsWith('http')) - return body - else - return Promise.reject() + const url = (body.match(/http[^\s$"]+/) || [])[0] + return url || Promise.reject() }) .catch(() => insure().kuwo.track(id)) } diff --git a/src/provider/migu.js b/src/provider/migu.js index 9026fd37..a4e429dd 100644 --- a/src/provider/migu.js +++ b/src/provider/migu.js @@ -37,7 +37,7 @@ const search = info => { const track = id => { const url = 'http://music.migu.cn/v3/api/music/audioPlayer/getPlayInfo?' + - 'dataType=2&' + crypto.miguapi.encrypt({copyrightId: id.toString()}) + 'dataType=2&' + crypto.miguapi.encryptBody({copyrightId: id.toString()}) return request('GET', url, headers) .then(response => response.json())