chore: rewrite the netease music resolver.

This commit is contained in:
Yufan Sheng 2024-11-22 15:21:34 +08:00
parent 85542d9c46
commit d30b4b2dc7
Signed by: syhily
GPG Key ID: 9D18A22A7DCD5A9B
8 changed files with 998 additions and 240 deletions

View File

@ -1,4 +0,0 @@
const net_ease_anonymous_token =
'de91e1f8119d32e01cc73efcb82c0a30c9137e8d4f88dbf5e3d7bf3f28998f21add2bc8204eeee5e56c0bbb8743574b46ca2c10c35dc172199bef9bf4d60ecdeab066bb4dc737d1c3324751bcc9aaf44c3061cd18d77b7a0';
export { net_ease_anonymous_token };

View File

@ -0,0 +1,726 @@
// An request token which is decoded from the Netease Android Player.
const neteaseAnonymousToken =
'de91e1f8119d32e01cc73efcb82c0a30c9137e8d4f88dbf5e3d7bf3f28998f21add2bc8204eeee5e56c0bbb8743574b46ca2c10c35dc172199bef9bf4d60ecdeab066bb4dc737d1c3324751bcc9aaf44c3061cd18d77b7a0';
// A selected IP set which locates in China.
const chineseIPs = [
'58.14.0.0',
'58.16.0.0',
'58.24.0.0',
'58.30.0.0',
'58.32.0.0',
'58.66.0.0',
'58.68.128.0',
'58.82.0.0',
'58.87.64.0',
'58.99.128.0',
'58.100.0.0',
'58.116.0.0',
'58.128.0.0',
'58.144.0.0',
'58.154.0.0',
'58.192.0.0',
'58.240.0.0',
'59.32.0.0',
'59.64.0.0',
'59.80.0.0',
'59.107.0.0',
'59.108.0.0',
'59.151.0.0',
'59.155.0.0',
'59.172.0.0',
'59.191.0.0',
'59.191.240.0',
'59.192.0.0',
'60.0.0.0',
'60.55.0.0',
'60.63.0.0',
'60.160.0.0',
'60.194.0.0',
'60.200.0.0',
'60.208.0.0',
'60.232.0.0',
'60.235.0.0',
'60.245.128.0',
'60.247.0.0',
'60.252.0.0',
'60.253.128.0',
'60.255.0.0',
'61.4.80.0',
'61.4.176.0',
'61.8.160.0',
'61.28.0.0',
'61.29.128.0',
'61.45.128.0',
'61.47.128.0',
'61.48.0.0',
'61.87.192.0',
'61.128.0.0',
'61.232.0.0',
'61.236.0.0',
'61.240.0.0',
'114.28.0.0',
'114.54.0.0',
'114.60.0.0',
'114.64.0.0',
'114.68.0.0',
'114.80.0.0',
'116.1.0.0',
'116.2.0.0',
'116.4.0.0',
'116.8.0.0',
'116.13.0.0',
'116.16.0.0',
'116.52.0.0',
'116.56.0.0',
'116.58.128.0',
'116.58.208.0',
'116.60.0.0',
'116.66.0.0',
'116.69.0.0',
'116.70.0.0',
'116.76.0.0',
'116.89.144.0',
'116.90.184.0',
'116.95.0.0',
'116.112.0.0',
'116.116.0.0',
'116.128.0.0',
'116.192.0.0',
'116.193.16.0',
'116.193.32.0',
'116.194.0.0',
'116.196.0.0',
'116.198.0.0',
'116.199.0.0',
'116.199.128.0',
'116.204.0.0',
'116.207.0.0',
'116.208.0.0',
'116.212.160.0',
'116.213.64.0',
'116.213.128.0',
'116.214.32.0',
'116.214.64.0',
'116.214.128.0',
'116.215.0.0',
'116.216.0.0',
'116.224.0.0',
'116.242.0.0',
'116.244.0.0',
'116.248.0.0',
'116.252.0.0',
'116.254.128.0',
'116.255.128.0',
'117.8.0.0',
'117.21.0.0',
'117.22.0.0',
'117.24.0.0',
'117.32.0.0',
'117.40.0.0',
'117.44.0.0',
'117.48.0.0',
'117.53.48.0',
'117.53.176.0',
'117.57.0.0',
'117.58.0.0',
'117.59.0.0',
'117.60.0.0',
'117.64.0.0',
'117.72.0.0',
'117.74.64.0',
'117.74.128.0',
'117.75.0.0',
'117.76.0.0',
'117.80.0.0',
'117.100.0.0',
'117.103.16.0',
'117.103.128.0',
'117.106.0.0',
'117.112.0.0',
'117.120.64.0',
'117.120.128.0',
'117.121.0.0',
'117.121.128.0',
'117.121.192.0',
'117.122.128.0',
'117.124.0.0',
'117.128.0.0',
'118.24.0.0',
'118.64.0.0',
'118.66.0.0',
'118.67.112.0',
'118.72.0.0',
'118.80.0.0',
'118.84.0.0',
'118.88.32.0',
'118.88.64.0',
'118.88.128.0',
'118.89.0.0',
'118.91.240.0',
'118.102.16.0',
'118.112.0.0',
'118.120.0.0',
'118.124.0.0',
'118.126.0.0',
'118.132.0.0',
'118.144.0.0',
'118.178.0.0',
'118.180.0.0',
'118.184.0.0',
'118.192.0.0',
'118.212.0.0',
'118.224.0.0',
'118.228.0.0',
'118.230.0.0',
'118.239.0.0',
'118.242.0.0',
'118.244.0.0',
'118.248.0.0',
'119.0.0.0',
'119.2.0.0',
'119.2.128.0',
'119.3.0.0',
'119.4.0.0',
'119.8.0.0',
'119.10.0.0',
'119.15.136.0',
'119.16.0.0',
'119.18.192.0',
'119.18.208.0',
'119.18.224.0',
'119.19.0.0',
'119.20.0.0',
'119.27.64.0',
'119.27.160.0',
'119.27.192.0',
'119.28.0.0',
'119.30.48.0',
'119.31.192.0',
'119.32.0.0',
'119.40.0.0',
'119.40.64.0',
'119.40.128.0',
'119.41.0.0',
'119.42.0.0',
'119.42.136.0',
'119.42.224.0',
'119.44.0.0',
'119.48.0.0',
'119.57.0.0',
'119.58.0.0',
'119.59.128.0',
'119.60.0.0',
'119.62.0.0',
'119.63.32.0',
'119.75.208.0',
'119.78.0.0',
'119.80.0.0',
'119.84.0.0',
'119.88.0.0',
'119.96.0.0',
'119.108.0.0',
'119.112.0.0',
'119.128.0.0',
'119.144.0.0',
'119.148.160.0',
'119.161.128.0',
'119.162.0.0',
'119.164.0.0',
'119.176.0.0',
'119.232.0.0',
'119.235.128.0',
'119.248.0.0',
'119.253.0.0',
'119.254.0.0',
'120.0.0.0',
'120.24.0.0',
'120.30.0.0',
'120.32.0.0',
'120.48.0.0',
'120.52.0.0',
'120.64.0.0',
'120.72.32.0',
'120.72.128.0',
'120.76.0.0',
'120.80.0.0',
'120.90.0.0',
'120.92.0.0',
'120.94.0.0',
'120.128.0.0',
'120.136.128.0',
'120.137.0.0',
'120.192.0.0',
'121.0.16.0',
'121.4.0.0',
'121.8.0.0',
'121.16.0.0',
'121.32.0.0',
'121.40.0.0',
'121.46.0.0',
'121.48.0.0',
'121.51.0.0',
'121.52.160.0',
'121.52.208.0',
'121.52.224.0',
'121.55.0.0',
'121.56.0.0',
'121.58.0.0',
'121.58.144.0',
'121.59.0.0',
'121.60.0.0',
'121.68.0.0',
'121.76.0.0',
'121.79.128.0',
'121.89.0.0',
'121.100.128.0',
'121.101.208.0',
'121.192.0.0',
'121.201.0.0',
'121.204.0.0',
'121.224.0.0',
'121.248.0.0',
'121.255.0.0',
'122.0.64.0',
'122.0.128.0',
'122.4.0.0',
'122.8.0.0',
'122.48.0.0',
'122.49.0.0',
'122.51.0.0',
'122.64.0.0',
'122.96.0.0',
'122.102.0.0',
'122.102.64.0',
'122.112.0.0',
'122.119.0.0',
'122.136.0.0',
'122.144.128.0',
'122.152.192.0',
'122.156.0.0',
'122.192.0.0',
'122.198.0.0',
'122.200.64.0',
'122.204.0.0',
'122.224.0.0',
'122.240.0.0',
'122.248.48.0',
'123.0.128.0',
'123.4.0.0',
'123.8.0.0',
'123.49.128.0',
'123.52.0.0',
'123.56.0.0',
'123.64.0.0',
'123.96.0.0',
'123.98.0.0',
'123.99.128.0',
'123.100.0.0',
'123.101.0.0',
'123.103.0.0',
'123.108.128.0',
'123.108.208.0',
'123.112.0.0',
'123.128.0.0',
'123.136.80.0',
'123.137.0.0',
'123.138.0.0',
'123.144.0.0',
'123.160.0.0',
'123.176.80.0',
'123.177.0.0',
'123.178.0.0',
'123.180.0.0',
'123.184.0.0',
'123.196.0.0',
'123.199.128.0',
'123.206.0.0',
'123.232.0.0',
'123.242.0.0',
'123.244.0.0',
'123.249.0.0',
'123.253.0.0',
'124.6.64.0',
'124.14.0.0',
'124.16.0.0',
'124.20.0.0',
'124.28.192.0',
'124.29.0.0',
'124.31.0.0',
'124.40.112.0',
'124.40.128.0',
'124.42.0.0',
'124.47.0.0',
'124.64.0.0',
'124.66.0.0',
'124.67.0.0',
'124.68.0.0',
'124.72.0.0',
'124.88.0.0',
'124.108.8.0',
'124.108.40.0',
'124.112.0.0',
'124.126.0.0',
'124.128.0.0',
'124.147.128.0',
'124.156.0.0',
'124.160.0.0',
'124.172.0.0',
'124.192.0.0',
'124.196.0.0',
'124.200.0.0',
'124.220.0.0',
'124.224.0.0',
'124.240.0.0',
'124.240.128.0',
'124.242.0.0',
'124.243.192.0',
'124.248.0.0',
'124.249.0.0',
'124.250.0.0',
'124.254.0.0',
'125.31.192.0',
'125.32.0.0',
'125.58.128.0',
'125.61.128.0',
'125.62.0.0',
'125.64.0.0',
'125.96.0.0',
'125.98.0.0',
'125.104.0.0',
'125.112.0.0',
'125.169.0.0',
'125.171.0.0',
'125.208.0.0',
'125.210.0.0',
'125.213.0.0',
'125.214.96.0',
'125.215.0.0',
'125.216.0.0',
'125.254.128.0',
'134.196.0.0',
'159.226.0.0',
'161.207.0.0',
'162.105.0.0',
'166.111.0.0',
'167.139.0.0',
'168.160.0.0',
'169.211.1.0',
'192.83.122.0',
'192.83.169.0',
'192.124.154.0',
'192.188.170.0',
'198.17.7.0',
'202.0.110.0',
'202.0.176.0',
'202.4.128.0',
'202.4.252.0',
'202.8.128.0',
'202.10.64.0',
'202.14.88.0',
'202.14.235.0',
'202.14.236.0',
'202.14.238.0',
'202.20.120.0',
'202.22.248.0',
'202.38.0.0',
'202.38.64.0',
'202.38.128.0',
'202.38.136.0',
'202.38.138.0',
'202.38.140.0',
'202.38.146.0',
'202.38.149.0',
'202.38.150.0',
'202.38.152.0',
'202.38.156.0',
'202.38.158.0',
'202.38.160.0',
'202.38.164.0',
'202.38.168.0',
'202.38.176.0',
'202.38.184.0',
'202.38.192.0',
'202.41.152.0',
'202.41.240.0',
'202.43.144.0',
'202.46.32.0',
'202.46.224.0',
'202.60.112.0',
'202.63.248.0',
'202.69.4.0',
'202.69.16.0',
'202.70.0.0',
'202.74.8.0',
'202.75.208.0',
'202.85.208.0',
'202.90.0.0',
'202.90.224.0',
'202.90.252.0',
'202.91.0.0',
'202.91.128.0',
'202.91.176.0',
'202.91.224.0',
'202.92.0.0',
'202.92.252.0',
'202.93.0.0',
'202.93.252.0',
'202.95.0.0',
'202.95.252.0',
'202.96.0.0',
'202.112.0.0',
'202.120.0.0',
'202.122.0.0',
'202.122.32.0',
'202.122.64.0',
'202.122.112.0',
'202.122.128.0',
'202.123.96.0',
'202.124.24.0',
'202.125.176.0',
'202.127.0.0',
'202.127.12.0',
'202.127.16.0',
'202.127.40.0',
'202.127.48.0',
'202.127.112.0',
'202.127.128.0',
'202.127.160.0',
'202.127.192.0',
'202.127.208.0',
'202.127.212.0',
'202.127.216.0',
'202.127.224.0',
'202.130.0.0',
'202.130.224.0',
'202.131.16.0',
'202.131.48.0',
'202.131.208.0',
'202.136.48.0',
'202.136.208.0',
'202.136.224.0',
'202.141.160.0',
'202.142.16.0',
'202.143.16.0',
'202.148.96.0',
'202.149.160.0',
'202.149.224.0',
'202.150.16.0',
'202.152.176.0',
'202.153.48.0',
'202.158.160.0',
'202.160.176.0',
'202.164.0.0',
'202.164.25.0',
'202.165.96.0',
'202.165.176.0',
'202.165.208.0',
'202.168.160.0',
'202.170.128.0',
'202.170.216.0',
'202.173.8.0',
'202.173.224.0',
'202.179.240.0',
'202.180.128.0',
'202.181.112.0',
'202.189.80.0',
'202.192.0.0',
'203.18.50.0',
'203.79.0.0',
'203.80.144.0',
'203.81.16.0',
'203.83.56.0',
'203.86.0.0',
'203.86.64.0',
'203.88.32.0',
'203.88.192.0',
'203.89.0.0',
'203.90.0.0',
'203.90.128.0',
'203.90.192.0',
'203.91.32.0',
'203.91.96.0',
'203.91.120.0',
'203.92.0.0',
'203.92.160.0',
'203.93.0.0',
'203.94.0.0',
'203.95.0.0',
'203.95.96.0',
'203.99.16.0',
'203.99.80.0',
'203.100.32.0',
'203.100.80.0',
'203.100.96.0',
'203.100.192.0',
'203.110.160.0',
'203.118.192.0',
'203.119.24.0',
'203.119.32.0',
'203.128.32.0',
'203.128.96.0',
'203.130.32.0',
'203.132.32.0',
'203.134.240.0',
'203.135.96.0',
'203.135.160.0',
'203.142.219.0',
'203.148.0.0',
'203.152.64.0',
'203.156.192.0',
'203.158.16.0',
'203.161.192.0',
'203.166.160.0',
'203.171.224.0',
'203.174.7.0',
'203.174.96.0',
'203.175.128.0',
'203.175.192.0',
'203.176.168.0',
'203.184.80.0',
'203.187.160.0',
'203.190.96.0',
'203.191.16.0',
'203.191.64.0',
'203.191.144.0',
'203.192.0.0',
'203.196.0.0',
'203.207.64.0',
'203.207.128.0',
'203.208.0.0',
'203.208.16.0',
'203.208.32.0',
'203.209.224.0',
'203.212.0.0',
'203.212.80.0',
'203.222.192.0',
'203.223.0.0',
'210.2.0.0',
'210.5.0.0',
'210.5.144.0',
'210.12.0.0',
'210.14.64.0',
'210.14.112.0',
'210.14.128.0',
'210.15.0.0',
'210.15.128.0',
'210.16.128.0',
'210.21.0.0',
'210.22.0.0',
'210.23.32.0',
'210.25.0.0',
'210.26.0.0',
'210.28.0.0',
'210.32.0.0',
'210.51.0.0',
'210.52.0.0',
'210.56.192.0',
'210.72.0.0',
'210.76.0.0',
'210.78.0.0',
'210.79.64.0',
'210.79.224.0',
'210.82.0.0',
'210.87.128.0',
'210.185.192.0',
'210.192.96.0',
'211.64.0.0',
'211.80.0.0',
'211.96.0.0',
'211.136.0.0',
'211.144.0.0',
'211.160.0.0',
'218.0.0.0',
'218.56.0.0',
'218.64.0.0',
'218.96.0.0',
'218.104.0.0',
'218.108.0.0',
'218.185.192.0',
'218.192.0.0',
'218.240.0.0',
'218.249.0.0',
'219.72.0.0',
'219.82.0.0',
'219.128.0.0',
'219.216.0.0',
'219.224.0.0',
'219.242.0.0',
'219.244.0.0',
'220.101.192.0',
'220.112.0.0',
'220.152.128.0',
'220.154.0.0',
'220.160.0.0',
'220.192.0.0',
'220.231.0.0',
'220.231.128.0',
'220.232.64.0',
'220.234.0.0',
'220.242.0.0',
'220.248.0.0',
'220.252.0.0',
'221.0.0.0',
'221.8.0.0',
'221.12.0.0',
'221.12.128.0',
'221.13.0.0',
'221.14.0.0',
'221.122.0.0',
'221.129.0.0',
'221.130.0.0',
'221.133.224.0',
'221.136.0.0',
'221.172.0.0',
'221.176.0.0',
'221.192.0.0',
'221.196.0.0',
'221.198.0.0',
'221.199.0.0',
'221.199.128.0',
'221.199.192.0',
'221.199.224.0',
'221.200.0.0',
'221.208.0.0',
'221.224.0.0',
'222.16.0.0',
'222.32.0.0',
'222.64.0.0',
'222.125.0.0',
'222.126.128.0',
'222.128.0.0',
'222.160.0.0',
'222.168.0.0',
'222.176.0.0',
'222.192.0.0',
'222.240.0.0',
'222.248.0.0',
];
// The User Agents which are used for each request. You can add or delete this for matching your needs.
const userAgents = {
mobile: [
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.',
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML like Gecko) Mobile/14A456 QQ/6.5.7.408 V1_IPH_SQ_6.5.7_1_APP_A Pixel/750 Core/UIWebView NetType/4G Mem/103',
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.15(0x17000f27) NetType/WIFI Language/zh',
'Mozilla/5.0 (Linux; Android 9; PCT-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.64 HuaweiBrowser/10.0.3.311 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; U; Android 9; zh-cn; Redmi Note 8 Build/PKQ1.190616.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.141 Mobile Safari/537.36 XiaoMi/MiuiBrowser/12.5.22',
'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.62 XWEB/2581 MMWEBSDK/200801 Mobile Safari/537.36 MMWEBID/3027 MicroMessenger/7.0.18.1740(0x27001235) Process/toolsmp WeChat/arm64 NetType/WIFI Language/zh_CN ABI/arm64',
'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; BKK-AL10 Build/HONORBKK-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.6 Mobile Safari/537.36',
],
pc: [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.30 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.30 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586',
],
};
export type CryptoMethod = 'weapi' | 'linuxapi' | 'eapi';
export type UserAgentType = 'mobile' | 'pc';
export type HttpMethod = 'POST' | 'GET' | 'HEAD' | 'OPTIONS' | 'DELETE' | 'TRACE' | 'PATCH' | 'PUT' | 'CONNECT';
export { chineseIPs, neteaseAnonymousToken, userAgents };

View File

@ -1,3 +1,4 @@
import type { HttpMethod } from '@/components/player/netease/config';
import { Buffer } from 'node:buffer'; import { Buffer } from 'node:buffer';
import crypto from 'node:crypto'; import crypto from 'node:crypto';
@ -9,21 +10,27 @@ const publicKey =
'-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----'; '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----';
const eapiKey = 'e82ckenh8dichen8'; const eapiKey = 'e82ckenh8dichen8';
const aesEncrypt = (buffer, mode, key, iv) => { const aesEncrypt = (
buffer: Buffer | Uint8Array,
mode: string,
key: Buffer | Uint8Array | string,
iv: Buffer | string,
) => {
const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv); const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv);
return Buffer.concat([cipher.update(buffer), cipher.final()]); return Buffer.concat([cipher.update(buffer), cipher.final()]);
}; };
const rsaEncrypt = (buffer, key) => { const rsaEncrypt = (buffer: Buffer | Uint8Array, key: string) => {
return crypto.publicEncrypt( return crypto.publicEncrypt(
{ key: key, padding: crypto.constants.RSA_NO_PADDING }, { key: key, padding: crypto.constants.RSA_NO_PADDING },
Buffer.concat([Buffer.alloc(128 - buffer.length), buffer]), Buffer.concat([Buffer.alloc(128 - buffer.length), buffer]),
); );
}; };
const weapi = (object) => { export const weapi = (form: Record<string, string>): Record<string, string> => {
const text = JSON.stringify(object); const text = JSON.stringify(form);
const secretKey = crypto.randomBytes(16).map((n) => base62.charAt(n % 62).charCodeAt()); const secretKey = crypto.randomBytes(16).map((n) => base62.charAt(n % 62).charCodeAt(0));
return { return {
params: aesEncrypt( params: aesEncrypt(
Buffer.from(aesEncrypt(Buffer.from(text), 'cbc', presetKey, iv).toString('base64')), Buffer.from(aesEncrypt(Buffer.from(text), 'cbc', presetKey, iv).toString('base64')),
@ -35,15 +42,30 @@ const weapi = (object) => {
}; };
}; };
const linuxapi = (object) => { export const linuxapi = (method: HttpMethod, url: string, form: Record<string, string>): Record<string, string> => {
const text = JSON.stringify(object); const request = {
method: method,
url: url.replace(/\w*api/, 'api'),
params: form,
};
const text = JSON.stringify(request);
return { return {
eparams: aesEncrypt(Buffer.from(text), 'ecb', linuxapiKey, '').toString('hex').toUpperCase(), eparams: aesEncrypt(Buffer.from(text), 'ecb', linuxapiKey, '').toString('hex').toUpperCase(),
}; };
}; };
const eapi = (url, object) => { export const eapi = (
const text = typeof object === 'object' ? JSON.stringify(object) : object; url: string,
form: Record<string, string>,
header: Record<string, string | boolean | number>,
): Record<string, string> => {
const request = {
...form,
header: header,
};
const text = JSON.stringify(request);
const message = `nobody${url}use${text}md5forencrypt`; const message = `nobody${url}use${text}md5forencrypt`;
const digest = crypto.createHash('md5').update(message).digest('hex'); const digest = crypto.createHash('md5').update(message).digest('hex');
const data = `${url}-36cd479b6b5-${text}-36cd479b6b5-${digest}`; const data = `${url}-36cd479b6b5-${text}-36cd479b6b5-${digest}`;
@ -51,10 +73,3 @@ const eapi = (url, object) => {
params: aesEncrypt(Buffer.from(data), 'ecb', eapiKey, '').toString('hex').toUpperCase(), params: aesEncrypt(Buffer.from(data), 'ecb', eapiKey, '').toString('hex').toUpperCase(),
}; };
}; };
const decrypt = (cipherBuffer) => {
const decipher = crypto.createDecipheriv('aes-128-ecb', eapiKey, '');
return Buffer.concat([decipher.update(cipherBuffer), decipher.final()]);
};
export default { weapi, linuxapi, eapi, decrypt };

View File

@ -0,0 +1,150 @@
// The request is a rewritten file which is type safe to dispatch the netease music requests.
// We will try to use three types of requests here. The weapi, linuxapi and eapi.
import {
chineseIPs,
neteaseAnonymousToken,
userAgents,
type CryptoMethod,
type HttpMethod,
type UserAgentType,
} from '@/components/player/netease/config';
import { eapi, linuxapi, weapi } from '@/components/player/netease/encrypt';
import { customAlphabet } from 'nanoid/non-secure';
const nanoid = customAlphabet('1234567890abcdef', 32);
const chooseUserAgent = (ua?: UserAgentType) => {
const agents = ua === undefined ? [...userAgents.mobile, ...userAgents.pc] : userAgents[ua];
return agents[Math.floor(Math.random() * agents.length)];
};
const randomChineseIP = () => chineseIPs[Math.floor(Math.random() * chineseIPs.length)];
export type RequestOptions = {
cookie?: Record<string, string | boolean | number>;
ua?: UserAgentType;
crypto: CryptoMethod;
url?: string;
};
export const request = async (
method: HttpMethod,
url: string,
form: Record<string, string>,
options: RequestOptions,
) => {
// Generate the fundamental request headers.
const headers: Record<string, string> = { 'User-Agent': chooseUserAgent(options.ua) };
const ip = randomChineseIP();
headers['X-Real-IP'] = ip;
headers['X-Forwarded-For'] = ip;
if (method === 'POST' || method === 'PUT') {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
if (url.includes('music.163.com')) {
headers.Referer = 'https://music.163.com';
}
// Append cookies to header.
let cookies = options.cookie ? options.cookie : {};
cookies = {
...cookies,
_remember_me: true,
_ntes_nuid: nanoid(),
};
if (!cookies.MUSIC_U) {
// This is a guest request.
if (!cookies.MUSIC_A) {
cookies.MUSIC_A = neteaseAnonymousToken;
}
}
headers.Cookie = Object.keys(cookies)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(cookies[key])}`)
.join('; ');
// Try to choose encrypt method for generating the fetch request.
let decryptedForm: Record<string, string>;
let decryptedURL: string;
if (options.crypto === 'weapi') {
const csrfToken = (headers.Cookie || '').match(/_csrf=([^(;|$)]+)/);
form.csrf_token = csrfToken ? csrfToken[1] : '';
decryptedForm = weapi(form);
decryptedURL = url.replace(/\w*api/, 'weapi');
} else if (options.crypto === 'linuxapi') {
headers['User-Agent'] =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36';
decryptedForm = linuxapi(method, url, form);
decryptedURL = 'https://music.163.com/api/linux/forward';
} else if (options.crypto === 'eapi') {
// Generate new cookies.
const header: Record<string, string | boolean | number> = {
osver: cookies.osver,
deviceId: cookies.deviceId,
appver: cookies.appver || '8.7.01',
versioncode: cookies.versioncode || '140',
mobilename: cookies.mobilename,
buildver: cookies.buildver || Date.now().toString().substring(0, 10),
resolution: cookies.resolution || '1920x1080',
__csrf: cookies.__csrf || '',
os: cookies.os || 'android',
channel: cookies.channel,
requestId: `${Date.now()}_${Math.floor(Math.random() * 1000)
.toString()
.padStart(4, '0')} `,
MUSIC_U: cookies.MUSIC_U,
MUSIC_A: cookies.MUSIC_A,
};
headers.Cookie = Object.keys(header)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(header[key])}`)
.join('; ');
if (options.url === undefined) {
throw new Error('eapi is required to have a url options.');
}
decryptedForm = eapi(options.url, form, header);
decryptedURL = url.replace(/\w*api/, 'eapi');
} else {
throw new Error(`Unsupported crypto method ${options.crypto}`);
}
let settings: Record<string, string | Record<string, string>> = {
method,
headers,
body: new URLSearchParams(decryptedForm).toString(),
};
if (options.crypto === 'eapi') {
settings = {
...settings,
responseType: 'arraybuffer',
};
}
let res: Response;
let count = 0;
let result = {};
do {
res = await fetch(decryptedURL, settings);
if (options.crypto === 'eapi') {
const enc = new TextDecoder();
result = JSON.parse(enc.decode(await res.arrayBuffer()));
} else {
result = await res.json();
}
count++;
if (count > 1) {
console.log(`Request ${count} times.`);
}
if (count > 5) {
console.error('Max retries exceeded.');
break;
}
await new Promise((resolve) => setTimeout(resolve, 100));
} while (res.status === -460);
return result;
};

