Skip to content

Instantly share code, notes, and snippets.

@Jupiops
Created November 19, 2019 13:11
Show Gist options
  • Save Jupiops/84a359810ab347ed67cac1c5b8380657 to your computer and use it in GitHub Desktop.
Save Jupiops/84a359810ab347ed67cac1c5b8380657 to your computer and use it in GitHub Desktop.
<?php
namespace NeteaseMusic;
class NeteaseMusic
{
private const SECRET = '7246674226682325323F5E6544673A51';
private $_MINI_MODE = false;
private function randomUserAgent()
{
$userAgentList = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1",
"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Mobile/14F89;GameHelper",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:46.0) Gecko/20100101 Firefox/46.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:46.0) Gecko/20100101 Firefox/46.0",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)",
"Mozilla/5.0 (Windows NT 6.3; Win64, x64; Trident/7.0; rv:11.0) like Gecko",
"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",
"Mozilla/5.0 (iPad; CPU OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1"
];
return $userAgentList[rand(0, count($userAgentList) - 1)];
}
private function randomCookies($music_u)
{
$cookiesList = [
'os=pc; osver=Microsoft-Windows-10-Professional-build-10586-64bit; appver=2.0.3.131777; channel=netease; __remember_me=true',
'MUSIC_U=' . $music_u . '; buildver=1506310743; resolution=1920x1080; mobilename=MI5; osver=7.0.1; channel=coolapk; os=android; appver=4.2.0',
'osver=%E7%89%88%E6%9C%AC%2010.13.3%EF%BC%88%E7%89%88%E5%8F%B7%2017D47%EF%BC%89; os=osx; appver=1.5.9; MUSIC_U=' . $music_u . '; channel=netease;'
];
return $cookiesList[rand(0, count($cookiesList) - 1)];
}
public function mini()
{
$this->_MINI_MODE = true;
return $this;
}
/**
* Get a list of songs based on keywords
* @param {Integer} string Key words
* @return
*/
function search($keyword, $type = 1, $limit = 10, $page = 1)
{
$body = array(
'method' => 'POST',
'params' => array(
's' => $keyword,
// 1: Single, 10: Album, 100: Singer, 1000: Song, 1002: User, 1004: MV, 1006: Lyrics, 1009: Radio, 1014: Video
'type' => $type,
'limit' => $limit,
'total' => true,
'offset' => $page - 1
),
'url' => 'http://music.163.com/api/cloudsearch/pc'
);
$form = $this->neteaseAESECB($body);
$options = $this->getHttpOption('POST', strlen($form));
$raw = $this->makeRequest('/api/linux/forward', $form, $options);
if ($this->_MINI_MODE) {
$this->_MINI_MODE = false;
$raw = json_decode($raw, true, 512, JSON_BIGINT_AS_STRING);
return json_encode($this->clear_data($raw["result"]["songs"]), JSON_UNESCAPED_SLASHES);
} else return $raw;
}
/**
* Get artist information based on artist id
* @param {Integer} string Artist id
* @return {Promise}
*/
function artist($id, $limit = 50)
{
$body = array(
'method' => 'GET',
'params' => array(
'id' => $id,
'ext' => true,
'top' => $limit
),
'url' => "http://music.163.com/api/v1/artist/$id"
);
$form = $this->neteaseAESECB($body);
$options = $this->getHttpOption('POST', strlen($form));
$raw = $this->makeRequest('/api/linux/forward', $form, $options);
if ($this->_MINI_MODE) {
$this->_MINI_MODE = false;
$raw = json_decode($raw, true, 512, JSON_BIGINT_AS_STRING);
return json_encode($this->clear_data($raw["hotSongs"]), JSON_UNESCAPED_SLASHES);
} else return $raw;
}
/**
* Get song list information and song list based on song list id
* @param {Integer} string Song list id
* @return {Promise}
*/
function playlist($id)
{
$body = array(
'method' => 'POST',
'params' => array(
'id' => $id,
'n' => 1000
),
'url' => 'http://music.163.com/api/v3/playlist/detail'
);
$form = $this->neteaseAESECB($body);
$options = $this->getHttpOption('POST', strlen($form));
$raw = $this->makeRequest('/api/linux/forward', $form, $options);
return $raw;
}
/**
* 根据专辑 id 获取专辑信息及歌曲列表
* @param {Integer} string 专辑 id
* @return {Promise}
*/
function album($id)
{
$body = array(
'method' => 'GET',
'params' => array('id' => $id),
'url' => "http://music.163.com/api/v1/album/$id"
);
$form = $this->neteaseAESECB($body);
$options = $this->getHttpOption('POST', strlen($form));
$raw = $this->makeRequest('/api/linux/forward', $form, $options);
if ($this->_MINI_MODE) {
$this->_MINI_MODE = false;
$raw = json_decode($raw, true, 512, JSON_BIGINT_AS_STRING);
return json_encode($this->clear_data($raw["songs"]), JSON_UNESCAPED_SLASHES);
} else return $raw;
}
/**
* 根据歌曲 id 获取歌曲信息
* @param {Integer} string 歌曲 id
* @return {Promise}
*/
function song($id)
{
$body = array(
'method' => 'POST',
'params' => array(
'c' => "[{id: $id}]"
),
'url' => 'http://music.163.com/api/v3/song/detail'
);
$form = $this->neteaseAESECB($body);
$options = $this->getHttpOption('POST', strlen($form));
$raw = $this->makeRequest('/api/linux/forward', $form, $options);
if ($this->_MINI_MODE) {
$this->_MINI_MODE = false;
$raw = json_decode($raw, true, 512, JSON_BIGINT_AS_STRING);
return json_encode($this->clear_data($raw["songs"]), JSON_UNESCAPED_SLASHES);
} else return $raw;
}
/**
* Get song resource address based on song id
* @param {Integer} string song id
* @return {Promise}
*/
function url($id, $br = 320)
{
$body = array(
'method' => 'POST',
'params' => array(
'ids' => "[$id]",
'br' => $br * 1000
),
'url' => 'http://music.163.com/api/song/enhance/player/url'
);
$form = $this->neteaseAESECB($body);
$options = $this->getHttpOption('POST', strlen($form));
return $this->makeRequest('/api/linux/forward', $form, $options);
}
private
function getHttpOption($method, $contentLength)
{
$header = array(
'Referer' => 'https://music.163.com/',
'Cookie' => $this->randomCookies($this->getRandomHex(128)),
'User-Agent' => $this->randomUserAgent(),
'X-Real-IP' => '118.88.88.88'
);
if ('POST' === $method) {
$header['Content-Type'] = 'application/x-www-form-urlencoded';
if ($contentLength) {
$header['Content-Length'] = $contentLength;
}
}
$options = array('http' => array(
'method' => $method,
'header' => $this->build_header_str($header),
// "ssl" => array(
//// "verify_peer" => false,
//// "verify_peer_name" => false,
//// ),
));
return $options;
}
private
function makeRequest($path, $form, $options)
{
$options['http']['content'] = $form;
return file_get_contents('https://music.163.com' . $path, false, stream_context_create($options));
}
private
function build_header_str($input)
{
return implode("\r\n", array_map(
function ($v, $k) {
return sprintf("%s: %s", $k, $v);
},
$input,
array_keys($input)
)) . "\r\n";
}
private function getRandomHex($strlen)
{
return implode(array_map(function () {
return dechex(mt_rand(0, 15));
}, array_fill(0, $strlen, null)));
}
function neteaseAESECB($body)
{
$body = json_encode($body, JSON_UNESCAPED_SLASHES);
$password = pack("H*", self::SECRET);
$cipher = openssl_encrypt($body, "AES-128-ECB", $password, OPENSSL_RAW_DATA);
$hex = bin2hex($cipher);
$form = array(
'eparams' => strtoupper($hex)
);
return http_build_query($form);
}
private function clear_data($result)
{
// you can modify it by yourself, change to your API?!
foreach ($result as $key => $vo) {
$data[$key] = array(
'id' => $vo["id"],
'name' => $vo["name"],
'album' => array(
'id' => $vo['al']['id'],
'name' => $vo['al']['name'],
'cover' => array_key_exists('picUrl', $vo['al']) ? $vo['al']['picUrl'] : 'https://p1.music.126.net/' . self::Id2Url($vo['al']['pic_str'] ?: $vo['al']['pic']) . '/' . ($vo['al']['pic_str'] ?: $vo['al']['pic']) . '.jpg',
),
// 'cover' => 'https://p4.music.126.net/' . self::Id2Url($vo['al']['pic_str'] ?: $vo['al']['pic']) . '/' . ($vo['al']['pic_str'] ?: $vo['al']['pic']) . '.jpg',
// 'url' => 'http://music.163.com/song/media/outer/url?id=' . $vo["id"],
//'lyric'=>$vo["id"],
'artists' => array(),
);
foreach ($vo['ar'] as $key_ => $vvo) {
$data[$key]['artists'][$key_]['id'] = $vvo['id'];
$data[$key]['artists'][$key_]['name'] = $vvo['name'];
}
// $data[$key]['artist'] = implode(', ', $data[$key]['artist']);
}
return $data;
}
/* static url encrypt, use for pic*/
public function Id2Url($id)
{
$byte1[] = $this->Str2Arr('3go8&$8*3*3h0k(2)2');
$byte2[] = $this->Str2Arr($id);
$magic = $byte1[0];
$song_id = $byte2[0];
for ($i = 0; $i < count($song_id); $i++) $song_id[$i] = $song_id[$i] ^ $magic[$i % count($magic)];
$result = base64_encode(md5($this->Arr2Str($song_id), 1));
$result = str_replace('/', '_', $result);
$result = str_replace('+', '-', $result);
return $result;
}
private function Str2Arr($string)
{
$bytes = array();
for ($i = 0; $i < strlen($string); $i++) $bytes[] = ord($string[$i]);
return $bytes;
}
private function Arr2Str($bytes)
{
$str = '';
for ($i = 0; $i < count($bytes); $i++) $str .= chr($bytes[$i]);
return $str;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment