request.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. const encrypt = require('./crypto')
  2. const CryptoJS = require('crypto-js')
  3. const { default: axios } = require('axios')
  4. const { PacProxyAgent } = require('pac-proxy-agent')
  5. const http = require('http')
  6. const https = require('https')
  7. const tunnel = require('tunnel')
  8. const fs = require('fs')
  9. const path = require('path')
  10. const tmpPath = require('os').tmpdir()
  11. const { cookieToJson, cookieObjToString, toBoolean } = require('./index')
  12. const anonymous_token = fs.readFileSync(
  13. path.resolve(tmpPath, './anonymous_token'),
  14. 'utf-8',
  15. )
  16. const { URLSearchParams, URL } = require('url')
  17. const iosAppVersion = '9.0.65'
  18. const { APP_CONF } = require('../util/config.json')
  19. // request.debug = true // 开启可看到更详细信息
  20. const chooseUserAgent = (uaType) => {
  21. const userAgentMap = {
  22. mobile:
  23. 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1',
  24. pc: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0',
  25. }
  26. if (uaType === 'mobile') {
  27. return userAgentMap.mobile
  28. }
  29. return userAgentMap.pc
  30. }
  31. const createRequest = (uri, data, options) => {
  32. const cookie = options.cookie || {}
  33. return new Promise((resolve, reject) => {
  34. options.headers = options.headers || {}
  35. let headers = options.headers
  36. let ip = options.realIP || options.ip || ''
  37. // console.log(ip)
  38. if (ip) {
  39. headers['X-Real-IP'] = ip
  40. headers['X-Forwarded-For'] = ip
  41. }
  42. // headers['X-Real-IP'] = '118.88.88.88'
  43. if (typeof options.cookie === 'object') {
  44. options.cookie = {
  45. ...options.cookie,
  46. __remember_me: true,
  47. // NMTID: CryptoJS.lib.WordArray.random(16).toString(),
  48. _ntes_nuid: CryptoJS.lib.WordArray.random(16).toString(),
  49. }
  50. if (uri.indexOf('login') === -1) {
  51. options.cookie['NMTID'] = CryptoJS.lib.WordArray.random(16).toString()
  52. }
  53. if (!options.cookie.MUSIC_U) {
  54. // 游客
  55. if (!options.cookie.MUSIC_A) {
  56. options.cookie.MUSIC_A = anonymous_token
  57. }
  58. }
  59. headers['Cookie'] = cookieObjToString(options.cookie)
  60. } else if (options.cookie) {
  61. // cookie string
  62. headers['Cookie'] = options.cookie
  63. } else {
  64. const cookie = cookieToJson('__remember_me=true; NMTID=xxx')
  65. headers['Cookie'] = cookieObjToString(cookie)
  66. }
  67. // console.log(options.cookie, headers['Cookie'])
  68. let url = '',
  69. encryptData = '',
  70. crypto = options.crypto,
  71. csrfToken = cookie['__csrf'] || ''
  72. if (crypto === '') {
  73. // 加密方式为空,以配置文件的加密方式为准
  74. if (APP_CONF.encrypt) {
  75. crypto = 'eapi'
  76. } else {
  77. crypto = 'api'
  78. }
  79. }
  80. // 根据加密方式加密请求数据;目前任意uri都支持四种加密方式
  81. switch (crypto) {
  82. case 'weapi':
  83. headers['Referer'] = APP_CONF.domain
  84. headers['User-Agent'] = options.ua || chooseUserAgent('pc')
  85. data.csrf_token = csrfToken
  86. encryptData = encrypt.weapi(data)
  87. url = APP_CONF.domain + '/weapi/' + uri.substr(5)
  88. break
  89. case 'linuxapi':
  90. headers['User-Agent'] =
  91. 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
  92. encryptData = encrypt.linuxapi({
  93. method: 'POST',
  94. url: APP_CONF.apiDomain + uri,
  95. params: data,
  96. })
  97. url = 'https://music.163.com/api/linux/forward'
  98. break
  99. case 'eapi':
  100. case 'api':
  101. // 两种加密方式,都应生成客户端的cookie
  102. const cookie = options.cookie || {}
  103. const header = {
  104. osver: cookie.osver || '17.4.1', //系统版本
  105. deviceId: cookie.deviceId || global.deviceId,
  106. os: cookie.os || 'ios',
  107. appver: cookie.appver || (cookie.os != 'pc' ? iosAppVersion : ''), // app版本
  108. versioncode: cookie.versioncode || '140', //版本号
  109. mobilename: cookie.mobilename || '', //设备model
  110. buildver: cookie.buildver || Date.now().toString().substr(0, 10),
  111. resolution: cookie.resolution || '1920x1080', //设备分辨率
  112. __csrf: csrfToken,
  113. channel: cookie.channel || '',
  114. requestId: `${Date.now()}_${Math.floor(Math.random() * 1000)
  115. .toString()
  116. .padStart(4, '0')}`,
  117. }
  118. if (cookie.MUSIC_U) header['MUSIC_U'] = cookie.MUSIC_U
  119. if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
  120. headers['Cookie'] = Object.keys(header)
  121. .map(
  122. (key) =>
  123. encodeURIComponent(key) + '=' + encodeURIComponent(header[key]),
  124. )
  125. .join('; ')
  126. headers['User-Agent'] = options.ua || chooseUserAgent(options.uaType)
  127. if (crypto === 'eapi') {
  128. // 使用eapi加密
  129. data.header = header
  130. data.e_r =
  131. options.e_r != undefined
  132. ? options.e_r
  133. : data.e_r != undefined
  134. ? data.e_r
  135. : APP_CONF.encryptResponse // 用于加密接口返回值
  136. data.e_r = toBoolean(data.e_r)
  137. encryptData = encrypt.eapi(uri, data)
  138. url = APP_CONF.apiDomain + '/eapi/' + uri.substr(5)
  139. } else if (crypto === 'api') {
  140. // 不使用任何加密
  141. url = APP_CONF.apiDomain + uri
  142. encryptData = data
  143. }
  144. break
  145. default:
  146. // 未知的加密方式
  147. console.log('[ERR]', 'Unknown Crypto:', crypto)
  148. break
  149. }
  150. const answer = { status: 500, body: {}, cookie: [] }
  151. // console.log(headers, 'headers')
  152. let settings = {
  153. method: 'POST',
  154. url: url,
  155. headers: headers,
  156. data: new URLSearchParams(encryptData).toString(),
  157. httpAgent: new http.Agent({ keepAlive: true }),
  158. httpsAgent: new https.Agent({ keepAlive: true }),
  159. }
  160. if (data.e_r) {
  161. settings = {
  162. ...settings,
  163. encoding: null,
  164. responseType: 'arraybuffer',
  165. }
  166. }
  167. if (options.proxy) {
  168. if (options.proxy.indexOf('pac') > -1) {
  169. settings.httpAgent = new PacProxyAgent(options.proxy)
  170. settings.httpsAgent = new PacProxyAgent(options.proxy)
  171. } else {
  172. const purl = new URL(options.proxy)
  173. if (purl.hostname) {
  174. const agent = tunnel[
  175. purl.protocol === 'https' ? 'httpsOverHttp' : 'httpOverHttp'
  176. ]({
  177. proxy: {
  178. host: purl.hostname,
  179. port: purl.port || 80,
  180. proxyAuth:
  181. purl.username && purl.password
  182. ? purl.username + ':' + purl.password
  183. : '',
  184. },
  185. })
  186. settings.httpsAgent = agent
  187. settings.httpAgent = agent
  188. settings.proxy = false
  189. } else {
  190. console.error('代理配置无效,不使用代理')
  191. }
  192. }
  193. } else {
  194. settings.proxy = false
  195. }
  196. axios(settings)
  197. .then((res) => {
  198. const body = res.data
  199. answer.cookie = (res.headers['set-cookie'] || []).map((x) =>
  200. x.replace(/\s*Domain=[^(;|$)]+;*/, ''),
  201. )
  202. try {
  203. if (data.e_r) {
  204. // eapi接口返回值被加密,需要解密
  205. answer.body = encrypt.eapiResDecrypt(
  206. body.toString('hex').toUpperCase(),
  207. )
  208. } else {
  209. answer.body =
  210. typeof body == 'object' ? body : JSON.parse(body.toString())
  211. }
  212. if (answer.body.code) {
  213. answer.body.code = Number(answer.body.code)
  214. }
  215. answer.status = Number(answer.body.code || res.status)
  216. if (
  217. [201, 302, 400, 502, 800, 801, 802, 803].indexOf(answer.body.code) >
  218. -1
  219. ) {
  220. // 特殊状态码
  221. answer.status = 200
  222. }
  223. } catch (e) {
  224. // console.log(e)
  225. // can't decrypt and can't parse directly
  226. answer.body = body
  227. answer.status = res.status
  228. }
  229. answer.status =
  230. 100 < answer.status && answer.status < 600 ? answer.status : 400
  231. if (answer.status === 200) resolve(answer)
  232. else reject(answer)
  233. })
  234. .catch((err) => {
  235. answer.status = 502
  236. answer.body = { code: 502, msg: err }
  237. reject(answer)
  238. })
  239. })
  240. }
  241. module.exports = createRequest