View File

@ -1,35 +0,0 @@
import { map_song_list, request } from './util.js';
export const get_song_url = async (id) => {
const data = {
ids: `[${id}]`,
level: 'standard',
encodeType: 'flac',
};
const res = await request('POST', 'https://interface.music.163.com/eapi/song/enhance/player/url/v1', data, {
crypto: 'eapi',
url: '/api/song/enhance/player/url/v1',
cookie: {},
});
const url = res.data[0]?.url?.replace('http://', 'https://');
return url || `https://music.163.com/song/media/outer/url?id=${id}.mp3`;
};
export const get_song_info = async (id) => {
const ids = [id];
const data = {
c: `[${ids.map((id) => `{"id":${id}}`).join(',')}]`,
};
let res = await request('POST', 'https://music.163.com/api/v3/song/detail', data, {
crypto: 'weapi',
});
if (!res.songs) {
throw res;
}
res = map_song_list(res);
return res;
};

View File

@ -0,0 +1,87 @@
import { request } from '@/components/player/netease/request';
type SongUrl = {
url?: string;
};
type SongUrlResponse = {
data: SongUrl[];
};
export interface SongArtist {
albumSize: number;
id: number;
img1v1Id: number;
img1v1Url: string;
musicSize: number;
name: string;
picId: number;
picUrl: string;
topicPerson: number;
alias?: string[];
}
type SongInfo = {
al: {
id: number;
name: string;
picUrl: string;
};
ar: SongArtist[];
dt: number;
id: number;
name: string;
publishTime: number;
fee?: number;
status?: number;
};
type SongInfoResponse = {
songs?: SongInfo[];
};
export const getSongUrl = async (id: number) => {
const data = {
ids: `[${id}]`,
level: 'standard',
encodeType: 'flac',
};
const res = await request('POST', 'https://interface.music.163.com/eapi/song/enhance/player/url/v1', data, {
crypto: 'eapi',
url: '/api/song/enhance/player/url/v1',
cookie: {},
});
const url = (res as SongUrlResponse).data[0]?.url?.replace('http://', 'https://');
return url || `https://music.163.com/song/media/outer/url?id=${id}.mp3`;
};
const mapSongList = (songs: SongInfo[]) => {
return songs.map((song) => {
const artists = song.ar || [];
return {
title: song.name,
author: artists.reduce((i, v) => (i ? `${i} / ` : i) + v.name, ''),
pic: song?.al?.picUrl,
url: song.id,
lrc: song.id,
};
});
};
export const getSongInfo = async (id: number) => {
const ids = [id];
const data = {
c: `[${ids.map((id) => `{"id":${id}}`).join(',')}]`,
};
const res = (await request('POST', 'https://music.163.com/api/v3/song/detail', data, {
crypto: 'weapi',
})) as SongInfoResponse;
if (!res.songs) {
throw res;
}
return mapSongList(res.songs);
};

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import { get_song_info, get_song_url } from '@/components/player/netease/song'; import { getSongInfo, getSongUrl } from '@/components/player/netease/song';
export type Song = { export type Song = {
name: string; name: string;
@ -9,7 +9,7 @@ export type Song = {
// The props for music player. We support both netease music and direct linked music. // The props for music player. We support both netease music and direct linked music.
export interface MusicPlayerProps { export interface MusicPlayerProps {
netease?: string; netease?: number;
song?: Song; song?: Song;
} }
@ -19,8 +19,8 @@ const song = async (props: MusicPlayerProps): Promise<Song> => {
const { netease, song } = props; const { netease, song } = props;
if (netease) { if (netease) {
const info = await get_song_info(netease); const info = await getSongInfo(netease);
const url = await get_song_url(netease); const url = await getSongUrl(netease);
// Check the return result. // Check the return result.
return { name: info[0].title, artist: info[0].author, url: url, pic: info[0].pic }; return { name: info[0].title, artist: info[0].author, url: url, pic: info[0].pic };